@plexui/vue 0.2.0 → 0.4.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/dist/es/components/Page/Page.js +89 -0
- package/dist/es/components/Page/Page.js.map +1 -0
- package/dist/es/components/Page/index.js +2 -0
- package/dist/es/components/Page/index.js.map +1 -0
- package/dist/es/components/Sidebar/Sidebar.js +17 -3
- package/dist/es/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/es/components/Sidebar/index.js.map +1 -1
- package/dist/es/components/Sidebar/useSidebarContext.js +7 -2
- package/dist/es/components/Sidebar/useSidebarContext.js.map +1 -1
- package/dist/es/components/Sortable/Sortable.js +48 -3
- package/dist/es/components/Sortable/Sortable.js.map +1 -1
- package/dist/es/components/Sortable/index.js +1 -1
- package/dist/es/components/Sortable/index.js.map +1 -1
- package/dist/es/lib/sortable.js +192 -74
- package/dist/es/lib/sortable.js.map +1 -1
- package/dist/types/components/Page/Page.d.ts +117 -0
- package/dist/types/components/Page/index.d.ts +1 -0
- package/dist/types/components/Sidebar/Sidebar.d.ts +2 -2
- package/dist/types/components/Sidebar/index.d.ts +1 -1
- package/dist/types/components/Sidebar/useSidebarContext.d.ts +13 -0
- package/dist/types/components/Sortable/Sortable.d.ts +71 -2
- package/dist/types/components/Sortable/index.d.ts +1 -1
- package/dist/types/lib/sortable.d.ts +20 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import s from "@plexui/styles/components/Sortable/Sortable.module.css";
|
|
|
6
6
|
// Vue port of packages/ui/src/components/Sortable/Sortable.tsx.
|
|
7
7
|
//
|
|
8
8
|
// - `value` binds with `v-model:value`; `change` fires with it.
|
|
9
|
-
// - React's `Sortable.Handle`
|
|
9
|
+
// - React's `Sortable.Handle` and `Sortable.Target` are the flat `SortableHandle` and `SortableTarget` here.
|
|
10
10
|
// - Attributes reach the rendered element, as React's rest props do.
|
|
11
11
|
const preventNativeDrag = (event) => event.preventDefault();
|
|
12
12
|
const SortableComponent = defineComponent({
|
|
@@ -23,10 +23,18 @@ const SortableComponent = defineComponent({
|
|
|
23
23
|
disabled: { type: Boolean, default: false },
|
|
24
24
|
/** Shows the grips of `SortableHandle`. A hidden grip still takes the keyboard, and the item drags from anywhere but a control. @default true in a table, otherwise false */
|
|
25
25
|
handles: { type: Boolean, default: undefined },
|
|
26
|
+
/** Lists of one group trade items, and a `SortableTarget` of that group takes them. Give each list a `name`. */
|
|
27
|
+
group: { type: String, default: undefined },
|
|
28
|
+
/** Names the list in `move`. */
|
|
29
|
+
name: { type: String, default: undefined },
|
|
26
30
|
},
|
|
27
31
|
emits: {
|
|
28
32
|
"update:value": (_value) => true,
|
|
29
33
|
"change": (_value) => true,
|
|
34
|
+
/** An item moved, inside this list or out of it. Out of it the app updates both lists: `update:value` is for a move inside one. */
|
|
35
|
+
"move": (_move) => true,
|
|
36
|
+
/** A drag rests on a `SortableTarget` of the group: open what it stands for or go there. */
|
|
37
|
+
"hold": (_name) => true,
|
|
30
38
|
},
|
|
31
39
|
slots: Object,
|
|
32
40
|
setup(props, { attrs, emit, slots }) {
|
|
@@ -34,16 +42,24 @@ const SortableComponent = defineComponent({
|
|
|
34
42
|
const next = moveItem(props.value, from, to);
|
|
35
43
|
emit("update:value", next);
|
|
36
44
|
emit("change", next);
|
|
45
|
+
emit("move", { item: props.value[from], from: props.name, fromIndex: from, to: props.name, toIndex: to });
|
|
37
46
|
await nextTick();
|
|
38
47
|
};
|
|
48
|
+
const link = {
|
|
49
|
+
move: async (fromIndex, to, toIndex) => {
|
|
50
|
+
emit("move", { item: props.value[fromIndex], from: props.name, fromIndex, to, toIndex });
|
|
51
|
+
await nextTick();
|
|
52
|
+
},
|
|
53
|
+
hold: (name) => emit("hold", name),
|
|
54
|
+
};
|
|
39
55
|
const onPointerdown = (event) => {
|
|
40
56
|
if (!props.disabled) {
|
|
41
|
-
sortablePointerDown(event, event.currentTarget, props.layout, commit);
|
|
57
|
+
sortablePointerDown(event, event.currentTarget, props.layout, commit, link);
|
|
42
58
|
}
|
|
43
59
|
};
|
|
44
60
|
const onKeydown = (event) => {
|
|
45
61
|
if (!props.disabled)
|
|
46
|
-
void sortableKeyDown(event, event.currentTarget, commit);
|
|
62
|
+
void sortableKeyDown(event, event.currentTarget, commit, link);
|
|
47
63
|
};
|
|
48
64
|
return () => {
|
|
49
65
|
const { class: className, ...rest } = attrs;
|
|
@@ -55,6 +71,8 @@ const SortableComponent = defineComponent({
|
|
|
55
71
|
"data-layout": props.layout,
|
|
56
72
|
"data-disabled": props.disabled ? "" : undefined,
|
|
57
73
|
"data-handles": props.handles?.toString(),
|
|
74
|
+
"data-group": props.group,
|
|
75
|
+
"data-name": props.name,
|
|
58
76
|
onPointerdown,
|
|
59
77
|
onKeydown,
|
|
60
78
|
"onDragstart": preventNativeDrag,
|
|
@@ -83,8 +101,35 @@ const SortableHandleComponent = defineComponent({
|
|
|
83
101
|
};
|
|
84
102
|
},
|
|
85
103
|
});
|
|
104
|
+
const SortableTargetComponent = defineComponent({
|
|
105
|
+
name: "SortableTarget",
|
|
106
|
+
inheritAttrs: false,
|
|
107
|
+
props: {
|
|
108
|
+
/** The `group` of the lists whose items it takes */
|
|
109
|
+
group: { type: String, required: true },
|
|
110
|
+
/** Names the target in `move` and `hold` on the list */
|
|
111
|
+
name: { type: String, required: true },
|
|
112
|
+
/** Element to render. @default "div" */
|
|
113
|
+
as: { type: [String, Object], default: "div" },
|
|
114
|
+
},
|
|
115
|
+
slots: Object,
|
|
116
|
+
setup(props, { attrs, slots }) {
|
|
117
|
+
return () => {
|
|
118
|
+
const { class: className, ...rest } = attrs;
|
|
119
|
+
const isElement = typeof props.as === "string";
|
|
120
|
+
return h(props.as, {
|
|
121
|
+
...rest,
|
|
122
|
+
"class": clsx(s.Target, className),
|
|
123
|
+
"data-sortable-target": props.group,
|
|
124
|
+
"data-name": props.name,
|
|
125
|
+
}, isElement ? slots.default?.() : { default: () => slots.default?.() });
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
86
129
|
/** Reorders its direct children by dragging, from anywhere but a control, or by a `SortableHandle` where grips are shown. */
|
|
87
130
|
export const Sortable = SortableComponent;
|
|
88
131
|
/** The grip of an item. Arrow keys move the item while the handle has focus. */
|
|
89
132
|
export const SortableHandle = SortableHandleComponent;
|
|
133
|
+
/** A place outside the lists that takes an item whole: a folder, a category in a side list. */
|
|
134
|
+
export const SortableTarget = SortableTargetComponent;
|
|
90
135
|
//# sourceMappingURL=Sortable.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sortable.js","sourceRoot":"","sources":["../../../../src/components/Sortable/Sortable.ts"],"names":[],"mappings":"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","sourcesContent":["import clsx, { type ClassValue } from \"clsx\"\nimport {\n defineComponent,\n h,\n nextTick,\n type Component,\n type PropType,\n type SlotsType,\n type VNodeArrayChildren,\n type VNodeChild,\n} from \"vue\"\nimport { moveItem, sortableKeyDown, sortablePointerDown, type SortableLayout } from \"../../lib/sortable.js\"\nimport { GripVertical } from \"../Icon/index.js\"\nimport s from \"@plexui/styles/components/Sortable/Sortable.module.css\"\n\n// Vue port of packages/ui/src/components/Sortable/Sortable.tsx.\n//\n// - `value` binds with `v-model:value`; `change` fires with it.\n// - React's `Sortable.Handle` is the flat `SortableHandle` here.\n// - Attributes reach the rendered element, as React's rest props do.\n\nconst preventNativeDrag = (event: Event) => event.preventDefault()\n\nconst SortableComponent = defineComponent({\n name: \"Sortable\",\n inheritAttrs: false,\n props: {\n /** The list, in the order of the children. Bind with `v-model:value`. */\n value: { type: Array as PropType<readonly unknown[]>, required: true },\n /** `list` for rows one under another, `grid` for items that stand side by side. @default \"list\" */\n layout: { type: String as PropType<SortableLayout>, default: \"list\" },\n /** Element to render, such as `TableBody` for table rows. @default \"div\" */\n as: { type: [String, Object] as PropType<string | Component>, default: \"div\" },\n /** Turns dragging off. @default false */\n disabled: { type: Boolean, default: false },\n /** Shows the grips of `SortableHandle`. A hidden grip still takes the keyboard, and the item drags from anywhere but a control. @default true in a table, otherwise false */\n handles: { type: Boolean, default: undefined },\n },\n emits: {\n \"update:value\": (_value: unknown[]) => true,\n \"change\": (_value: unknown[]) => true,\n },\n slots: Object as SlotsType<{\n /** The items, one direct child per entry of `value`, keyed by the entry and not by its index */\n default?: () => VNodeChild\n }>,\n setup(props, { attrs, emit, slots }) {\n const commit = async (from: number, to: number) => {\n const next = moveItem(props.value, from, to)\n emit(\"update:value\", next)\n emit(\"change\", next)\n await nextTick()\n }\n\n const onPointerdown = (event: PointerEvent) => {\n if (!props.disabled) {\n sortablePointerDown(event, event.currentTarget as HTMLElement, props.layout, commit)\n }\n }\n\n const onKeydown = (event: KeyboardEvent) => {\n if (!props.disabled) void sortableKeyDown(event, event.currentTarget as HTMLElement, commit)\n }\n\n return () => {\n const { class: className, ...rest } = attrs\n const isElement = typeof props.as === \"string\"\n return h(\n props.as as string,\n {\n ...rest,\n \"class\": clsx(s.Sortable, className as ClassValue),\n \"data-sortable\": \"\",\n \"data-layout\": props.layout,\n \"data-disabled\": props.disabled ? \"\" : undefined,\n \"data-handles\": props.handles?.toString(),\n onPointerdown,\n onKeydown,\n \"onDragstart\": preventNativeDrag,\n },\n isElement ? (slots.default?.() as VNodeArrayChildren) : { default: () => slots.default?.() },\n )\n }\n },\n})\n\nconst SortableHandleComponent = defineComponent({\n name: \"SortableHandle\",\n inheritAttrs: false,\n props: {\n /** Name of the handle for assistive technology. @default \"Reorder\" */\n ariaLabel: { type: String, default: \"Reorder\" },\n },\n slots: Object as SlotsType<{\n /** The grip */\n default?: () => VNodeChild\n }>,\n setup(props, { attrs, slots }) {\n return () => {\n const { class: className, ...rest } = attrs\n return h(\n \"button\",\n {\n ...rest,\n \"type\": \"button\",\n \"class\": clsx(s.Handle, className as ClassValue),\n \"data-sortable-handle\": \"\",\n \"aria-label\": props.ariaLabel,\n },\n (slots.default ? slots.default() : [h(GripVertical, { \"aria-hidden\": true })]) as VNodeArrayChildren,\n )\n }\n },\n})\n\n/** Reorders its direct children by dragging, from anywhere but a control, or by a `SortableHandle` where grips are shown. */\nexport const Sortable = SortableComponent\n/** The grip of an item. Arrow keys move the item while the handle has focus. */\nexport const SortableHandle = SortableHandleComponent\nexport type { SortableLayout }\n"]}
|
|
1
|
+
{"version":3,"file":"Sortable.js","sourceRoot":"","sources":["../../../../src/components/Sortable/Sortable.ts"],"names":[],"mappings":"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","sourcesContent":["import clsx, { type ClassValue } from \"clsx\"\nimport {\n defineComponent,\n h,\n nextTick,\n type Component,\n type PropType,\n type SlotsType,\n type VNodeArrayChildren,\n type VNodeChild,\n} from \"vue\"\nimport { moveItem, sortableKeyDown, sortablePointerDown, type SortableLayout, type SortableMove } from \"../../lib/sortable.js\"\nimport { GripVertical } from \"../Icon/index.js\"\nimport s from \"@plexui/styles/components/Sortable/Sortable.module.css\"\n\n// Vue port of packages/ui/src/components/Sortable/Sortable.tsx.\n//\n// - `value` binds with `v-model:value`; `change` fires with it.\n// - React's `Sortable.Handle` and `Sortable.Target` are the flat `SortableHandle` and `SortableTarget` here.\n// - Attributes reach the rendered element, as React's rest props do.\n\nconst preventNativeDrag = (event: Event) => event.preventDefault()\n\nconst SortableComponent = defineComponent({\n name: \"Sortable\",\n inheritAttrs: false,\n props: {\n /** The list, in the order of the children. Bind with `v-model:value`. */\n value: { type: Array as PropType<readonly unknown[]>, required: true },\n /** `list` for rows one under another, `grid` for items that stand side by side. @default \"list\" */\n layout: { type: String as PropType<SortableLayout>, default: \"list\" },\n /** Element to render, such as `TableBody` for table rows. @default \"div\" */\n as: { type: [String, Object] as PropType<string | Component>, default: \"div\" },\n /** Turns dragging off. @default false */\n disabled: { type: Boolean, default: false },\n /** Shows the grips of `SortableHandle`. A hidden grip still takes the keyboard, and the item drags from anywhere but a control. @default true in a table, otherwise false */\n handles: { type: Boolean, default: undefined },\n /** Lists of one group trade items, and a `SortableTarget` of that group takes them. Give each list a `name`. */\n group: { type: String, default: undefined },\n /** Names the list in `move`. */\n name: { type: String, default: undefined },\n },\n emits: {\n \"update:value\": (_value: unknown[]) => true,\n \"change\": (_value: unknown[]) => true,\n /** An item moved, inside this list or out of it. Out of it the app updates both lists: `update:value` is for a move inside one. */\n \"move\": (_move: SortableMove) => true,\n /** A drag rests on a `SortableTarget` of the group: open what it stands for or go there. */\n \"hold\": (_name: string) => true,\n },\n slots: Object as SlotsType<{\n /** The items, one direct child per entry of `value`, keyed by the entry and not by its index */\n default?: () => VNodeChild\n }>,\n setup(props, { attrs, emit, slots }) {\n const commit = async (from: number, to: number) => {\n const next = moveItem(props.value, from, to)\n emit(\"update:value\", next)\n emit(\"change\", next)\n emit(\"move\", { item: props.value[from], from: props.name as string, fromIndex: from, to: props.name as string, toIndex: to })\n await nextTick()\n }\n\n const link = {\n move: async (fromIndex: number, to: string, toIndex?: number) => {\n emit(\"move\", { item: props.value[fromIndex], from: props.name as string, fromIndex, to, toIndex })\n await nextTick()\n },\n hold: (name: string) => emit(\"hold\", name),\n }\n\n const onPointerdown = (event: PointerEvent) => {\n if (!props.disabled) {\n sortablePointerDown(event, event.currentTarget as HTMLElement, props.layout, commit, link)\n }\n }\n\n const onKeydown = (event: KeyboardEvent) => {\n if (!props.disabled) void sortableKeyDown(event, event.currentTarget as HTMLElement, commit, link)\n }\n\n return () => {\n const { class: className, ...rest } = attrs\n const isElement = typeof props.as === \"string\"\n return h(\n props.as as string,\n {\n ...rest,\n \"class\": clsx(s.Sortable, className as ClassValue),\n \"data-sortable\": \"\",\n \"data-layout\": props.layout,\n \"data-disabled\": props.disabled ? \"\" : undefined,\n \"data-handles\": props.handles?.toString(),\n \"data-group\": props.group,\n \"data-name\": props.name,\n onPointerdown,\n onKeydown,\n \"onDragstart\": preventNativeDrag,\n },\n isElement ? (slots.default?.() as VNodeArrayChildren) : { default: () => slots.default?.() },\n )\n }\n },\n})\n\nconst SortableHandleComponent = defineComponent({\n name: \"SortableHandle\",\n inheritAttrs: false,\n props: {\n /** Name of the handle for assistive technology. @default \"Reorder\" */\n ariaLabel: { type: String, default: \"Reorder\" },\n },\n slots: Object as SlotsType<{\n /** The grip */\n default?: () => VNodeChild\n }>,\n setup(props, { attrs, slots }) {\n return () => {\n const { class: className, ...rest } = attrs\n return h(\n \"button\",\n {\n ...rest,\n \"type\": \"button\",\n \"class\": clsx(s.Handle, className as ClassValue),\n \"data-sortable-handle\": \"\",\n \"aria-label\": props.ariaLabel,\n },\n (slots.default ? slots.default() : [h(GripVertical, { \"aria-hidden\": true })]) as VNodeArrayChildren,\n )\n }\n },\n})\n\nconst SortableTargetComponent = defineComponent({\n name: \"SortableTarget\",\n inheritAttrs: false,\n props: {\n /** The `group` of the lists whose items it takes */\n group: { type: String, required: true },\n /** Names the target in `move` and `hold` on the list */\n name: { type: String, required: true },\n /** Element to render. @default \"div\" */\n as: { type: [String, Object] as PropType<string | Component>, default: \"div\" },\n },\n slots: Object as SlotsType<{\n /** What the target shows */\n default?: () => VNodeChild\n }>,\n setup(props, { attrs, slots }) {\n return () => {\n const { class: className, ...rest } = attrs\n const isElement = typeof props.as === \"string\"\n return h(\n props.as as string,\n {\n ...rest,\n \"class\": clsx(s.Target, className as ClassValue),\n \"data-sortable-target\": props.group,\n \"data-name\": props.name,\n },\n isElement ? (slots.default?.() as VNodeArrayChildren) : { default: () => slots.default?.() },\n )\n }\n },\n})\n\n/** Reorders its direct children by dragging, from anywhere but a control, or by a `SortableHandle` where grips are shown. */\nexport const Sortable = SortableComponent\n/** The grip of an item. Arrow keys move the item while the handle has focus. */\nexport const SortableHandle = SortableHandleComponent\n/** A place outside the lists that takes an item whole: a folder, a category in a side list. */\nexport const SortableTarget = SortableTargetComponent\nexport type { SortableLayout, SortableMove }\n"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Sortable, SortableHandle } from "./Sortable.js";
|
|
1
|
+
export { Sortable, SortableHandle, SortableTarget } from "./Sortable.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Sortable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Sortable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAA0C,MAAM,eAAe,CAAA","sourcesContent":["export { Sortable, SortableHandle, SortableTarget, type SortableLayout, type SortableMove } from \"./Sortable.js\"\n"]}
|
package/dist/es/lib/sortable.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import s from "@plexui/styles/components/Sortable/Sortable.module.css";
|
|
2
2
|
const START_DISTANCE = 4;
|
|
3
3
|
const TOUCH_HOLD = 200;
|
|
4
|
+
// Long enough to pass over a target on the way to another
|
|
5
|
+
const TARGET_HOLD = 500;
|
|
4
6
|
const SCROLL_EDGE = 80;
|
|
5
7
|
const SCROLL_SLOW = 4;
|
|
6
8
|
const SCROLL_FAST = 30;
|
|
@@ -10,6 +12,8 @@ const PART = 4;
|
|
|
10
12
|
// --cubic-enter: an animation here cannot read a CSS variable
|
|
11
13
|
const EASING = "cubic-bezier(.19,1,.22,1)";
|
|
12
14
|
const HANDLE = "[data-sortable-handle]";
|
|
15
|
+
const GRAB = "[data-sortable-grab]";
|
|
16
|
+
const STATIC = "data-sortable-static";
|
|
13
17
|
const INTERACTIVE = [
|
|
14
18
|
"a[href]",
|
|
15
19
|
"button",
|
|
@@ -29,7 +33,20 @@ const INTERACTIVE = [
|
|
|
29
33
|
].join(",");
|
|
30
34
|
// Read at the moment of use: the setting can change while the page is open
|
|
31
35
|
const reduced = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
32
|
-
|
|
36
|
+
/** How far the line stands in from the sides of the list */
|
|
37
|
+
const lineInset = (list) => {
|
|
38
|
+
const inset = parseFloat(getComputedStyle(list).getPropertyValue("--sortable-line-inset"));
|
|
39
|
+
return Number.isNaN(inset) ? LINE_INSET : inset;
|
|
40
|
+
};
|
|
41
|
+
/** A head row or an empty message stands among the items and is not one of them */
|
|
42
|
+
const itemsOf = (list) => [...list.children].filter((item) => !item.hasAttribute(STATIC));
|
|
43
|
+
/** The lists a drag from this one can reach, in the order of the page */
|
|
44
|
+
const listsOf = (list) => {
|
|
45
|
+
const { group } = list.dataset;
|
|
46
|
+
if (!group)
|
|
47
|
+
return [list];
|
|
48
|
+
return [...document.querySelectorAll(`[data-sortable][data-group="${CSS.escape(group)}"]:not([data-disabled])`)];
|
|
49
|
+
};
|
|
33
50
|
/** Grips stand in a table, elsewhere only when the list asks for them */
|
|
34
51
|
const showsHandles = (list) => (list.dataset.handles ?? String(list.tagName === "TBODY")) === "true";
|
|
35
52
|
/** The direct child of the list that holds `target` */
|
|
@@ -62,14 +79,21 @@ const nameOf = (item) => labelOf(item) || item.querySelector("img")?.alt || "";
|
|
|
62
79
|
function makeChip(item) {
|
|
63
80
|
const chip = document.createElement("div");
|
|
64
81
|
chip.className = s.Chip;
|
|
65
|
-
const image = item.querySelector("img");
|
|
66
|
-
if (image) {
|
|
82
|
+
const image = item.querySelector("[data-sortable-image], img");
|
|
83
|
+
if (image instanceof HTMLImageElement) {
|
|
67
84
|
const picture = document.createElement("img");
|
|
68
85
|
picture.className = s.ChipImage;
|
|
69
86
|
picture.src = image.currentSrc || image.src;
|
|
70
87
|
picture.alt = "";
|
|
71
88
|
chip.append(picture);
|
|
72
89
|
}
|
|
90
|
+
else if (image) {
|
|
91
|
+
// A cropped background: the chip takes the element as it stands, at the chip's size
|
|
92
|
+
const picture = image.cloneNode(false);
|
|
93
|
+
picture.className = s.ChipImage;
|
|
94
|
+
picture.hidden = false;
|
|
95
|
+
chip.append(picture);
|
|
96
|
+
}
|
|
73
97
|
const text = labelOf(item);
|
|
74
98
|
if (text) {
|
|
75
99
|
const label = document.createElement("span");
|
|
@@ -91,8 +115,8 @@ function announce(text) {
|
|
|
91
115
|
}
|
|
92
116
|
region.textContent = text;
|
|
93
117
|
}
|
|
94
|
-
const scrollParent = (
|
|
95
|
-
for (let node =
|
|
118
|
+
const scrollParent = (start) => {
|
|
119
|
+
for (let node = start; node && node !== document.body; node = node.parentElement) {
|
|
96
120
|
if (/auto|scroll/.test(getComputedStyle(node).overflowY) && node.scrollHeight > node.clientHeight)
|
|
97
121
|
return node;
|
|
98
122
|
}
|
|
@@ -102,6 +126,12 @@ const scrollParent = (list) => {
|
|
|
102
126
|
const scrollStep = (depth) => SCROLL_SLOW + (SCROLL_FAST - SCROLL_SLOW) * Math.min(depth / SCROLL_EDGE, 1);
|
|
103
127
|
function listSlots(list, items) {
|
|
104
128
|
const box = list.getBoundingClientRect();
|
|
129
|
+
const inset = lineInset(list);
|
|
130
|
+
if (!items.length) {
|
|
131
|
+
// Above a message or an empty row: the line must not cross its text
|
|
132
|
+
const rest = (list.lastElementChild ?? list).getBoundingClientRect();
|
|
133
|
+
return [{ gap: 0, x: inset, y: rest.top - box.top, length: box.width - inset * 2 }];
|
|
134
|
+
}
|
|
105
135
|
const rects = items.map((item) => item.getBoundingClientRect());
|
|
106
136
|
return Array.from({ length: items.length + 1 }, (_, gap) => {
|
|
107
137
|
const above = rects[gap - 1];
|
|
@@ -109,9 +139,9 @@ function listSlots(list, items) {
|
|
|
109
139
|
const y = above && below ? (above.bottom + below.top) / 2 : (below?.top ?? above.bottom);
|
|
110
140
|
return {
|
|
111
141
|
gap,
|
|
112
|
-
x:
|
|
142
|
+
x: inset,
|
|
113
143
|
y: y - box.top,
|
|
114
|
-
length: box.width -
|
|
144
|
+
length: box.width - inset * 2,
|
|
115
145
|
before: items[gap - 1],
|
|
116
146
|
after: items[gap],
|
|
117
147
|
};
|
|
@@ -120,6 +150,8 @@ function listSlots(list, items) {
|
|
|
120
150
|
// One slot per gap, and two where a line wraps: the end of one line and the start of the next
|
|
121
151
|
function gridSlots(list, items) {
|
|
122
152
|
const box = list.getBoundingClientRect();
|
|
153
|
+
if (!items.length)
|
|
154
|
+
return [{ gap: 0, x: 0, y: 0, length: box.height }];
|
|
123
155
|
const half = (parseFloat(getComputedStyle(list).columnGap) || 8) / 2;
|
|
124
156
|
const rects = items.map((item) => item.getBoundingClientRect());
|
|
125
157
|
const slots = [];
|
|
@@ -153,9 +185,9 @@ function flyChip(chip, to, layout) {
|
|
|
153
185
|
], { duration: 200, easing: EASING, fill: "forwards" }).onfinish = () => chip.remove();
|
|
154
186
|
}
|
|
155
187
|
/** The items that changed place slide there from where they stood */
|
|
156
|
-
async function
|
|
188
|
+
async function flip(items, work, skip) {
|
|
157
189
|
const before = new Map(items.map((item) => [item, item.getBoundingClientRect()]));
|
|
158
|
-
await
|
|
190
|
+
await work();
|
|
159
191
|
if (reduced())
|
|
160
192
|
return;
|
|
161
193
|
items
|
|
@@ -173,7 +205,7 @@ const swallow = (event) => {
|
|
|
173
205
|
event.stopPropagation();
|
|
174
206
|
event.preventDefault();
|
|
175
207
|
};
|
|
176
|
-
function startDrag(event, list, source, layout, immediate, commit) {
|
|
208
|
+
function startDrag(event, list, source, layout, immediate, commit, link) {
|
|
177
209
|
const touch = event.pointerType === "touch" && !immediate;
|
|
178
210
|
const origin = { x: event.clientX, y: event.clientY };
|
|
179
211
|
const pointer = { ...origin };
|
|
@@ -181,6 +213,8 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
181
213
|
const style = getComputedStyle(list);
|
|
182
214
|
const part = parseFloat(layout === "grid" ? style.columnGap : style.rowGap) ? PART : 0;
|
|
183
215
|
const scroller = scrollParent(list);
|
|
216
|
+
const { group } = list.dataset;
|
|
217
|
+
const targets = group ? `[data-sortable-target="${CSS.escape(group)}"]` : undefined;
|
|
184
218
|
let drag;
|
|
185
219
|
window.addEventListener("pointermove", onMove);
|
|
186
220
|
window.addEventListener("pointerup", onUp);
|
|
@@ -219,12 +253,17 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
219
253
|
window.removeEventListener("keydown", onKey, true);
|
|
220
254
|
window.removeEventListener("touchmove", blockScroll);
|
|
221
255
|
}
|
|
256
|
+
function measure() {
|
|
257
|
+
return listsOf(list).map((entry) => {
|
|
258
|
+
const items = itemsOf(entry);
|
|
259
|
+
entry.setAttribute("data-dragging", "");
|
|
260
|
+
return { list: entry, items, slots: layout === "grid" ? gridSlots(entry, items) : listSlots(entry, items) };
|
|
261
|
+
});
|
|
262
|
+
}
|
|
222
263
|
function begin() {
|
|
223
|
-
const
|
|
224
|
-
const slots = layout === "grid" ? gridSlots(list, items) : listSlots(list, items);
|
|
264
|
+
const areas = measure();
|
|
225
265
|
window.getSelection()?.removeAllRanges();
|
|
226
266
|
document.documentElement.classList.add(s.Dragging);
|
|
227
|
-
list.setAttribute("data-dragging", "");
|
|
228
267
|
source.setAttribute("data-sortable-source", "");
|
|
229
268
|
window.addEventListener("keydown", onKey, true);
|
|
230
269
|
window.addEventListener("touchmove", blockScroll, { passive: false });
|
|
@@ -234,25 +273,32 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
234
273
|
line.hidden = true;
|
|
235
274
|
document.body.append(line);
|
|
236
275
|
const { width, height } = chip.getBoundingClientRect();
|
|
237
|
-
drag = { chip, line, width, height,
|
|
276
|
+
drag = { chip, line, width, height, areas };
|
|
238
277
|
tick();
|
|
239
278
|
}
|
|
240
279
|
function tick() {
|
|
241
280
|
if (!drag)
|
|
242
281
|
return;
|
|
282
|
+
drag.under = targets ? document.elementFromPoint(pointer.x, pointer.y) : undefined;
|
|
243
283
|
autoScroll();
|
|
244
284
|
update();
|
|
245
285
|
drag.frame = requestAnimationFrame(tick);
|
|
246
286
|
}
|
|
287
|
+
/** A column of targets with a scroll of its own scrolls alone, the page stays */
|
|
288
|
+
function panelUnder() {
|
|
289
|
+
const panel = targets && drag?.under ? scrollParent(drag.under) : undefined;
|
|
290
|
+
return panel && panel !== scroller && panel.querySelector(targets) ? panel : undefined;
|
|
291
|
+
}
|
|
247
292
|
function autoScroll() {
|
|
248
|
-
const
|
|
293
|
+
const area = panelUnder() ?? scroller;
|
|
294
|
+
const box = area?.getBoundingClientRect() ?? { top: 0, bottom: window.innerHeight };
|
|
249
295
|
const above = box.top + SCROLL_EDGE - pointer.y;
|
|
250
296
|
const below = pointer.y - box.bottom + SCROLL_EDGE;
|
|
251
297
|
const step = above > 0 ? -scrollStep(above) : below > 0 ? scrollStep(below) : 0;
|
|
252
298
|
if (!step)
|
|
253
299
|
return;
|
|
254
|
-
if (
|
|
255
|
-
|
|
300
|
+
if (area)
|
|
301
|
+
area.scrollTop += step;
|
|
256
302
|
else
|
|
257
303
|
window.scrollBy(0, step);
|
|
258
304
|
}
|
|
@@ -260,93 +306,145 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
260
306
|
if (!drag)
|
|
261
307
|
return;
|
|
262
308
|
drag.chip.style.transform = `translate(${pointer.x - drag.width / 2}px, ${pointer.y - drag.height / 2}px)`;
|
|
309
|
+
const target = (targets && drag.under?.closest(targets)) || undefined;
|
|
310
|
+
if (target !== drag.target)
|
|
311
|
+
pointAt(target);
|
|
263
312
|
const box = source.getBoundingClientRect();
|
|
264
313
|
const overSource = pointer.x >= box.left && pointer.x <= box.right && pointer.y >= box.top && pointer.y <= box.bottom;
|
|
265
314
|
source.toggleAttribute("data-sortable-away", !overSource);
|
|
266
|
-
showGap(overSource ? undefined : nearestSlot());
|
|
315
|
+
showGap(target || overSource ? undefined : nearestSlot());
|
|
316
|
+
}
|
|
317
|
+
function pointAt(target) {
|
|
318
|
+
if (!drag)
|
|
319
|
+
return;
|
|
320
|
+
clearTimeout(drag.rest);
|
|
321
|
+
drag.target?.removeAttribute("data-over");
|
|
322
|
+
drag.target = target;
|
|
323
|
+
if (!target)
|
|
324
|
+
return;
|
|
325
|
+
target.setAttribute("data-over", "");
|
|
326
|
+
drag.rest = setTimeout(() => {
|
|
327
|
+
link?.hold?.(target.dataset.name);
|
|
328
|
+
// What the hold changed is drawn two frames on: a list opened, the page jumped
|
|
329
|
+
requestAnimationFrame(() => requestAnimationFrame(() => drag && (drag.areas = measure())));
|
|
330
|
+
}, TARGET_HOLD);
|
|
331
|
+
}
|
|
332
|
+
function distance(slot, box) {
|
|
333
|
+
if (layout === "list")
|
|
334
|
+
return Math.abs(pointer.y - (box.top + slot.y));
|
|
335
|
+
const top = box.top + slot.y;
|
|
336
|
+
return Math.hypot(pointer.x - (box.left + slot.x), Math.max(top - pointer.y, 0, pointer.y - top - slot.length));
|
|
267
337
|
}
|
|
268
338
|
function nearestSlot() {
|
|
269
339
|
if (!drag)
|
|
270
340
|
return undefined;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
pointer.
|
|
276
|
-
|
|
341
|
+
let best;
|
|
342
|
+
let closest = Infinity;
|
|
343
|
+
for (const area of drag.areas) {
|
|
344
|
+
const box = area.list.getBoundingClientRect();
|
|
345
|
+
const near = pointer.x >= box.left - NEAR_LIST &&
|
|
346
|
+
pointer.x <= box.right + NEAR_LIST &&
|
|
347
|
+
pointer.y >= box.top - NEAR_LIST &&
|
|
348
|
+
pointer.y <= box.bottom + NEAR_LIST;
|
|
349
|
+
if (!near)
|
|
350
|
+
continue;
|
|
351
|
+
for (const slot of area.slots) {
|
|
352
|
+
const away = distance(slot, box);
|
|
353
|
+
if (away < closest) {
|
|
354
|
+
closest = away;
|
|
355
|
+
best = { area, slot };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!best)
|
|
277
360
|
return undefined;
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
return Math.abs(pointer.y - (box.top + slot.y));
|
|
281
|
-
const top = box.top + slot.y;
|
|
282
|
-
return Math.hypot(pointer.x - (box.left + slot.x), Math.max(top - pointer.y, 0, pointer.y - top - slot.length));
|
|
283
|
-
};
|
|
284
|
-
const best = drag.slots.reduce((nearest, slot) => (distance(slot) < distance(nearest) ? slot : nearest));
|
|
285
|
-
const index = drag.items.indexOf(source);
|
|
286
|
-
return best.gap === index || best.gap === index + 1 ? undefined : best;
|
|
361
|
+
const index = best.area.items.indexOf(source);
|
|
362
|
+
return index >= 0 && (best.slot.gap === index || best.slot.gap === index + 1) ? undefined : best;
|
|
287
363
|
}
|
|
288
|
-
function shift(slot,
|
|
289
|
-
const move = (item,
|
|
364
|
+
function shift(slot, by) {
|
|
365
|
+
const move = (item, offset) => {
|
|
290
366
|
if (!item || !part)
|
|
291
367
|
return;
|
|
292
|
-
item.style.transform =
|
|
368
|
+
item.style.transform = by
|
|
293
369
|
? layout === "grid"
|
|
294
|
-
? `translateX(${
|
|
295
|
-
: `translateY(${
|
|
370
|
+
? `translateX(${offset}px)`
|
|
371
|
+
: `translateY(${offset}px)`
|
|
296
372
|
: "";
|
|
297
373
|
};
|
|
298
|
-
move(slot?.before, -
|
|
299
|
-
move(slot?.after,
|
|
374
|
+
move(slot?.before, -by);
|
|
375
|
+
move(slot?.after, by);
|
|
300
376
|
}
|
|
301
377
|
function showGap(found) {
|
|
302
378
|
if (!drag)
|
|
303
379
|
return;
|
|
304
|
-
if (found !== drag.found) {
|
|
305
|
-
shift(drag.found, 0);
|
|
306
|
-
shift(found, part);
|
|
380
|
+
if (found?.slot !== drag.found?.slot) {
|
|
381
|
+
shift(drag.found?.slot, 0);
|
|
382
|
+
shift(found?.slot, part);
|
|
307
383
|
}
|
|
308
384
|
drag.found = found;
|
|
309
385
|
drag.line.hidden = !found;
|
|
310
386
|
if (!found)
|
|
311
387
|
return;
|
|
312
|
-
const box = list.getBoundingClientRect();
|
|
388
|
+
const box = found.area.list.getBoundingClientRect();
|
|
313
389
|
if (layout === "grid") {
|
|
314
|
-
drag.line.style.transform = `translate(${box.left + found.x - 1}px, ${box.top + found.y}px)`;
|
|
315
|
-
drag.line.style.height = `${found.length}px`;
|
|
390
|
+
drag.line.style.transform = `translate(${box.left + found.slot.x - 1}px, ${box.top + found.slot.y}px)`;
|
|
391
|
+
drag.line.style.height = `${found.slot.length}px`;
|
|
316
392
|
}
|
|
317
393
|
else {
|
|
318
|
-
drag.line.style.transform = `translate(${box.left + found.x}px, ${box.top + found.y - 1}px)`;
|
|
319
|
-
drag.line.style.width = `${found.length}px`;
|
|
394
|
+
drag.line.style.transform = `translate(${box.left + found.slot.x}px, ${box.top + found.slot.y - 1}px)`;
|
|
395
|
+
drag.line.style.width = `${found.slot.length}px`;
|
|
320
396
|
}
|
|
321
397
|
}
|
|
322
398
|
function restoreSource() {
|
|
323
399
|
source.removeAttribute("data-sortable-source");
|
|
324
400
|
source.removeAttribute("data-sortable-away");
|
|
325
401
|
}
|
|
326
|
-
function settle(chip) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
402
|
+
function settle(chip, item = source) {
|
|
403
|
+
const shown = item?.isConnected ? item : undefined;
|
|
404
|
+
flyChip(chip, shown?.getBoundingClientRect(), layout);
|
|
405
|
+
if (!reduced() && shown)
|
|
406
|
+
shown.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 250, easing: EASING });
|
|
330
407
|
}
|
|
331
|
-
async function land(chip,
|
|
332
|
-
const from =
|
|
333
|
-
const to = gap > from ? gap - 1 : gap;
|
|
408
|
+
async function land(chip, areas, { area, slot: { gap } }, clear) {
|
|
409
|
+
const from = itemsOf(list).indexOf(source);
|
|
334
410
|
restoreSource();
|
|
335
|
-
|
|
411
|
+
if (area.list === list) {
|
|
412
|
+
const to = gap > from ? gap - 1 : gap;
|
|
413
|
+
await flip(area.items, () => {
|
|
414
|
+
clear();
|
|
415
|
+
return commit(from, to);
|
|
416
|
+
}, source);
|
|
417
|
+
announce(`${nameOf(source)}, ${to + 1} / ${area.items.length}`);
|
|
418
|
+
settle(chip);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const name = nameOf(source);
|
|
422
|
+
await flip(areas.flatMap(({ items }) => items), () => {
|
|
336
423
|
clear();
|
|
337
|
-
return
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
settle(chip);
|
|
424
|
+
return link?.move(from, area.list.dataset.name, gap);
|
|
425
|
+
}, source);
|
|
426
|
+
announce(`${name}, ${gap + 1} / ${area.items.length + 1}`);
|
|
427
|
+
// A framework builds the row anew under its new parent
|
|
428
|
+
settle(chip, source.isConnected ? source : itemsOf(area.list)[gap]);
|
|
429
|
+
}
|
|
430
|
+
async function dropOn(chip, areas, name, clear) {
|
|
431
|
+
const from = itemsOf(list).indexOf(source);
|
|
432
|
+
restoreSource();
|
|
433
|
+
await flip(areas.flatMap(({ items }) => items), () => {
|
|
434
|
+
clear();
|
|
435
|
+
return link?.move(from, name);
|
|
436
|
+
}, source);
|
|
437
|
+
flyChip(chip, undefined, layout);
|
|
342
438
|
}
|
|
343
439
|
function finish(drop) {
|
|
344
440
|
stopListening();
|
|
345
441
|
if (!drag)
|
|
346
442
|
return;
|
|
347
|
-
const { chip, line, found,
|
|
443
|
+
const { chip, line, found, areas, frame, target, rest } = drag;
|
|
348
444
|
if (frame)
|
|
349
445
|
cancelAnimationFrame(frame);
|
|
446
|
+
clearTimeout(rest);
|
|
447
|
+
target?.removeAttribute("data-over");
|
|
350
448
|
// The pointer lifts over a row: that click must not act on it
|
|
351
449
|
window.addEventListener("click", swallow, { capture: true, once: true });
|
|
352
450
|
setTimeout(() => window.removeEventListener("click", swallow, { capture: true }));
|
|
@@ -354,12 +452,17 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
354
452
|
drag = undefined;
|
|
355
453
|
// On a drop this waits for the flip to read where the parted items stand
|
|
356
454
|
const clear = () => {
|
|
357
|
-
list.removeAttribute("data-dragging");
|
|
455
|
+
areas.forEach((area) => area.list.removeAttribute("data-dragging"));
|
|
358
456
|
document.documentElement.classList.remove(s.Dragging);
|
|
359
|
-
shift(found, 0);
|
|
457
|
+
shift(found?.slot, 0);
|
|
360
458
|
};
|
|
361
|
-
|
|
362
|
-
|
|
459
|
+
const targetName = target?.dataset.name;
|
|
460
|
+
if (drop && targetName && targetName !== list.dataset.name) {
|
|
461
|
+
void dropOn(chip, areas, targetName, clear);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (drop && found && !target) {
|
|
465
|
+
void land(chip, areas, found, clear);
|
|
363
466
|
return;
|
|
364
467
|
}
|
|
365
468
|
clear();
|
|
@@ -368,22 +471,24 @@ function startDrag(event, list, source, layout, immediate, commit) {
|
|
|
368
471
|
}
|
|
369
472
|
}
|
|
370
473
|
/** Starts a drag from a press inside the list. Goes on the list's `pointerdown`. */
|
|
371
|
-
export function sortablePointerDown(event, list, layout, commit) {
|
|
474
|
+
export function sortablePointerDown(event, list, layout, commit, link) {
|
|
372
475
|
const target = event.target;
|
|
373
476
|
if (event.button || target.closest("[data-sortable]") !== list)
|
|
374
477
|
return;
|
|
375
478
|
const source = itemOf(list, target);
|
|
376
|
-
if (!source)
|
|
479
|
+
if (!source || source.hasAttribute(STATIC))
|
|
377
480
|
return;
|
|
378
481
|
const handle = target.closest(HANDLE);
|
|
379
|
-
|
|
380
|
-
|
|
482
|
+
const control = target.closest(INTERACTIVE);
|
|
483
|
+
// A grip in sight is the only way to drag, otherwise anything but a control
|
|
484
|
+
// starts it, and a control that opens its row on a click may let a pull through
|
|
485
|
+
if (source.querySelector(HANDLE) && showsHandles(list) ? !handle : control && !control.closest(GRAB))
|
|
381
486
|
return;
|
|
382
|
-
startDrag(event, list, source, layout, !!handle, commit);
|
|
487
|
+
startDrag(event, list, source, layout, !!handle, commit, link);
|
|
383
488
|
}
|
|
384
489
|
const KEY_STEPS = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 };
|
|
385
490
|
/** Moves the focused handle's item with the arrow keys. Goes on the list's `keydown`. */
|
|
386
|
-
export async function sortableKeyDown(event, list, commit) {
|
|
491
|
+
export async function sortableKeyDown(event, list, commit, link) {
|
|
387
492
|
const step = KEY_STEPS[event.key];
|
|
388
493
|
const target = event.target;
|
|
389
494
|
if (!step || target.closest("[data-sortable]") !== list)
|
|
@@ -394,10 +499,23 @@ export async function sortableKeyDown(event, list, commit) {
|
|
|
394
499
|
const items = itemsOf(list);
|
|
395
500
|
const from = items.indexOf(source);
|
|
396
501
|
const to = from + step;
|
|
397
|
-
if (to < 0 || to >= items.length)
|
|
502
|
+
if (to < 0 || to >= items.length) {
|
|
503
|
+
// Past the end of the list: on to the next list of the group
|
|
504
|
+
const lists = listsOf(list);
|
|
505
|
+
const next = lists[lists.indexOf(list) + step];
|
|
506
|
+
if (!next || !link)
|
|
507
|
+
return;
|
|
508
|
+
event.preventDefault();
|
|
509
|
+
const place = step > 0 ? 0 : itemsOf(next).length;
|
|
510
|
+
const name = nameOf(source);
|
|
511
|
+
await flip(lists.flatMap(itemsOf), () => link.move(from, next.dataset.name, place));
|
|
512
|
+
const landed = itemsOf(next)[place];
|
|
513
|
+
(landed?.querySelector(HANDLE) ?? landed)?.focus();
|
|
514
|
+
announce(`${name}, ${place + 1} / ${itemsOf(next).length}`);
|
|
398
515
|
return;
|
|
516
|
+
}
|
|
399
517
|
event.preventDefault();
|
|
400
|
-
await
|
|
518
|
+
await flip(items, () => commit(from, to));
|
|
401
519
|
// Moving an element in the document drops its focus
|
|
402
520
|
if (target.isConnected && document.activeElement !== target)
|
|
403
521
|
target.focus();
|