@@ -40,30 +40,37 @@ export class SorterComponent extends BaseComponent {
4040 // get items from state or config
4141 const items = this . getItems ( ) ;
4242
43- items . forEach ( ( item ) => {
43+ items . forEach ( ( item , index ) => {
4444 const listItem = document . createElement ( "li" ) ;
45- const isEnabled = hasCheckboxes ? item . enabled !== false : true ;
45+ const isFixed = ( index === 0 && this . config . fixFirst ) ||
46+ ( index === items . length - 1 && this . config . fixLast ) ;
47+ const isEnabled = isFixed ? true : ( hasCheckboxes ? item . enabled !== false : true ) ;
4648
4749 listItem . classList . add ( "sortable-item" , "box" , "is-flex" , "is-align-items-center" , "p-3" , "m-2" ) ;
4850 if ( ! isEnabled ) {
4951 listItem . classList . add ( "is-disabled" ) ;
5052 listItem . style . opacity = "0.5" ;
5153 }
5254
53- listItem . setAttribute ( "draggable" , isEnabled ? "true" : "false" ) ;
55+ listItem . setAttribute ( "draggable" , ( isEnabled && ! isFixed ) ? "true" : "false" ) ;
5456 listItem . setAttribute ( "data-value" , item . value || item ) ;
5557 listItem . setAttribute ( "data-enabled" , isEnabled . toString ( ) ) ;
56- listItem . style . cursor = isEnabled ? "grab" : "default" ;
58+ if ( isFixed ) {
59+ listItem . setAttribute ( "data-fixed" , "true" ) ;
60+ }
61+ listItem . style . cursor = ( isEnabled && ! isFixed ) ? "grab" : "default" ;
5762
58- const checkboxHtml = hasCheckboxes ? `
63+ const showCheckbox = hasCheckboxes && ! isFixed ;
64+ const checkboxHtml = showCheckbox ? `
5965 <label class="checkbox mr-2" style="cursor: pointer;">
6066 <input type="checkbox" ${ isEnabled ? 'checked' : '' } data-toggle-item="${ item . value || item } ">
6167 </label>` : '' ;
6268
69+ const showDragHandle = ! isFixed ;
6370 listItem . innerHTML = `
64- <span class="drag-handle icon is-small mr-2" style="cursor: ${ isEnabled ? 'grab' : 'not-allowed' } ;">
71+ ${ showDragHandle ? ` <span class="drag-handle icon is-small mr-2" style="cursor: ${ isEnabled ? 'grab' : 'not-allowed' } ;">
6572 ${ this . #getDragIcon( isEnabled ) }
66- </span>
73+ </span>` : '' }
6774 ${ checkboxHtml }
6875 <span class="item-label ${ isEnabled ? '' : 'has-text-dark' } ">${ item . value || item } </span>
6976 ` ;
@@ -160,7 +167,8 @@ export class SorterComponent extends BaseComponent {
160167 */
161168 handleDragStart ( e ) {
162169 if ( ! e . target . classList . contains ( 'sortable-item' ) ) return ;
163- if ( e . target . getAttribute ( 'data-enabled' ) === 'false' ) {
170+ if ( e . target . getAttribute ( 'data-enabled' ) === 'false' ||
171+ e . target . getAttribute ( 'data-fixed' ) === 'true' ) {
164172 e . preventDefault ( ) ;
165173 return ;
166174 }
@@ -188,8 +196,21 @@ export class SorterComponent extends BaseComponent {
188196 const afterElement = this . getDragAfterElement ( sortableList , isHorizontal ? e . clientX : e . clientY , isHorizontal ) ;
189197 const dragging = sortableList . querySelector ( '.dragging' ) ;
190198
199+ // Prevent placing before a fixed-first item or after a fixed-last item
200+ const children = [ ...sortableList . querySelectorAll ( '.sortable-item' ) ] ;
201+ const firstChild = children [ 0 ] ;
202+ const lastChild = children [ children . length - 1 ] ;
203+
191204 if ( afterElement == null ) {
192- sortableList . appendChild ( dragging ) ;
205+ // Would append to end — block if last item is fixed
206+ if ( this . config . fixLast && lastChild && lastChild . getAttribute ( 'data-fixed' ) === 'true' ) {
207+ sortableList . insertBefore ( dragging , lastChild ) ;
208+ } else {
209+ sortableList . appendChild ( dragging ) ;
210+ }
211+ } else if ( this . config . fixFirst && afterElement === firstChild && firstChild . getAttribute ( 'data-fixed' ) === 'true' ) {
212+ // Would insert before the fixed first item — place after it instead
213+ sortableList . insertBefore ( dragging , firstChild . nextSibling ) ;
193214 } else {
194215 sortableList . insertBefore ( dragging , afterElement ) ;
195216 }
@@ -323,32 +344,61 @@ export class SorterComponent extends BaseComponent {
323344 #normalizeStateItems( ) {
324345 const hasCheckboxes = this . #hasCheckboxes( ) ;
325346 const stateItems = this . myState . value ;
347+ const configItems = this . config . items || [ ] ;
326348
327349 if ( ! stateItems ) {
328- return ( this . config . items || [ ] ) . map ( item => ( {
350+ return configItems . map ( item => ( {
329351 value : item ,
330352 enabled : true
331353 } ) ) ;
332354 }
333355
334356 if ( ! Array . isArray ( stateItems ) || stateItems . length === 0 ) {
335- return ( this . config . items || [ ] ) . map ( item => ( {
357+ return configItems . map ( item => ( {
336358 value : item ,
337359 enabled : true
338360 } ) ) ;
339361 }
340362
341363 const needsNormalization = stateItems . some ( item => typeof item !== 'object' || item === null || ! ( 'value' in item ) ) ;
342364 const shouldEnableAll = ! hasCheckboxes && stateItems . some ( item => item && item . enabled === false ) ;
365+ const needsFixEnforce = this . config . fixFirst || this . config . fixLast ;
343366
344- if ( ! needsNormalization && ! shouldEnableAll ) {
367+ if ( ! needsNormalization && ! shouldEnableAll && ! needsFixEnforce ) {
345368 return null ;
346369 }
347370
348- return stateItems . map ( item => ( {
371+ let items = stateItems . map ( item => ( {
349372 value : ( typeof item === 'object' && item !== null && 'value' in item ) ? item . value : item ,
350373 enabled : hasCheckboxes ? ( typeof item === 'object' && item !== null && item . enabled === false ? false : true ) : true
351374 } ) ) ;
375+
376+ // Ensure fixed items are in their correct positions
377+ if ( this . config . fixFirst && configItems . length > 0 ) {
378+ const fixedValue = configItems [ 0 ] ;
379+ const idx = items . findIndex ( i => i . value === fixedValue ) ;
380+ if ( idx > 0 ) {
381+ const [ fixedItem ] = items . splice ( idx , 1 ) ;
382+ fixedItem . enabled = true ;
383+ items . unshift ( fixedItem ) ;
384+ } else if ( idx === 0 ) {
385+ items [ 0 ] . enabled = true ;
386+ }
387+ }
388+
389+ if ( this . config . fixLast && configItems . length > 0 ) {
390+ const fixedValue = configItems [ configItems . length - 1 ] ;
391+ const idx = items . findIndex ( i => i . value === fixedValue ) ;
392+ if ( idx >= 0 && idx < items . length - 1 ) {
393+ const [ fixedItem ] = items . splice ( idx , 1 ) ;
394+ fixedItem . enabled = true ;
395+ items . push ( fixedItem ) ;
396+ } else if ( idx === items . length - 1 ) {
397+ items [ items . length - 1 ] . enabled = true ;
398+ }
399+ }
400+
401+ return items ;
352402 }
353403}
354404
0 commit comments