@rogieking/figui3 11.1.5 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/skills/fig-editor/SKILL.md +7 -3
- package/.cursor/skills/fig-editor/components.md +52 -1
- package/.cursor/skills/fig-editor/reference.md +18 -0
- package/.cursor/skills/fig-lab/SKILL.md +2 -2
- package/.cursor/skills/fig-lab/components.md +0 -32
- package/.cursor/skills/fig-lab/reference.md +0 -14
- package/.cursor/skills/figui3/SKILL.md +2 -2
- package/.cursor/skills/figui3/components.md +17 -1
- package/.cursor/skills/figui3/react.md +1 -0
- package/CHANGELOG.md +25 -0
- package/README.md +31 -2
- package/components.css +19 -16
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +5 -5
- package/fig-editor.css +101 -20
- package/fig-editor.js +1008 -5
- package/fig-lab.css +0 -75
- package/fig-lab.js +0 -672
- package/fig.js +232 -22
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -2902,675 +2902,3 @@ class FigCanvasControl extends HTMLElement {
|
|
|
2902
2902
|
}
|
|
2903
2903
|
}
|
|
2904
2904
|
figLabDefineElement("fig-canvas-control", FigCanvasControl);
|
|
2905
|
-
/* Reorder wrapper */
|
|
2906
|
-
/**
|
|
2907
|
-
* @attr {string} axis - Reorder direction: "vertical" (default) or "horizontal"
|
|
2908
|
-
* @attr {string} handle - Selector for the drag handle within each item
|
|
2909
|
-
* @attr {string} items - Selector limiting which direct children are reorderable
|
|
2910
|
-
* @attr {string} indicator - Drop indicator style: "default" or "ring"
|
|
2911
|
-
* @attr {boolean} disabled - Disable reordering
|
|
2912
|
-
*/
|
|
2913
|
-
class FigReorder extends HTMLElement {
|
|
2914
|
-
static observedAttributes = [
|
|
2915
|
-
"axis",
|
|
2916
|
-
"handle",
|
|
2917
|
-
"items",
|
|
2918
|
-
"indicator",
|
|
2919
|
-
"disabled",
|
|
2920
|
-
];
|
|
2921
|
-
|
|
2922
|
-
static #DRAG_THRESHOLD = 6;
|
|
2923
|
-
|
|
2924
|
-
static #NESTED_DRAG_SELECTORS = [
|
|
2925
|
-
'[draggable]:not([draggable="false"])',
|
|
2926
|
-
'img:not([draggable="false"])',
|
|
2927
|
-
'a[href]:not([draggable="false"])',
|
|
2928
|
-
'input[type="range"]',
|
|
2929
|
-
"fig-handle",
|
|
2930
|
-
"fig-slider",
|
|
2931
|
-
"fig-input-number",
|
|
2932
|
-
"fig-input-gradient",
|
|
2933
|
-
"fig-easing-curve",
|
|
2934
|
-
"fig-angle",
|
|
2935
|
-
"fig-input-audio",
|
|
2936
|
-
"fig-input-wheel",
|
|
2937
|
-
"fig-joystick",
|
|
2938
|
-
"fig-origin-grid",
|
|
2939
|
-
"fig-chooser",
|
|
2940
|
-
"fig-canvas-control",
|
|
2941
|
-
];
|
|
2942
|
-
|
|
2943
|
-
static #OWNED_ATTRIBUTES = new Set([
|
|
2944
|
-
"data-reorder-item",
|
|
2945
|
-
"data-reorder-generated-role",
|
|
2946
|
-
"data-reorder-handle",
|
|
2947
|
-
"role",
|
|
2948
|
-
"tabindex",
|
|
2949
|
-
"aria-label",
|
|
2950
|
-
"aria-roledescription",
|
|
2951
|
-
]);
|
|
2952
|
-
|
|
2953
|
-
#childObserver = null;
|
|
2954
|
-
#attributeObserver = null;
|
|
2955
|
-
#attributeSyncQueued = false;
|
|
2956
|
-
#bindings = new Map();
|
|
2957
|
-
#drag = null;
|
|
2958
|
-
#indicator = null;
|
|
2959
|
-
#liveRegion = null;
|
|
2960
|
-
|
|
2961
|
-
connectedCallback() {
|
|
2962
|
-
this.style.display = "contents";
|
|
2963
|
-
if (!this.hasAttribute("role")) this.setAttribute("role", "list");
|
|
2964
|
-
if (!this.hasAttribute("aria-label")) {
|
|
2965
|
-
this.setAttribute("aria-label", "Reorderable list");
|
|
2966
|
-
}
|
|
2967
|
-
this.#ensureLiveRegion();
|
|
2968
|
-
this.#syncChildren();
|
|
2969
|
-
this.#childObserver = new MutationObserver(() => this.#syncChildren());
|
|
2970
|
-
this.#childObserver.observe(this, { childList: true });
|
|
2971
|
-
this.#syncAttributeObserver();
|
|
2972
|
-
}
|
|
2973
|
-
|
|
2974
|
-
disconnectedCallback() {
|
|
2975
|
-
this.#childObserver?.disconnect();
|
|
2976
|
-
this.#childObserver = null;
|
|
2977
|
-
this.#attributeObserver?.disconnect();
|
|
2978
|
-
this.#attributeObserver = null;
|
|
2979
|
-
this.#unbindAll();
|
|
2980
|
-
this.#cancelDrag();
|
|
2981
|
-
this.#removeIndicator();
|
|
2982
|
-
this.#liveRegion?.remove();
|
|
2983
|
-
this.#liveRegion = null;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
attributeChangedCallback(name) {
|
|
2987
|
-
if (this.isConnected) {
|
|
2988
|
-
if (this.#disabled) this.#cancelDrag();
|
|
2989
|
-
if (name === "indicator") this.#syncIndicatorStyle();
|
|
2990
|
-
this.#syncAttributeObserver();
|
|
2991
|
-
this.#syncChildren();
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
refresh() {
|
|
2996
|
-
if (this.isConnected) this.#syncChildren();
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
#syncAttributeObserver() {
|
|
3000
|
-
if (!this.#itemsSelector) {
|
|
3001
|
-
this.#attributeObserver?.disconnect();
|
|
3002
|
-
this.#attributeObserver = null;
|
|
3003
|
-
return;
|
|
3004
|
-
}
|
|
3005
|
-
if (this.#attributeObserver) return;
|
|
3006
|
-
this.#attributeObserver = new MutationObserver((records) => {
|
|
3007
|
-
const relevant = records.some(
|
|
3008
|
-
(record) =>
|
|
3009
|
-
record.target.parentElement === this &&
|
|
3010
|
-
!FigReorder.#OWNED_ATTRIBUTES.has(record.attributeName) &&
|
|
3011
|
-
!(record.attributeName === "class" && record.target === this.#drag?.item),
|
|
3012
|
-
);
|
|
3013
|
-
if (!relevant || this.#attributeSyncQueued) return;
|
|
3014
|
-
this.#attributeSyncQueued = true;
|
|
3015
|
-
queueMicrotask(() => {
|
|
3016
|
-
this.#attributeSyncQueued = false;
|
|
3017
|
-
if (this.isConnected) this.#syncChildren();
|
|
3018
|
-
});
|
|
3019
|
-
});
|
|
3020
|
-
this.#attributeObserver.observe(this, { subtree: true, attributes: true });
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
get #disabled() {
|
|
3024
|
-
return (
|
|
3025
|
-
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
|
|
3026
|
-
);
|
|
3027
|
-
}
|
|
3028
|
-
|
|
3029
|
-
get #axis() {
|
|
3030
|
-
const value = (this.getAttribute("axis") || "vertical").trim().toLowerCase();
|
|
3031
|
-
return value === "horizontal" ? "horizontal" : "vertical";
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
get #handleSelector() {
|
|
3035
|
-
return (this.getAttribute("handle") || "").trim();
|
|
3036
|
-
}
|
|
3037
|
-
|
|
3038
|
-
get #itemsSelector() {
|
|
3039
|
-
return (this.getAttribute("items") || "").trim();
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
get #indicatorStyle() {
|
|
3043
|
-
return (this.getAttribute("indicator") || "default").trim().toLowerCase();
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
|
-
#getAllElementChildren() {
|
|
3047
|
-
return [...this.children].filter(
|
|
3048
|
-
(node) =>
|
|
3049
|
-
node.nodeType === Node.ELEMENT_NODE &&
|
|
3050
|
-
!node.hasAttribute("data-reorder-live"),
|
|
3051
|
-
);
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3054
|
-
#getElementChildren() {
|
|
3055
|
-
const children = this.#getAllElementChildren();
|
|
3056
|
-
const selector = this.#itemsSelector;
|
|
3057
|
-
if (!selector) return children;
|
|
3058
|
-
try {
|
|
3059
|
-
return children.filter((child) => child.matches(selector));
|
|
3060
|
-
} catch {
|
|
3061
|
-
return [];
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
#syncChildren() {
|
|
3066
|
-
const allChildren = this.#getAllElementChildren();
|
|
3067
|
-
const children = this.#getElementChildren();
|
|
3068
|
-
const childSet = new Set(children);
|
|
3069
|
-
|
|
3070
|
-
for (const [child, binding] of this.#bindings) {
|
|
3071
|
-
if (!childSet.has(child)) {
|
|
3072
|
-
binding.target.removeEventListener(
|
|
3073
|
-
"pointerdown",
|
|
3074
|
-
binding.onPointerDown,
|
|
3075
|
-
true,
|
|
3076
|
-
);
|
|
3077
|
-
binding.target.removeEventListener("keydown", binding.onKeyDown);
|
|
3078
|
-
this.#restoreKeyboardAttrs(binding);
|
|
3079
|
-
this.#bindings.delete(child);
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
this.#clearHandleMarks(allChildren);
|
|
3084
|
-
this.#clearReorderItemMarks(allChildren);
|
|
3085
|
-
this.#markNonItems(allChildren.filter((child) => !childSet.has(child)));
|
|
3086
|
-
const reorderable = !this.#disabled && children.length >= 2;
|
|
3087
|
-
this.#markReorderItems(children, reorderable);
|
|
3088
|
-
|
|
3089
|
-
if (!reorderable) {
|
|
3090
|
-
this.#unbindAll();
|
|
3091
|
-
return;
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
for (const child of children) {
|
|
3095
|
-
const target = this.#getDragTarget(child);
|
|
3096
|
-
const existing = this.#bindings.get(child);
|
|
3097
|
-
if (existing && existing.target === target) continue;
|
|
3098
|
-
if (existing) {
|
|
3099
|
-
existing.target.removeEventListener(
|
|
3100
|
-
"pointerdown",
|
|
3101
|
-
existing.onPointerDown,
|
|
3102
|
-
true,
|
|
3103
|
-
);
|
|
3104
|
-
this.#bindings.delete(child);
|
|
3105
|
-
}
|
|
3106
|
-
if (!target) continue;
|
|
3107
|
-
this.#bindChild(child, target);
|
|
3108
|
-
}
|
|
3109
|
-
|
|
3110
|
-
this.#markHandles(children);
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
#clearReorderItemMarks(children) {
|
|
3114
|
-
for (const child of children) {
|
|
3115
|
-
child.removeAttribute("data-reorder-item");
|
|
3116
|
-
if (child.hasAttribute("data-reorder-generated-role")) {
|
|
3117
|
-
child.removeAttribute("role");
|
|
3118
|
-
child.removeAttribute("data-reorder-generated-role");
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
#markReorderItems(children, reorderable = true) {
|
|
3124
|
-
for (const child of children) {
|
|
3125
|
-
if (reorderable) child.setAttribute("data-reorder-item", "");
|
|
3126
|
-
if (!child.hasAttribute("role")) {
|
|
3127
|
-
child.setAttribute("role", "listitem");
|
|
3128
|
-
child.setAttribute("data-reorder-generated-role", "");
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
#markNonItems(children) {
|
|
3134
|
-
for (const child of children) {
|
|
3135
|
-
if (!child.hasAttribute("role")) {
|
|
3136
|
-
child.setAttribute("role", "none");
|
|
3137
|
-
child.setAttribute("data-reorder-generated-role", "");
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
#clearHandleMarks(children) {
|
|
3143
|
-
for (const child of children) {
|
|
3144
|
-
child
|
|
3145
|
-
.querySelectorAll("[data-reorder-handle]")
|
|
3146
|
-
.forEach((node) => {
|
|
3147
|
-
node.removeAttribute("data-reorder-handle");
|
|
3148
|
-
node.removeAttribute("aria-roledescription");
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
|
-
}
|
|
3152
|
-
|
|
3153
|
-
#markHandles(children) {
|
|
3154
|
-
if (!this.#handleSelector) return;
|
|
3155
|
-
for (const child of children) {
|
|
3156
|
-
const handle = child.querySelector(this.#handleSelector);
|
|
3157
|
-
if (handle) {
|
|
3158
|
-
handle.setAttribute("data-reorder-handle", "");
|
|
3159
|
-
handle.setAttribute("aria-roledescription", "reorder handle");
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
#getDragTarget(child) {
|
|
3165
|
-
if (this.#handleSelector) {
|
|
3166
|
-
return child.querySelector(this.#handleSelector);
|
|
3167
|
-
}
|
|
3168
|
-
return child;
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
#bindChild(child, target) {
|
|
3172
|
-
const onPointerDown = (event) => {
|
|
3173
|
-
if (this.#disabled || this.#getElementChildren().length < 2) return;
|
|
3174
|
-
if (event.button !== 0) return;
|
|
3175
|
-
if (
|
|
3176
|
-
!this.#handleSelector &&
|
|
3177
|
-
this.#isNestedDragTarget(event.target, child)
|
|
3178
|
-
) {
|
|
3179
|
-
return;
|
|
3180
|
-
}
|
|
3181
|
-
this.#startPendingDrag(event, child, target);
|
|
3182
|
-
};
|
|
3183
|
-
const originalTabIndex = target.getAttribute("tabindex");
|
|
3184
|
-
const originalAriaLabel = target.getAttribute("aria-label");
|
|
3185
|
-
const originalRole = target.getAttribute("role");
|
|
3186
|
-
const itemName =
|
|
3187
|
-
child.getAttribute("aria-label") ||
|
|
3188
|
-
child.textContent?.trim().replace(/\s+/g, " ").slice(0, 80) ||
|
|
3189
|
-
"item";
|
|
3190
|
-
target.setAttribute("tabindex", "0");
|
|
3191
|
-
target.setAttribute("aria-label", `Move ${itemName}`);
|
|
3192
|
-
if (target !== child && !target.hasAttribute("role")) {
|
|
3193
|
-
target.setAttribute("role", "button");
|
|
3194
|
-
}
|
|
3195
|
-
const onKeyDown = (event) => {
|
|
3196
|
-
if (this.#disabled) return;
|
|
3197
|
-
const horizontal = this.#axis === "horizontal";
|
|
3198
|
-
const previousKey = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
3199
|
-
const nextKey = horizontal ? "ArrowRight" : "ArrowDown";
|
|
3200
|
-
if (
|
|
3201
|
-
event.key !== previousKey &&
|
|
3202
|
-
event.key !== nextKey &&
|
|
3203
|
-
event.key !== "Home" &&
|
|
3204
|
-
event.key !== "End"
|
|
3205
|
-
) {
|
|
3206
|
-
return;
|
|
3207
|
-
}
|
|
3208
|
-
event.preventDefault();
|
|
3209
|
-
event.stopPropagation();
|
|
3210
|
-
const items = this.#getElementChildren();
|
|
3211
|
-
const oldIndex = items.indexOf(child);
|
|
3212
|
-
let newIndex = oldIndex;
|
|
3213
|
-
if (event.key === previousKey) newIndex = Math.max(0, oldIndex - 1);
|
|
3214
|
-
else if (event.key === nextKey) {
|
|
3215
|
-
newIndex = Math.min(items.length - 1, oldIndex + 1);
|
|
3216
|
-
} else if (event.key === "Home") newIndex = 0;
|
|
3217
|
-
else if (event.key === "End") newIndex = items.length - 1;
|
|
3218
|
-
if (newIndex === oldIndex) {
|
|
3219
|
-
this.#announce(`${itemName}, position ${oldIndex + 1} of ${items.length}`);
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
this.#moveItemToFinalIndex(child, newIndex);
|
|
3223
|
-
this.#syncChildren();
|
|
3224
|
-
this.#getDragTarget(child)?.focus();
|
|
3225
|
-
this.dispatchEvent(
|
|
3226
|
-
new CustomEvent("reorder", {
|
|
3227
|
-
bubbles: true,
|
|
3228
|
-
detail: { oldIndex, newIndex, item: child },
|
|
3229
|
-
}),
|
|
3230
|
-
);
|
|
3231
|
-
this.#announce(`${itemName}, position ${newIndex + 1} of ${items.length}`);
|
|
3232
|
-
};
|
|
3233
|
-
|
|
3234
|
-
target.addEventListener("pointerdown", onPointerDown, true);
|
|
3235
|
-
target.addEventListener("keydown", onKeyDown);
|
|
3236
|
-
this.#bindings.set(child, {
|
|
3237
|
-
target,
|
|
3238
|
-
onPointerDown,
|
|
3239
|
-
onKeyDown,
|
|
3240
|
-
originalTabIndex,
|
|
3241
|
-
originalAriaLabel,
|
|
3242
|
-
originalRole,
|
|
3243
|
-
});
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
#restoreKeyboardAttrs(binding) {
|
|
3247
|
-
const { target, originalTabIndex, originalAriaLabel, originalRole } = binding;
|
|
3248
|
-
if (originalTabIndex === null) target.removeAttribute("tabindex");
|
|
3249
|
-
else target.setAttribute("tabindex", originalTabIndex);
|
|
3250
|
-
if (originalAriaLabel === null) target.removeAttribute("aria-label");
|
|
3251
|
-
else target.setAttribute("aria-label", originalAriaLabel);
|
|
3252
|
-
if (originalRole === null) target.removeAttribute("role");
|
|
3253
|
-
else target.setAttribute("role", originalRole);
|
|
3254
|
-
}
|
|
3255
|
-
|
|
3256
|
-
#ensureLiveRegion() {
|
|
3257
|
-
if (this.#liveRegion?.isConnected) return;
|
|
3258
|
-
const region = document.createElement("span");
|
|
3259
|
-
region.setAttribute("data-reorder-live", "");
|
|
3260
|
-
region.setAttribute("role", "status");
|
|
3261
|
-
region.setAttribute("aria-live", "polite");
|
|
3262
|
-
region.setAttribute("aria-atomic", "true");
|
|
3263
|
-
Object.assign(region.style, {
|
|
3264
|
-
position: "absolute",
|
|
3265
|
-
width: "1px",
|
|
3266
|
-
height: "1px",
|
|
3267
|
-
padding: "0",
|
|
3268
|
-
margin: "-1px",
|
|
3269
|
-
overflow: "hidden",
|
|
3270
|
-
clip: "rect(0, 0, 0, 0)",
|
|
3271
|
-
whiteSpace: "nowrap",
|
|
3272
|
-
border: "0",
|
|
3273
|
-
});
|
|
3274
|
-
this.#liveRegion = region;
|
|
3275
|
-
document.body.appendChild(region);
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
#announce(message) {
|
|
3279
|
-
if (!this.#liveRegion) return;
|
|
3280
|
-
this.#liveRegion.textContent = "";
|
|
3281
|
-
requestAnimationFrame(() => {
|
|
3282
|
-
if (this.#liveRegion) this.#liveRegion.textContent = message;
|
|
3283
|
-
});
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
#isNestedDragTarget(target, child) {
|
|
3287
|
-
let node = target;
|
|
3288
|
-
while (node && node !== child) {
|
|
3289
|
-
if (node instanceof Element) {
|
|
3290
|
-
for (const selector of FigReorder.#NESTED_DRAG_SELECTORS) {
|
|
3291
|
-
if (node.matches(selector)) return true;
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
node = node.parentElement;
|
|
3295
|
-
}
|
|
3296
|
-
return false;
|
|
3297
|
-
}
|
|
3298
|
-
|
|
3299
|
-
static #DRAGGING_BODY_CLASS = "fig-reorder-dragging";
|
|
3300
|
-
|
|
3301
|
-
static #setDocumentDragging(active) {
|
|
3302
|
-
document.body.classList.toggle(FigReorder.#DRAGGING_BODY_CLASS, active);
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
#startPendingDrag(event, item, target) {
|
|
3306
|
-
this.#cancelDrag();
|
|
3307
|
-
|
|
3308
|
-
const state = {
|
|
3309
|
-
item,
|
|
3310
|
-
target,
|
|
3311
|
-
pointerId: event.pointerId,
|
|
3312
|
-
oldIndex: this.#getElementChildren().indexOf(item),
|
|
3313
|
-
targetIndex: this.#getElementChildren().indexOf(item),
|
|
3314
|
-
startX: event.clientX,
|
|
3315
|
-
startY: event.clientY,
|
|
3316
|
-
active: false,
|
|
3317
|
-
onMove: null,
|
|
3318
|
-
onUp: null,
|
|
3319
|
-
onKeyDown: null,
|
|
3320
|
-
};
|
|
3321
|
-
|
|
3322
|
-
state.onMove = (moveEvent) => {
|
|
3323
|
-
if (moveEvent.pointerId !== state.pointerId) return;
|
|
3324
|
-
|
|
3325
|
-
const dx = moveEvent.clientX - state.startX;
|
|
3326
|
-
const dy = moveEvent.clientY - state.startY;
|
|
3327
|
-
|
|
3328
|
-
if (!state.active) {
|
|
3329
|
-
if (dx * dx + dy * dy < FigReorder.#DRAG_THRESHOLD * FigReorder.#DRAG_THRESHOLD) {
|
|
3330
|
-
return;
|
|
3331
|
-
}
|
|
3332
|
-
state.active = true;
|
|
3333
|
-
event.preventDefault();
|
|
3334
|
-
event.stopPropagation();
|
|
3335
|
-
item.classList.add("dragging");
|
|
3336
|
-
FigReorder.#setDocumentDragging(true);
|
|
3337
|
-
try {
|
|
3338
|
-
target.setPointerCapture(state.pointerId);
|
|
3339
|
-
} catch {}
|
|
3340
|
-
}
|
|
3341
|
-
|
|
3342
|
-
moveEvent.preventDefault();
|
|
3343
|
-
const pointer =
|
|
3344
|
-
this.#axis === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
|
|
3345
|
-
const index = this.#getInsertIndex(pointer);
|
|
3346
|
-
state.targetIndex = index;
|
|
3347
|
-
this.#updateIndicator(index, item);
|
|
3348
|
-
};
|
|
3349
|
-
|
|
3350
|
-
state.onKeyDown = (keyEvent) => {
|
|
3351
|
-
if (keyEvent.key !== "Escape" || !state.active) return;
|
|
3352
|
-
keyEvent.preventDefault();
|
|
3353
|
-
this.#finishDrag(state, true);
|
|
3354
|
-
};
|
|
3355
|
-
|
|
3356
|
-
state.onUp = (upEvent) => {
|
|
3357
|
-
if (upEvent.pointerId !== state.pointerId) return;
|
|
3358
|
-
this.#finishDrag(state, upEvent.type === "pointercancel");
|
|
3359
|
-
};
|
|
3360
|
-
|
|
3361
|
-
this.#drag = state;
|
|
3362
|
-
window.addEventListener("pointermove", state.onMove);
|
|
3363
|
-
window.addEventListener("pointerup", state.onUp);
|
|
3364
|
-
window.addEventListener("pointercancel", state.onUp);
|
|
3365
|
-
window.addEventListener("keydown", state.onKeyDown);
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
#getInsertIndex(pointer) {
|
|
3369
|
-
const items = this.#getElementChildren();
|
|
3370
|
-
const horizontal = this.#axis === "horizontal";
|
|
3371
|
-
|
|
3372
|
-
for (let i = 0; i < items.length; i++) {
|
|
3373
|
-
const rect = items[i].getBoundingClientRect();
|
|
3374
|
-
const midpoint = horizontal
|
|
3375
|
-
? rect.left + rect.width / 2
|
|
3376
|
-
: rect.top + rect.height / 2;
|
|
3377
|
-
if (pointer < midpoint) return i;
|
|
3378
|
-
}
|
|
3379
|
-
|
|
3380
|
-
return items.length;
|
|
3381
|
-
}
|
|
3382
|
-
|
|
3383
|
-
#shouldShowIndicator(item, index) {
|
|
3384
|
-
const items = this.#getElementChildren();
|
|
3385
|
-
const currentIndex = items.indexOf(item);
|
|
3386
|
-
if (currentIndex === -1) return false;
|
|
3387
|
-
|
|
3388
|
-
const clamped = Math.max(0, Math.min(index, items.length));
|
|
3389
|
-
|
|
3390
|
-
if (index === currentIndex) return false;
|
|
3391
|
-
|
|
3392
|
-
// Hide only the redundant bottom line when the dragged item is already last.
|
|
3393
|
-
if (clamped >= items.length && currentIndex === items.length - 1) {
|
|
3394
|
-
return false;
|
|
3395
|
-
}
|
|
3396
|
-
|
|
3397
|
-
return true;
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
|
-
#getReorderBounds(items) {
|
|
3401
|
-
if (!items.length) return null;
|
|
3402
|
-
|
|
3403
|
-
let left = Infinity;
|
|
3404
|
-
let right = -Infinity;
|
|
3405
|
-
let top = Infinity;
|
|
3406
|
-
let bottom = -Infinity;
|
|
3407
|
-
|
|
3408
|
-
for (const item of items) {
|
|
3409
|
-
const rect = item.getBoundingClientRect();
|
|
3410
|
-
left = Math.min(left, rect.left);
|
|
3411
|
-
right = Math.max(right, rect.right);
|
|
3412
|
-
top = Math.min(top, rect.top);
|
|
3413
|
-
bottom = Math.max(bottom, rect.bottom);
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
return {
|
|
3417
|
-
left,
|
|
3418
|
-
top,
|
|
3419
|
-
width: right - left,
|
|
3420
|
-
height: bottom - top,
|
|
3421
|
-
};
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
#ensureIndicator() {
|
|
3425
|
-
if (this.#indicator) return this.#indicator;
|
|
3426
|
-
|
|
3427
|
-
const indicator = document.createElement("div");
|
|
3428
|
-
indicator.className = "fig-reorder-indicator";
|
|
3429
|
-
indicator.setAttribute("data-axis", this.#axis);
|
|
3430
|
-
indicator.classList.toggle(
|
|
3431
|
-
"fig-reorder-indicator-ring",
|
|
3432
|
-
this.#indicatorStyle === "ring",
|
|
3433
|
-
);
|
|
3434
|
-
document.body.appendChild(indicator);
|
|
3435
|
-
this.#indicator = indicator;
|
|
3436
|
-
return indicator;
|
|
3437
|
-
}
|
|
3438
|
-
|
|
3439
|
-
#syncIndicatorStyle() {
|
|
3440
|
-
this.#indicator?.classList.toggle(
|
|
3441
|
-
"fig-reorder-indicator-ring",
|
|
3442
|
-
this.#indicatorStyle === "ring",
|
|
3443
|
-
);
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
|
-
#updateIndicator(index, item) {
|
|
3447
|
-
const items = this.#getElementChildren();
|
|
3448
|
-
if (!this.#shouldShowIndicator(item, index)) {
|
|
3449
|
-
this.#removeIndicator();
|
|
3450
|
-
return;
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
const bounds = this.#getReorderBounds(items);
|
|
3454
|
-
if (!bounds) {
|
|
3455
|
-
this.#removeIndicator();
|
|
3456
|
-
return;
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
const indicator = this.#ensureIndicator();
|
|
3460
|
-
indicator.setAttribute("data-axis", this.#axis);
|
|
3461
|
-
|
|
3462
|
-
if (this.#axis === "horizontal") {
|
|
3463
|
-
let x;
|
|
3464
|
-
if (index <= 0) {
|
|
3465
|
-
x = items[0].getBoundingClientRect().left;
|
|
3466
|
-
} else if (index >= items.length) {
|
|
3467
|
-
x = items[items.length - 1].getBoundingClientRect().right;
|
|
3468
|
-
} else {
|
|
3469
|
-
x = items[index].getBoundingClientRect().left;
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
|
-
indicator.style.left = `${x - 1}px`;
|
|
3473
|
-
indicator.style.top = `${bounds.top}px`;
|
|
3474
|
-
indicator.style.width = "2px";
|
|
3475
|
-
indicator.style.height = `${bounds.height}px`;
|
|
3476
|
-
return;
|
|
3477
|
-
}
|
|
3478
|
-
|
|
3479
|
-
let y;
|
|
3480
|
-
if (index <= 0) {
|
|
3481
|
-
y = items[0].getBoundingClientRect().top;
|
|
3482
|
-
} else if (index >= items.length) {
|
|
3483
|
-
y = items[items.length - 1].getBoundingClientRect().bottom;
|
|
3484
|
-
} else {
|
|
3485
|
-
y = items[index].getBoundingClientRect().top;
|
|
3486
|
-
}
|
|
3487
|
-
|
|
3488
|
-
indicator.style.left = `${bounds.left}px`;
|
|
3489
|
-
indicator.style.top = `${y - 1}px`;
|
|
3490
|
-
indicator.style.width = `${bounds.width}px`;
|
|
3491
|
-
indicator.style.height = "2px";
|
|
3492
|
-
}
|
|
3493
|
-
|
|
3494
|
-
#removeIndicator() {
|
|
3495
|
-
this.#indicator?.remove();
|
|
3496
|
-
this.#indicator = null;
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
#moveItemToIndex(item, index) {
|
|
3500
|
-
const items = this.#getElementChildren();
|
|
3501
|
-
const clamped = Math.max(0, Math.min(index, items.length));
|
|
3502
|
-
|
|
3503
|
-
if (clamped >= items.length) {
|
|
3504
|
-
const last = items[items.length - 1];
|
|
3505
|
-
if (last && last !== item) last.after(item);
|
|
3506
|
-
return;
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
const ref = items[clamped];
|
|
3510
|
-
if (ref !== item) this.insertBefore(item, ref);
|
|
3511
|
-
}
|
|
3512
|
-
|
|
3513
|
-
#moveItemToFinalIndex(item, newIndex) {
|
|
3514
|
-
const items = this.#getElementChildren().filter((candidate) => candidate !== item);
|
|
3515
|
-
const ref = items[newIndex] ?? null;
|
|
3516
|
-
if (ref) this.insertBefore(item, ref);
|
|
3517
|
-
else if (items.length) items[items.length - 1].after(item);
|
|
3518
|
-
else this.appendChild(item);
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
#finishDrag(state, revert) {
|
|
3522
|
-
const { item, oldIndex, active, onMove, onUp, onKeyDown } = state;
|
|
3523
|
-
|
|
3524
|
-
window.removeEventListener("pointermove", onMove);
|
|
3525
|
-
window.removeEventListener("pointerup", onUp);
|
|
3526
|
-
window.removeEventListener("pointercancel", onUp);
|
|
3527
|
-
window.removeEventListener("keydown", onKeyDown);
|
|
3528
|
-
|
|
3529
|
-
if (active) {
|
|
3530
|
-
if (!revert) {
|
|
3531
|
-
this.#moveItemToIndex(item, state.targetIndex);
|
|
3532
|
-
const newIndex = this.#getElementChildren().indexOf(item);
|
|
3533
|
-
if (newIndex !== -1 && newIndex !== oldIndex) {
|
|
3534
|
-
this.dispatchEvent(
|
|
3535
|
-
new CustomEvent("reorder", {
|
|
3536
|
-
bubbles: true,
|
|
3537
|
-
detail: { oldIndex, newIndex, item },
|
|
3538
|
-
}),
|
|
3539
|
-
);
|
|
3540
|
-
const name =
|
|
3541
|
-
item.getAttribute("aria-label") ||
|
|
3542
|
-
item.textContent?.trim().replace(/\s+/g, " ").slice(0, 80) ||
|
|
3543
|
-
"item";
|
|
3544
|
-
this.#announce(
|
|
3545
|
-
`${name}, position ${newIndex + 1} of ${this.#getElementChildren().length}`,
|
|
3546
|
-
);
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
|
|
3551
|
-
item.classList.remove("dragging");
|
|
3552
|
-
FigReorder.#setDocumentDragging(false);
|
|
3553
|
-
this.#removeIndicator();
|
|
3554
|
-
if (this.#drag === state) this.#drag = null;
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
#unbindAll() {
|
|
3558
|
-
for (const [, binding] of this.#bindings) {
|
|
3559
|
-
binding.target.removeEventListener(
|
|
3560
|
-
"pointerdown",
|
|
3561
|
-
binding.onPointerDown,
|
|
3562
|
-
true,
|
|
3563
|
-
);
|
|
3564
|
-
binding.target.removeEventListener("keydown", binding.onKeyDown);
|
|
3565
|
-
this.#restoreKeyboardAttrs(binding);
|
|
3566
|
-
}
|
|
3567
|
-
this.#bindings.clear();
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
#cancelDrag() {
|
|
3571
|
-
if (!this.#drag) return;
|
|
3572
|
-
this.#finishDrag(this.#drag, true);
|
|
3573
|
-
}
|
|
3574
|
-
}
|
|
3575
|
-
|
|
3576
|
-
figLabDefineElement("fig-reorder", FigReorder);
|