fix(layer-editor): use stable _key for drag-and-drop sorting

Add a unique `_key` to each layer config to maintain stable sortable IDs
across reorder operations. Update LayerRow to use the sortableId prop
and the `use:sortable` directive. Refactor layer-crud utilities to
generate and propagate `_key` values on creation and initialization.
This commit is contained in:
hyper
2026-07-03 21:49:18 +08:00
parent 186e3c0db6
commit 0add3f27d5
7 changed files with 86 additions and 30 deletions
+6 -2
View File
@@ -463,8 +463,12 @@ export function createDeckStore(initialSrc: string = ""): DeckStore {
setState({
cards: data,
activeTab: 0,
frontLayerConfigs: initLayerConfigsForSide(data, layersStr),
backLayerConfigs: initLayerConfigsForSide(data, backLayersStr),
frontLayerConfigs: layerCrud.withKeys(
initLayerConfigsForSide(data, layersStr),
),
backLayerConfigs: layerCrud.withKeys(
initLayerConfigsForSide(data, backLayersStr),
),
isLoading: false,
});
updateDimensions();
+35 -8
View File
@@ -1,8 +1,19 @@
import type { CardSide, LayerConfig } from "../types";
import type { LayerConfig } from "../types";
/** Creates a new LayerConfig with default placement. */
let nextKey = 1;
export function resetKeyCounter(value = 1) {
nextKey = value;
}
/** Creates a new LayerConfig with default placement and a stable _key. */
export function createDefaultLayer(prop: string): LayerConfig {
return { prop, visible: true, x1: 1, y1: 1, x2: 2, y2: 2 };
return { prop, visible: true, x1: 1, y1: 1, x2: 2, y2: 2, _key: nextKey++ };
}
/** Assign stable _key to layers that don't have one (e.g. parsed from string). */
export function withKeys(layers: LayerConfig[]): LayerConfig[] {
return layers.map((l) => (l._key ? l : { ...l, _key: nextKey++ }));
}
/** Insert a layer and return the new array. */
@@ -11,18 +22,33 @@ export function addLayer(configs: LayerConfig[], prop: string): LayerConfig[] {
}
/** Remove a layer by index and return the new array. */
export function removeLayer(configs: LayerConfig[], index: number): LayerConfig[] {
export function removeLayer(
configs: LayerConfig[],
index: number,
): LayerConfig[] {
return configs.filter((_, i) => i !== index);
}
/** Reorder a layer within the array. Returns new array. */
export function reorderLayers(configs: LayerConfig[], from: number, to: number): LayerConfig[] {
export function reorderLayers(
configs: LayerConfig[],
from: number,
to: number,
): LayerConfig[] {
const next = [...configs];
const [item] = next.splice(from, 1);
next.splice(to, 0, item);
return next;
}
/**
* Convert layer _key to flake ID (unique string) for SortableProvider.
* Allows multiple instances of the same field name.
*/
export function toSortableId(layer: LayerConfig, index: number): string {
return layer._key ? `layer-${layer._key}` : `layer-idx-${index}`;
}
/**
* Recalculate selectedLayer after a structural change (add/remove/reorder).
* Returns the new selectedLayer index, or null.
@@ -42,11 +68,12 @@ export function adjustSelectedLayer(
if (action === "reorder" && to !== undefined) {
if (currentSelected === from) return to;
if (from < to && currentSelected > from && currentSelected <= to) return currentSelected - 1;
if (from > to && currentSelected >= to && currentSelected < from) return currentSelected + 1;
if (from < to && currentSelected > from && currentSelected <= to)
return currentSelected - 1;
if (from > to && currentSelected >= to && currentSelected < from)
return currentSelected + 1;
return currentSelected;
}
// "add" — doesn't affect existing indices
return currentSelected;
}