@plexui/vue 0.1.12 → 0.3.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/RadioGroup/RadioGroup.js +7 -3
- package/dist/es/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/es/components/Sortable/Sortable.js +135 -0
- package/dist/es/components/Sortable/Sortable.js.map +1 -0
- package/dist/es/components/Sortable/index.js +2 -0
- package/dist/es/components/Sortable/index.js.map +1 -0
- package/dist/es/components/TagInput/TagInput.js +3 -0
- package/dist/es/components/TagInput/TagInput.js.map +1 -1
- package/dist/es/components/Textarea/Textarea.js.map +1 -1
- package/dist/es/lib/sortable.js +524 -0
- package/dist/es/lib/sortable.js.map +1 -0
- package/dist/types/components/Sortable/Sortable.d.ts +163 -0
- package/dist/types/components/Sortable/index.d.ts +1 -0
- package/dist/types/components/TagInput/TagInput.d.ts +12 -1
- package/dist/types/components/Textarea/Textarea.d.ts +3 -3
- package/dist/types/lib/sortable.d.ts +27 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../../../src/components/Textarea/Textarea.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computed,\n defineComponent,\n h,\n nextTick,\n onMounted,\n ref,\n useId,\n watch,\n withDirectives,\n type PropType,\n type TextareaHTMLAttributes,\n} from \"vue\"\nimport type { ControlSize, Sizes, Variants } from \"@plexui/styles/types\"\nimport { vServerText } from \"../../lib/defaultValue.js\"\nimport { toCssVariables } from \"../../lib/helpers.js\"\nimport s from \"@plexui/styles/components/Textarea/Textarea.module.css\"\n\n// Vue port of packages/ui/src/components/Textarea/Textarea.tsx. Props,\n// defaults and rendered DOM match React 1:1 (checked by the parity tests).\n//\n// - `class` goes to the container; every other attribute and listener\n// (`style`, `placeholder`, `@input`, `@change`, ...) goes to the <textarea>,\n// like React's `className` and `...restProps`.\n// - Value: `value` + `update:value` mirror React's `value` prop\n// (`v-model:value`) and fire on every input. Uncontrolled use takes\n// `default-value`. Like a React controlled textarea, a `value` the parent\n// does not update after input is put back.\n// - As in React, the textarea's text content (its `defaultValue`) follows\n// the controlled value, or `default-value` when uncontrolled. It is set\n// through the `textContent` property, which Vue's server renderer prints\n// as the element's content, so `vServerText` can adjust it at SSR.\n\ntype ListenerValue = ((event: Event) => void) | Array<(event: Event) => void> | undefined\n\nconst invoke = (handler: unknown, event: Event) => {\n for (const fn of ([] as unknown[]).concat(handler ?? [])) {\n if (typeof fn === \"function\") fn(event)\n }\n}\n\nconst TextareaComponent = defineComponent({\n name: \"Textarea\",\n inheritAttrs: false,\n props: {\n /** Visual style of the textarea. @default \"outline\" */\n variant: { type: String as PropType<Variants<\"outline\" | \"soft\">>, default: \"outline\" },\n /** Controls the size of the textarea. @default \"md\" */\n size: { type: String as PropType<ControlSize>, default: \"md\" },\n /** Applies a negative margin using the current gutter to optically align the text with surrounding content. */\n opticallyAlign: String as PropType<\"start\" | \"end\">,\n /** Controls gutter on the edges of the textarea, defaults to value from `size`. */\n gutterSize: String as PropType<Sizes<\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\">>,\n /** Disables the textarea visually and from interactions. @default false */\n disabled: { type: Boolean, default: false },\n /** Makes the textarea read-only. @default false */\n readOnly: { type: Boolean, default: false },\n /** Native spelling of `readOnly` (`<Textarea readonly />`). @default false */\n readonly: { type: Boolean, default: false },\n /** Mark the textarea as invalid. @default false */\n invalid: { type: Boolean, default: false },\n /** Allow autofill extensions to appear in the textarea. @default `true` when `name` is set, else `false` */\n allowAutofillExtensions: { type: Boolean, default: undefined },\n /** Select all contents of the textarea when mounted (and whenever it turns `true`). @default false */\n autoSelect: { type: Boolean, default: undefined },\n /** Default number of rows to display. @default 3 */\n rows: { type: Number, default: 3 },\n /** Automatically adjust the height of the textarea based on its contents. @default false */\n autoResize: { type: Boolean, default: undefined },\n /** Maximum number of rows that can be displayed when autoResize is enabled. @default Math.max(rows, 10) */\n maxRows: Number,\n id: String,\n name: String,\n /** Controlled value (`v-model:value`) */\n value: { type: [String, Number] as PropType<string | number>, default: undefined },\n /** Initial value when uncontrolled */\n defaultValue: { type: [String, Number] as PropType<string | number>, default: undefined },\n /** The textarea's `autocomplete` attribute */\n autoComplete: String,\n },\n emits: {\n \"update:value\": (_value: string) => true,\n /** Fired when the browser autofills the textarea */\n \"autofill\": () => true,\n },\n setup(props, { attrs, emit, expose }) {\n const textAreaRef = ref<HTMLTextAreaElement | null>(null)\n // Prevent 1Password from appearing: 1Password won't display if it thinks this field is for searching\n // Allow user to override this for connecting to labels.\n const onePasswordPreventionId = `search-ui-input-${useId()}`\n const focused = ref(false)\n\n const computedMaxRows = computed(() =>\n props.autoResize ? Math.max(props.maxRows ?? 10, props.rows) : props.rows,\n )\n const controlledValue = () => props.value\n\n const runAutoSelect = () => {\n if (props.autoSelect) {\n textAreaRef.value?.select()\n }\n }\n\n const autosizeTextarea = () => {\n const textarea = textAreaRef.value\n if (!props.autoResize || !textarea || computedMaxRows.value === undefined) return\n textarea.style.height = \"0px\"\n const scrollHeight = textarea.scrollHeight\n textarea.style.height = scrollHeight + \"px\"\n }\n\n // The textarea's value is set imperatively, like React DOM does: a `value`\n // prop would make Vue mirror it into a `value` attribute, which React\n // never renders. Puts the controlled value back when it differs.\n const syncValue = () => {\n const textarea = textAreaRef.value\n const value = controlledValue()\n if (!textarea || value == null) return\n const nextValue = String(value)\n if (textarea.value !== nextValue) textarea.value = nextValue\n }\n\n // React runs these effects after mount and when their dependencies change\n onMounted(() => {\n const textarea = textAreaRef.value\n // React's initTextarea: the initial text becomes the (dirty) value\n const initialValue = textarea?.textContent ?? \"\"\n if (textarea && initialValue !== \"\") textarea.value = initialValue\n runAutoSelect()\n autosizeTextarea()\n })\n // React DOM writes a new value while committing, before any effect runs,\n // so the selection covers the value that arrives with `autoSelect`\n watch(\n () => props.autoSelect,\n () => {\n syncValue()\n runAutoSelect()\n },\n { flush: \"post\" },\n )\n // Resize if value changes outside of user input - e.g. form reset\n watch(\n () => [controlledValue(), props.rows, props.autoResize, computedMaxRows.value],\n () => {\n syncValue()\n autosizeTextarea()\n },\n { flush: \"post\" },\n )\n\n const handleInput = (evt: Event) => {\n const next = (evt.target as HTMLTextAreaElement).value\n emit(\"update:value\", next)\n autosizeTextarea()\n // A controlled value the parent did not update wins over the typed text,\n // as in React\n void nextTick(syncValue)\n }\n\n expose({\n /** The rendered <textarea> element */\n textarea: textAreaRef,\n focus: () => textAreaRef.value?.focus(),\n })\n\n return () => {\n const {\n class: className,\n onInput,\n onFocus,\n onBlur,\n onAnimationstart,\n ...restAttrs\n } = attrs as Record<string, unknown>\n\n const { variant, size, gutterSize, disabled, invalid, rows } = props\n const readOnly = props.readOnly || props.readonly\n // Default to `true` when presence of `name`\n const allowAutofillExtensions = props.allowAutofillExtensions ?? !!props.name\n const value = controlledValue()\n // React keeps the textarea's defaultValue (its text content) in sync with\n // the controlled value, else with `defaultValue`\n const textContent = String(value ?? props.defaultValue ?? \"\")\n\n return h(\n \"div\",\n {\n \"class\": [s.Container, className],\n \"data-variant\": variant,\n \"data-size\": size,\n \"data-gutter-size\": gutterSize,\n \"data-optically-align\": props.opticallyAlign,\n \"data-focused\": String(focused.value),\n \"data-disabled\": disabled ? \"\" : undefined,\n \"data-readonly\": readOnly ? \"\" : undefined,\n \"data-invalid\": invalid ? \"\" : undefined,\n \"style\": toCssVariables({\n \"textarea-min-rows\": `${rows}`,\n \"textarea-max-rows\": `${computedMaxRows.value}`,\n }),\n },\n [\n withDirectives(\n h(\"textarea\", {\n ...restAttrs,\n \"onInput\": (evt: Event) => {\n invoke(onInput as ListenerValue, evt)\n handleInput(evt)\n },\n \"ref\": textAreaRef,\n \"id\": props.id || (allowAutofillExtensions ? undefined : onePasswordPreventionId),\n \"class\": s.Textarea,\n \"name\": props.name,\n \"autocomplete\": props.autoComplete ?? restAttrs.autocomplete,\n // Expose the visual invalid state to assistive tech; an explicit aria-invalid wins\n \"aria-invalid\": restAttrs[\"aria-invalid\"] ?? (invalid || undefined),\n \"readonly\": readOnly,\n \"disabled\": disabled,\n \"rows\": rows,\n \"onFocus\": (evt: Event) => {\n focused.value = true\n invoke(onFocus as ListenerValue, evt)\n },\n \"onBlur\": (evt: Event) => {\n focused.value = false\n invoke(onBlur as ListenerValue, evt)\n },\n \"onAnimationstart\": (evt: AnimationEvent) => {\n invoke(onAnimationstart as ListenerValue, evt)\n if (evt.animationName === \"native-autofill-in\") {\n emit(\"autofill\")\n }\n },\n // Prevent LastPass and 1Password from appearing\n \"data-lpignore\": allowAutofillExtensions ? undefined : true,\n \"data-1p-ignore\": allowAutofillExtensions ? undefined : true,\n \"textContent\": textContent,\n }),\n [[vServerText, textContent]],\n ),\n ],\n )\n }\n },\n})\n\ntype TextareaOwnProps = InstanceType<typeof TextareaComponent>[\"$props\"]\n\n/** Textarea props plus the native <textarea> attributes that fall through to it, as in React. */\nexport type TextareaProps = TextareaOwnProps & Omit<TextareaHTMLAttributes, keyof TextareaOwnProps | \"readonly\">\n\nexport const Textarea = TextareaComponent as typeof TextareaComponent & { new (): { $props: TextareaProps } }\n"]}
|
|
1
|
+
{"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../../../src/components/Textarea/Textarea.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computed,\n defineComponent,\n h,\n nextTick,\n onMounted,\n ref,\n useId,\n watch,\n withDirectives,\n type PropType,\n type TextareaHTMLAttributes,\n} from \"vue\"\nimport type { ControlSize, Sizes, Variants } from \"@plexui/styles/types\"\nimport { vServerText } from \"../../lib/defaultValue.js\"\nimport { toCssVariables } from \"../../lib/helpers.js\"\nimport s from \"@plexui/styles/components/Textarea/Textarea.module.css\"\n\n// Vue port of packages/ui/src/components/Textarea/Textarea.tsx. Props,\n// defaults and rendered DOM match React 1:1 (checked by the parity tests).\n//\n// - `class` goes to the container; every other attribute and listener\n// (`style`, `placeholder`, `@input`, `@change`, ...) goes to the <textarea>,\n// like React's `className` and `...restProps`.\n// - Value: `value` + `update:value` mirror React's `value` prop\n// (`v-model:value`) and fire on every input. Uncontrolled use takes\n// `default-value`. Like a React controlled textarea, a `value` the parent\n// does not update after input is put back.\n// - As in React, the textarea's text content (its `defaultValue`) follows\n// the controlled value, or `default-value` when uncontrolled. It is set\n// through the `textContent` property, which Vue's server renderer prints\n// as the element's content, so `vServerText` can adjust it at SSR.\n\ntype ListenerValue = ((event: Event) => void) | Array<(event: Event) => void> | undefined\n\nconst invoke = (handler: unknown, event: Event) => {\n for (const fn of ([] as unknown[]).concat(handler ?? [])) {\n if (typeof fn === \"function\") fn(event)\n }\n}\n\nconst TextareaComponent = defineComponent({\n name: \"Textarea\",\n inheritAttrs: false,\n props: {\n /** Visual style of the textarea. @default \"outline\" */\n variant: { type: String as PropType<Variants<\"outline\" | \"soft\" | \"ghost\">>, default: \"outline\" },\n /** Controls the size of the textarea. @default \"md\" */\n size: { type: String as PropType<ControlSize>, default: \"md\" },\n /** Applies a negative margin using the current gutter to optically align the text with surrounding content. */\n opticallyAlign: String as PropType<\"start\" | \"end\">,\n /** Controls gutter on the edges of the textarea, defaults to value from `size`. */\n gutterSize: String as PropType<Sizes<\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\">>,\n /** Disables the textarea visually and from interactions. @default false */\n disabled: { type: Boolean, default: false },\n /** Makes the textarea read-only. @default false */\n readOnly: { type: Boolean, default: false },\n /** Native spelling of `readOnly` (`<Textarea readonly />`). @default false */\n readonly: { type: Boolean, default: false },\n /** Mark the textarea as invalid. @default false */\n invalid: { type: Boolean, default: false },\n /** Allow autofill extensions to appear in the textarea. @default `true` when `name` is set, else `false` */\n allowAutofillExtensions: { type: Boolean, default: undefined },\n /** Select all contents of the textarea when mounted (and whenever it turns `true`). @default false */\n autoSelect: { type: Boolean, default: undefined },\n /** Default number of rows to display. @default 3 */\n rows: { type: Number, default: 3 },\n /** Automatically adjust the height of the textarea based on its contents. @default false */\n autoResize: { type: Boolean, default: undefined },\n /** Maximum number of rows that can be displayed when autoResize is enabled. @default Math.max(rows, 10) */\n maxRows: Number,\n id: String,\n name: String,\n /** Controlled value (`v-model:value`) */\n value: { type: [String, Number] as PropType<string | number>, default: undefined },\n /** Initial value when uncontrolled */\n defaultValue: { type: [String, Number] as PropType<string | number>, default: undefined },\n /** The textarea's `autocomplete` attribute */\n autoComplete: String,\n },\n emits: {\n \"update:value\": (_value: string) => true,\n /** Fired when the browser autofills the textarea */\n \"autofill\": () => true,\n },\n setup(props, { attrs, emit, expose }) {\n const textAreaRef = ref<HTMLTextAreaElement | null>(null)\n // Prevent 1Password from appearing: 1Password won't display if it thinks this field is for searching\n // Allow user to override this for connecting to labels.\n const onePasswordPreventionId = `search-ui-input-${useId()}`\n const focused = ref(false)\n\n const computedMaxRows = computed(() =>\n props.autoResize ? Math.max(props.maxRows ?? 10, props.rows) : props.rows,\n )\n const controlledValue = () => props.value\n\n const runAutoSelect = () => {\n if (props.autoSelect) {\n textAreaRef.value?.select()\n }\n }\n\n const autosizeTextarea = () => {\n const textarea = textAreaRef.value\n if (!props.autoResize || !textarea || computedMaxRows.value === undefined) return\n textarea.style.height = \"0px\"\n const scrollHeight = textarea.scrollHeight\n textarea.style.height = scrollHeight + \"px\"\n }\n\n // The textarea's value is set imperatively, like React DOM does: a `value`\n // prop would make Vue mirror it into a `value` attribute, which React\n // never renders. Puts the controlled value back when it differs.\n const syncValue = () => {\n const textarea = textAreaRef.value\n const value = controlledValue()\n if (!textarea || value == null) return\n const nextValue = String(value)\n if (textarea.value !== nextValue) textarea.value = nextValue\n }\n\n // React runs these effects after mount and when their dependencies change\n onMounted(() => {\n const textarea = textAreaRef.value\n // React's initTextarea: the initial text becomes the (dirty) value\n const initialValue = textarea?.textContent ?? \"\"\n if (textarea && initialValue !== \"\") textarea.value = initialValue\n runAutoSelect()\n autosizeTextarea()\n })\n // React DOM writes a new value while committing, before any effect runs,\n // so the selection covers the value that arrives with `autoSelect`\n watch(\n () => props.autoSelect,\n () => {\n syncValue()\n runAutoSelect()\n },\n { flush: \"post\" },\n )\n // Resize if value changes outside of user input - e.g. form reset\n watch(\n () => [controlledValue(), props.rows, props.autoResize, computedMaxRows.value],\n () => {\n syncValue()\n autosizeTextarea()\n },\n { flush: \"post\" },\n )\n\n const handleInput = (evt: Event) => {\n const next = (evt.target as HTMLTextAreaElement).value\n emit(\"update:value\", next)\n autosizeTextarea()\n // A controlled value the parent did not update wins over the typed text,\n // as in React\n void nextTick(syncValue)\n }\n\n expose({\n /** The rendered <textarea> element */\n textarea: textAreaRef,\n focus: () => textAreaRef.value?.focus(),\n })\n\n return () => {\n const {\n class: className,\n onInput,\n onFocus,\n onBlur,\n onAnimationstart,\n ...restAttrs\n } = attrs as Record<string, unknown>\n\n const { variant, size, gutterSize, disabled, invalid, rows } = props\n const readOnly = props.readOnly || props.readonly\n // Default to `true` when presence of `name`\n const allowAutofillExtensions = props.allowAutofillExtensions ?? !!props.name\n const value = controlledValue()\n // React keeps the textarea's defaultValue (its text content) in sync with\n // the controlled value, else with `defaultValue`\n const textContent = String(value ?? props.defaultValue ?? \"\")\n\n return h(\n \"div\",\n {\n \"class\": [s.Container, className],\n \"data-variant\": variant,\n \"data-size\": size,\n \"data-gutter-size\": gutterSize,\n \"data-optically-align\": props.opticallyAlign,\n \"data-focused\": String(focused.value),\n \"data-disabled\": disabled ? \"\" : undefined,\n \"data-readonly\": readOnly ? \"\" : undefined,\n \"data-invalid\": invalid ? \"\" : undefined,\n \"style\": toCssVariables({\n \"textarea-min-rows\": `${rows}`,\n \"textarea-max-rows\": `${computedMaxRows.value}`,\n }),\n },\n [\n withDirectives(\n h(\"textarea\", {\n ...restAttrs,\n \"onInput\": (evt: Event) => {\n invoke(onInput as ListenerValue, evt)\n handleInput(evt)\n },\n \"ref\": textAreaRef,\n \"id\": props.id || (allowAutofillExtensions ? undefined : onePasswordPreventionId),\n \"class\": s.Textarea,\n \"name\": props.name,\n \"autocomplete\": props.autoComplete ?? restAttrs.autocomplete,\n // Expose the visual invalid state to assistive tech; an explicit aria-invalid wins\n \"aria-invalid\": restAttrs[\"aria-invalid\"] ?? (invalid || undefined),\n \"readonly\": readOnly,\n \"disabled\": disabled,\n \"rows\": rows,\n \"onFocus\": (evt: Event) => {\n focused.value = true\n invoke(onFocus as ListenerValue, evt)\n },\n \"onBlur\": (evt: Event) => {\n focused.value = false\n invoke(onBlur as ListenerValue, evt)\n },\n \"onAnimationstart\": (evt: AnimationEvent) => {\n invoke(onAnimationstart as ListenerValue, evt)\n if (evt.animationName === \"native-autofill-in\") {\n emit(\"autofill\")\n }\n },\n // Prevent LastPass and 1Password from appearing\n \"data-lpignore\": allowAutofillExtensions ? undefined : true,\n \"data-1p-ignore\": allowAutofillExtensions ? undefined : true,\n \"textContent\": textContent,\n }),\n [[vServerText, textContent]],\n ),\n ],\n )\n }\n },\n})\n\ntype TextareaOwnProps = InstanceType<typeof TextareaComponent>[\"$props\"]\n\n/** Textarea props plus the native <textarea> attributes that fall through to it, as in React. */\nexport type TextareaProps = TextareaOwnProps & Omit<TextareaHTMLAttributes, keyof TextareaOwnProps | \"readonly\">\n\nexport const Textarea = TextareaComponent as typeof TextareaComponent & { new (): { $props: TextareaProps } }\n"]}
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import s from "@plexui/styles/components/Sortable/Sortable.module.css";
|
|
2
|
+
const START_DISTANCE = 4;
|
|
3
|
+
const TOUCH_HOLD = 200;
|
|
4
|
+
// Long enough to pass over a target on the way to another
|
|
5
|
+
const TARGET_HOLD = 500;
|
|
6
|
+
const SCROLL_EDGE = 80;
|
|
7
|
+
const SCROLL_SLOW = 4;
|
|
8
|
+
const SCROLL_FAST = 30;
|
|
9
|
+
const NEAR_LIST = 40;
|
|
10
|
+
const LINE_INSET = 16;
|
|
11
|
+
const PART = 4;
|
|
12
|
+
// --cubic-enter: an animation here cannot read a CSS variable
|
|
13
|
+
const EASING = "cubic-bezier(.19,1,.22,1)";
|
|
14
|
+
const HANDLE = "[data-sortable-handle]";
|
|
15
|
+
const GRAB = "[data-sortable-grab]";
|
|
16
|
+
const STATIC = "data-sortable-static";
|
|
17
|
+
const INTERACTIVE = [
|
|
18
|
+
"a[href]",
|
|
19
|
+
"button",
|
|
20
|
+
"input",
|
|
21
|
+
"select",
|
|
22
|
+
"summary",
|
|
23
|
+
"textarea",
|
|
24
|
+
"[contenteditable]",
|
|
25
|
+
"[role=button]",
|
|
26
|
+
"[role=checkbox]",
|
|
27
|
+
"[role=combobox]",
|
|
28
|
+
"[role=link]",
|
|
29
|
+
"[role=radio]",
|
|
30
|
+
"[role=slider]",
|
|
31
|
+
"[role=switch]",
|
|
32
|
+
"[role=tab]",
|
|
33
|
+
].join(",");
|
|
34
|
+
// Read at the moment of use: the setting can change while the page is open
|
|
35
|
+
const reduced = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
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
|
+
};
|
|
50
|
+
/** Grips stand in a table, elsewhere only when the list asks for them */
|
|
51
|
+
const showsHandles = (list) => (list.dataset.handles ?? String(list.tagName === "TBODY")) === "true";
|
|
52
|
+
/** The direct child of the list that holds `target` */
|
|
53
|
+
const itemOf = (list, target) => {
|
|
54
|
+
let node = target;
|
|
55
|
+
while (node && node.parentElement !== list)
|
|
56
|
+
node = node.parentElement;
|
|
57
|
+
return node;
|
|
58
|
+
};
|
|
59
|
+
/** The order after a move. `to` counts the list without the moved item. */
|
|
60
|
+
export const moveItem = (items, from, to) => {
|
|
61
|
+
const next = [...items];
|
|
62
|
+
next.splice(to, 0, ...next.splice(from, 1));
|
|
63
|
+
return next;
|
|
64
|
+
};
|
|
65
|
+
const labelOf = (item) => {
|
|
66
|
+
const named = item.dataset.sortableLabel ?? item.querySelector("[data-sortable-label], label")?.textContent;
|
|
67
|
+
if (named)
|
|
68
|
+
return named.trim();
|
|
69
|
+
// The cells of a row or the lines of a card run together: the first one is the name
|
|
70
|
+
const texts = document.createTreeWalker(item, NodeFilter.SHOW_TEXT);
|
|
71
|
+
while (texts.nextNode()) {
|
|
72
|
+
if (texts.currentNode.textContent?.trim())
|
|
73
|
+
return texts.currentNode.parentElement?.textContent?.trim() ?? "";
|
|
74
|
+
}
|
|
75
|
+
return "";
|
|
76
|
+
};
|
|
77
|
+
/** What a screen reader hears: a picture has no text of its own */
|
|
78
|
+
const nameOf = (item) => labelOf(item) || item.querySelector("img")?.alt || "";
|
|
79
|
+
function makeChip(item) {
|
|
80
|
+
const chip = document.createElement("div");
|
|
81
|
+
chip.className = s.Chip;
|
|
82
|
+
const image = item.querySelector("[data-sortable-image], img");
|
|
83
|
+
if (image instanceof HTMLImageElement) {
|
|
84
|
+
const picture = document.createElement("img");
|
|
85
|
+
picture.className = s.ChipImage;
|
|
86
|
+
picture.src = image.currentSrc || image.src;
|
|
87
|
+
picture.alt = "";
|
|
88
|
+
chip.append(picture);
|
|
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
|
+
}
|
|
97
|
+
const text = labelOf(item);
|
|
98
|
+
if (text) {
|
|
99
|
+
const label = document.createElement("span");
|
|
100
|
+
label.className = s.ChipLabel;
|
|
101
|
+
label.textContent = text;
|
|
102
|
+
chip.append(label);
|
|
103
|
+
}
|
|
104
|
+
document.body.append(chip);
|
|
105
|
+
return chip;
|
|
106
|
+
}
|
|
107
|
+
function announce(text) {
|
|
108
|
+
let region = document.querySelector("[data-sortable-live]");
|
|
109
|
+
if (!region) {
|
|
110
|
+
region = document.createElement("div");
|
|
111
|
+
region.className = s.Live;
|
|
112
|
+
region.setAttribute("aria-live", "polite");
|
|
113
|
+
region.setAttribute("data-sortable-live", "");
|
|
114
|
+
document.body.append(region);
|
|
115
|
+
}
|
|
116
|
+
region.textContent = text;
|
|
117
|
+
}
|
|
118
|
+
const scrollParent = (start) => {
|
|
119
|
+
for (let node = start; node && node !== document.body; node = node.parentElement) {
|
|
120
|
+
if (/auto|scroll/.test(getComputedStyle(node).overflowY) && node.scrollHeight > node.clientHeight)
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
};
|
|
125
|
+
// The nearer the edge the faster: slow to aim at a row, fast across a long list
|
|
126
|
+
const scrollStep = (depth) => SCROLL_SLOW + (SCROLL_FAST - SCROLL_SLOW) * Math.min(depth / SCROLL_EDGE, 1);
|
|
127
|
+
function listSlots(list, items) {
|
|
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
|
+
}
|
|
135
|
+
const rects = items.map((item) => item.getBoundingClientRect());
|
|
136
|
+
return Array.from({ length: items.length + 1 }, (_, gap) => {
|
|
137
|
+
const above = rects[gap - 1];
|
|
138
|
+
const below = rects[gap];
|
|
139
|
+
const y = above && below ? (above.bottom + below.top) / 2 : (below?.top ?? above.bottom);
|
|
140
|
+
return {
|
|
141
|
+
gap,
|
|
142
|
+
x: inset,
|
|
143
|
+
y: y - box.top,
|
|
144
|
+
length: box.width - inset * 2,
|
|
145
|
+
before: items[gap - 1],
|
|
146
|
+
after: items[gap],
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// One slot per gap, and two where a line wraps: the end of one line and the start of the next
|
|
151
|
+
function gridSlots(list, items) {
|
|
152
|
+
const box = list.getBoundingClientRect();
|
|
153
|
+
if (!items.length)
|
|
154
|
+
return [{ gap: 0, x: 0, y: 0, length: box.height }];
|
|
155
|
+
const half = (parseFloat(getComputedStyle(list).columnGap) || 8) / 2;
|
|
156
|
+
const rects = items.map((item) => item.getBoundingClientRect());
|
|
157
|
+
const slots = [];
|
|
158
|
+
const add = (gap, x, rect, before, after) => slots.push({ gap, x: x - box.left, y: rect.top - box.top, length: rect.height, before, after });
|
|
159
|
+
rects.forEach((rect, gap) => {
|
|
160
|
+
const previous = rects[gap - 1];
|
|
161
|
+
if (previous && Math.abs(previous.top - rect.top) < rect.height / 2) {
|
|
162
|
+
add(gap, (previous.right + rect.left) / 2, rect, items[gap - 1], items[gap]);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (previous)
|
|
166
|
+
add(gap, previous.right + half, previous, items[gap - 1]);
|
|
167
|
+
add(gap, rect.left - half, rect, undefined, items[gap]);
|
|
168
|
+
});
|
|
169
|
+
const last = rects.length - 1;
|
|
170
|
+
add(rects.length, rects[last].right + half, rects[last], items[last]);
|
|
171
|
+
return slots;
|
|
172
|
+
}
|
|
173
|
+
// The chip keeps its size and fades out: grown to the item, its text would stand over the item's own
|
|
174
|
+
function flyChip(chip, to, layout) {
|
|
175
|
+
if (reduced() || !to) {
|
|
176
|
+
chip.remove();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const from = chip.getBoundingClientRect();
|
|
180
|
+
const x = layout === "grid" ? to.left + (to.width - from.width) / 2 : to.left;
|
|
181
|
+
const y = to.top + (to.height - from.height) / 2;
|
|
182
|
+
chip.animate([
|
|
183
|
+
{ transform: `translate(${from.left}px, ${from.top}px)`, opacity: 1 },
|
|
184
|
+
{ transform: `translate(${x}px, ${y}px)`, opacity: 0 },
|
|
185
|
+
], { duration: 200, easing: EASING, fill: "forwards" }).onfinish = () => chip.remove();
|
|
186
|
+
}
|
|
187
|
+
/** The items that changed place slide there from where they stood */
|
|
188
|
+
async function flip(items, work, skip) {
|
|
189
|
+
const before = new Map(items.map((item) => [item, item.getBoundingClientRect()]));
|
|
190
|
+
await work();
|
|
191
|
+
if (reduced())
|
|
192
|
+
return;
|
|
193
|
+
items
|
|
194
|
+
.filter((item) => item !== skip && item.isConnected)
|
|
195
|
+
.forEach((item) => {
|
|
196
|
+
const was = before.get(item);
|
|
197
|
+
const now = item.getBoundingClientRect();
|
|
198
|
+
const x = was.left - now.left;
|
|
199
|
+
const y = was.top - now.top;
|
|
200
|
+
if (x || y)
|
|
201
|
+
item.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "none" }], { duration: 200, easing: EASING });
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const swallow = (event) => {
|
|
205
|
+
event.stopPropagation();
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
};
|
|
208
|
+
function startDrag(event, list, source, layout, immediate, commit, link) {
|
|
209
|
+
const touch = event.pointerType === "touch" && !immediate;
|
|
210
|
+
const origin = { x: event.clientX, y: event.clientY };
|
|
211
|
+
const pointer = { ...origin };
|
|
212
|
+
const holdTimer = touch ? setTimeout(begin, TOUCH_HOLD) : undefined;
|
|
213
|
+
const style = getComputedStyle(list);
|
|
214
|
+
const part = parseFloat(layout === "grid" ? style.columnGap : style.rowGap) ? PART : 0;
|
|
215
|
+
const scroller = scrollParent(list);
|
|
216
|
+
const { group } = list.dataset;
|
|
217
|
+
const targets = group ? `[data-sortable-target="${CSS.escape(group)}"]` : undefined;
|
|
218
|
+
let drag;
|
|
219
|
+
window.addEventListener("pointermove", onMove);
|
|
220
|
+
window.addEventListener("pointerup", onUp);
|
|
221
|
+
window.addEventListener("pointercancel", onCancel);
|
|
222
|
+
function onMove({ clientX, clientY }) {
|
|
223
|
+
pointer.x = clientX;
|
|
224
|
+
pointer.y = clientY;
|
|
225
|
+
if (drag)
|
|
226
|
+
return;
|
|
227
|
+
const moved = Math.hypot(clientX - origin.x, clientY - origin.y) >= START_DISTANCE;
|
|
228
|
+
if (moved && touch)
|
|
229
|
+
stopListening();
|
|
230
|
+
else if (moved)
|
|
231
|
+
begin();
|
|
232
|
+
}
|
|
233
|
+
function onUp() {
|
|
234
|
+
finish(true);
|
|
235
|
+
}
|
|
236
|
+
function onCancel() {
|
|
237
|
+
finish(false);
|
|
238
|
+
}
|
|
239
|
+
function onKey(keyEvent) {
|
|
240
|
+
if (keyEvent.key !== "Escape")
|
|
241
|
+
return;
|
|
242
|
+
keyEvent.stopPropagation();
|
|
243
|
+
finish(false);
|
|
244
|
+
}
|
|
245
|
+
function blockScroll(touchEvent) {
|
|
246
|
+
touchEvent.preventDefault();
|
|
247
|
+
}
|
|
248
|
+
function stopListening() {
|
|
249
|
+
clearTimeout(holdTimer);
|
|
250
|
+
window.removeEventListener("pointermove", onMove);
|
|
251
|
+
window.removeEventListener("pointerup", onUp);
|
|
252
|
+
window.removeEventListener("pointercancel", onCancel);
|
|
253
|
+
window.removeEventListener("keydown", onKey, true);
|
|
254
|
+
window.removeEventListener("touchmove", blockScroll);
|
|
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
|
+
}
|
|
263
|
+
function begin() {
|
|
264
|
+
const areas = measure();
|
|
265
|
+
window.getSelection()?.removeAllRanges();
|
|
266
|
+
document.documentElement.classList.add(s.Dragging);
|
|
267
|
+
source.setAttribute("data-sortable-source", "");
|
|
268
|
+
window.addEventListener("keydown", onKey, true);
|
|
269
|
+
window.addEventListener("touchmove", blockScroll, { passive: false });
|
|
270
|
+
const chip = makeChip(source);
|
|
271
|
+
const line = document.createElement("div");
|
|
272
|
+
line.className = s.Line;
|
|
273
|
+
line.hidden = true;
|
|
274
|
+
document.body.append(line);
|
|
275
|
+
const { width, height } = chip.getBoundingClientRect();
|
|
276
|
+
drag = { chip, line, width, height, areas };
|
|
277
|
+
tick();
|
|
278
|
+
}
|
|
279
|
+
function tick() {
|
|
280
|
+
if (!drag)
|
|
281
|
+
return;
|
|
282
|
+
drag.under = targets ? document.elementFromPoint(pointer.x, pointer.y) : undefined;
|
|
283
|
+
autoScroll();
|
|
284
|
+
update();
|
|
285
|
+
drag.frame = requestAnimationFrame(tick);
|
|
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
|
+
}
|
|
292
|
+
function autoScroll() {
|
|
293
|
+
const area = panelUnder() ?? scroller;
|
|
294
|
+
const box = area?.getBoundingClientRect() ?? { top: 0, bottom: window.innerHeight };
|
|
295
|
+
const above = box.top + SCROLL_EDGE - pointer.y;
|
|
296
|
+
const below = pointer.y - box.bottom + SCROLL_EDGE;
|
|
297
|
+
const step = above > 0 ? -scrollStep(above) : below > 0 ? scrollStep(below) : 0;
|
|
298
|
+
if (!step)
|
|
299
|
+
return;
|
|
300
|
+
if (area)
|
|
301
|
+
area.scrollTop += step;
|
|
302
|
+
else
|
|
303
|
+
window.scrollBy(0, step);
|
|
304
|
+
}
|
|
305
|
+
function update() {
|
|
306
|
+
if (!drag)
|
|
307
|
+
return;
|
|
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);
|
|
312
|
+
const box = source.getBoundingClientRect();
|
|
313
|
+
const overSource = pointer.x >= box.left && pointer.x <= box.right && pointer.y >= box.top && pointer.y <= box.bottom;
|
|
314
|
+
source.toggleAttribute("data-sortable-away", !overSource);
|
|
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));
|
|
337
|
+
}
|
|
338
|
+
function nearestSlot() {
|
|
339
|
+
if (!drag)
|
|
340
|
+
return undefined;
|
|
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)
|
|
360
|
+
return undefined;
|
|
361
|
+
const index = best.area.items.indexOf(source);
|
|
362
|
+
return index >= 0 && (best.slot.gap === index || best.slot.gap === index + 1) ? undefined : best;
|
|
363
|
+
}
|
|
364
|
+
function shift(slot, by) {
|
|
365
|
+
const move = (item, offset) => {
|
|
366
|
+
if (!item || !part)
|
|
367
|
+
return;
|
|
368
|
+
item.style.transform = by
|
|
369
|
+
? layout === "grid"
|
|
370
|
+
? `translateX(${offset}px)`
|
|
371
|
+
: `translateY(${offset}px)`
|
|
372
|
+
: "";
|
|
373
|
+
};
|
|
374
|
+
move(slot?.before, -by);
|
|
375
|
+
move(slot?.after, by);
|
|
376
|
+
}
|
|
377
|
+
function showGap(found) {
|
|
378
|
+
if (!drag)
|
|
379
|
+
return;
|
|
380
|
+
if (found?.slot !== drag.found?.slot) {
|
|
381
|
+
shift(drag.found?.slot, 0);
|
|
382
|
+
shift(found?.slot, part);
|
|
383
|
+
}
|
|
384
|
+
drag.found = found;
|
|
385
|
+
drag.line.hidden = !found;
|
|
386
|
+
if (!found)
|
|
387
|
+
return;
|
|
388
|
+
const box = found.area.list.getBoundingClientRect();
|
|
389
|
+
if (layout === "grid") {
|
|
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`;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
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`;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function restoreSource() {
|
|
399
|
+
source.removeAttribute("data-sortable-source");
|
|
400
|
+
source.removeAttribute("data-sortable-away");
|
|
401
|
+
}
|
|
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 });
|
|
407
|
+
}
|
|
408
|
+
async function land(chip, areas, { area, slot: { gap } }, clear) {
|
|
409
|
+
const from = itemsOf(list).indexOf(source);
|
|
410
|
+
restoreSource();
|
|
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), () => {
|
|
423
|
+
clear();
|
|
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);
|
|
438
|
+
}
|
|
439
|
+
function finish(drop) {
|
|
440
|
+
stopListening();
|
|
441
|
+
if (!drag)
|
|
442
|
+
return;
|
|
443
|
+
const { chip, line, found, areas, frame, target, rest } = drag;
|
|
444
|
+
if (frame)
|
|
445
|
+
cancelAnimationFrame(frame);
|
|
446
|
+
clearTimeout(rest);
|
|
447
|
+
target?.removeAttribute("data-over");
|
|
448
|
+
// The pointer lifts over a row: that click must not act on it
|
|
449
|
+
window.addEventListener("click", swallow, { capture: true, once: true });
|
|
450
|
+
setTimeout(() => window.removeEventListener("click", swallow, { capture: true }));
|
|
451
|
+
line.remove();
|
|
452
|
+
drag = undefined;
|
|
453
|
+
// On a drop this waits for the flip to read where the parted items stand
|
|
454
|
+
const clear = () => {
|
|
455
|
+
areas.forEach((area) => area.list.removeAttribute("data-dragging"));
|
|
456
|
+
document.documentElement.classList.remove(s.Dragging);
|
|
457
|
+
shift(found?.slot, 0);
|
|
458
|
+
};
|
|
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);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
clear();
|
|
469
|
+
restoreSource();
|
|
470
|
+
settle(chip);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/** Starts a drag from a press inside the list. Goes on the list's `pointerdown`. */
|
|
474
|
+
export function sortablePointerDown(event, list, layout, commit, link) {
|
|
475
|
+
const target = event.target;
|
|
476
|
+
if (event.button || target.closest("[data-sortable]") !== list)
|
|
477
|
+
return;
|
|
478
|
+
const source = itemOf(list, target);
|
|
479
|
+
if (!source || source.hasAttribute(STATIC))
|
|
480
|
+
return;
|
|
481
|
+
const handle = target.closest(HANDLE);
|
|
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))
|
|
486
|
+
return;
|
|
487
|
+
startDrag(event, list, source, layout, !!handle, commit, link);
|
|
488
|
+
}
|
|
489
|
+
const KEY_STEPS = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 };
|
|
490
|
+
/** Moves the focused handle's item with the arrow keys. Goes on the list's `keydown`. */
|
|
491
|
+
export async function sortableKeyDown(event, list, commit, link) {
|
|
492
|
+
const step = KEY_STEPS[event.key];
|
|
493
|
+
const target = event.target;
|
|
494
|
+
if (!step || target.closest("[data-sortable]") !== list)
|
|
495
|
+
return;
|
|
496
|
+
const source = itemOf(list, target);
|
|
497
|
+
if (!source || (target !== source && !target.closest(HANDLE)))
|
|
498
|
+
return;
|
|
499
|
+
const items = itemsOf(list);
|
|
500
|
+
const from = items.indexOf(source);
|
|
501
|
+
const to = from + step;
|
|
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}`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
event.preventDefault();
|
|
518
|
+
await flip(items, () => commit(from, to));
|
|
519
|
+
// Moving an element in the document drops its focus
|
|
520
|
+
if (target.isConnected && document.activeElement !== target)
|
|
521
|
+
target.focus();
|
|
522
|
+
announce(`${nameOf(source)}, ${to + 1} / ${items.length}`);
|
|
523
|
+
}
|
|
524
|
+
//# sourceMappingURL=sortable.js.map
|