@fusion.dev/helix-core 0.148.0 → 0.149.1

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.
Files changed (70) hide show
  1. package/custom-elements.json +8518 -6050
  2. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +1 -1
  3. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +2 -2
  4. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +5 -4
  5. package/dist/controllers/machine-controller/machine-controller.d.ts +69 -0
  6. package/dist/controllers/machine-controller/machine-controller.js +78 -0
  7. package/dist/controllers/machine-controller/machine.d.ts +93 -0
  8. package/dist/controllers/machine-controller/machine.js +20 -0
  9. package/dist/controllers/reorder-controller/reorder-controller.d.ts +285 -0
  10. package/dist/controllers/reorder-controller/reorder-controller.js +497 -0
  11. package/dist/controllers/source-controller/source-controller.js +1 -1
  12. package/dist/controllers/virtualize-controller/heights.js +1 -1
  13. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +117 -3
  14. package/dist/controllers/virtualize-controller/virtualize-controller.js +134 -32
  15. package/dist/events/before-reorder.d.ts +14 -0
  16. package/dist/events/before-reorder.js +11 -0
  17. package/dist/events/details.d.ts +28 -0
  18. package/dist/events/events.d.ts +4 -2
  19. package/dist/events/events.js +3 -1
  20. package/dist/events/names.js +1 -1
  21. package/dist/events/reorder.d.ts +18 -12
  22. package/dist/events/reordered.d.ts +4 -4
  23. package/dist/helix-element/helix-element.js +2 -1
  24. package/dist/helix-element/react-audit-off.d.ts +11 -0
  25. package/dist/helix-element/react-audit-off.js +1 -0
  26. package/dist/helix-element/react-audit.d.ts +20 -0
  27. package/dist/helix-element/react-audit.js +56 -0
  28. package/dist/helix-element/react-bridge.d.ts +2 -22
  29. package/dist/helix-element/react-bridge.js +1 -56
  30. package/dist/index.d.ts +10 -4
  31. package/dist/index.js +10 -4
  32. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +2 -2
  33. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +3 -0
  34. package/dist/mixins/mixin-collection/mixin-collection.js +4 -0
  35. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +3 -0
  36. package/dist/mixins/mixin-field/mixin-field.js +1 -1
  37. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +12 -0
  38. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +15 -2
  39. package/dist/mixins/mixin-tokens/mixin-tokens.d.ts +5 -0
  40. package/dist/mixins/mixin-tokens/mixin-tokens.js +13 -2
  41. package/dist/mixins/mixin-viewport/mixin-viewport.d.ts +17 -15
  42. package/dist/mixins/mixin-viewport/mixin-viewport.js +15 -34
  43. package/dist/utils/calendar/calendar.d.ts +27 -0
  44. package/dist/utils/calendar/calendar.js +17 -0
  45. package/dist/utils/count-label/count-label.d.ts +7 -4
  46. package/dist/utils/count-label/count-label.js +2 -5
  47. package/dist/utils/date-segments/date-segments.d.ts +2 -1
  48. package/dist/utils/date-segments/date-segments.js +10 -0
  49. package/dist/utils/locale-of/locale-of.d.ts +5 -0
  50. package/dist/utils/locale-of/locale-of.js +8 -2
  51. package/dist/utils/reorder/drag-preview.d.ts +46 -0
  52. package/dist/utils/reorder/drag-preview.js +58 -0
  53. package/dist/utils/reorder/reorder.d.ts +178 -0
  54. package/dist/utils/reorder/reorder.js +112 -0
  55. package/dist/utils/report-runtime/report-runtime.d.ts +7 -4
  56. package/dist/utils/slotted-words/slotted-words.d.ts +3 -2
  57. package/dist/utils/slotted-words/slotted-words.js +3 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/dist/words/index.d.ts +3 -0
  61. package/dist/words/index.js +3 -0
  62. package/dist/words/message.d.ts +47 -0
  63. package/dist/words/message.js +152 -0
  64. package/dist/words/registry.d.ts +89 -0
  65. package/dist/words/registry.js +101 -0
  66. package/dist/words/words-locale.d.ts +34 -0
  67. package/dist/words/words-locale.js +30 -0
  68. package/dist/words/words.d.ts +52 -0
  69. package/dist/words/words.js +100 -0
  70. package/package.json +40 -25
@@ -8,9 +8,9 @@ import { html, nothing } from 'lit';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
10
10
  import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
11
- import { fillTemplate } from '../../utils/fill-template/fill-template.js';
12
11
  import { inlineStep } from '../../utils/inline-step/inline-step.js';
13
12
  import { isRtl } from '../../utils/is-rtl/is-rtl.js';
13
+ import { formatWords, words } from '../../words/words.js';
14
14
  const HINT_SHOWS = 1600;
15
15
  export const MixinViewport = (Base) => {
16
16
  class Viewport extends Base {
@@ -38,11 +38,10 @@ export const MixinViewport = (Base) => {
38
38
  this.handleGestureWheel = (event) => {
39
39
  if (this.gestures === 'cooperative' && !(event.ctrlKey || event.metaKey)) {
40
40
  const mac = typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac');
41
- const words = this.wheelHintLabel ?? this.restingGestureLabels.wheel;
42
- this.showHint(fillTemplate(words, { key: mac ? '⌘' : 'Ctrl' }), event);
41
+ this.showHint(formatWords(this, 'wheelHintLabel', { key: mac ? '⌘' : 'Ctrl' }), event);
43
42
  }
44
43
  else if (this.gestures === 'activate' && !this.gesturesTaken) {
45
- this.showHint(this.activateHintLabel ?? this.restingGestureLabels.activate, event);
44
+ this.showHint(this.activateHintLabel, event);
46
45
  }
47
46
  };
48
47
  this.handleGestureTouch = (event) => {
@@ -51,10 +50,10 @@ export const MixinViewport = (Base) => {
51
50
  return;
52
51
  }
53
52
  if (this.gestures === 'cooperative') {
54
- this.showHint(this.twoFingerHintLabel ?? this.restingGestureLabels.twoFinger, finger);
53
+ this.showHint(this.twoFingerHintLabel, finger);
55
54
  }
56
55
  else if (this.gestures === 'activate' && !this.gesturesTaken) {
57
- this.showHint(this.activateHintLabel ?? this.restingGestureLabels.activate, finger);
56
+ this.showHint(this.activateHintLabel, finger);
58
57
  }
59
58
  };
60
59
  this.handleTake = (event) => {
@@ -133,17 +132,7 @@ export const MixinViewport = (Base) => {
133
132
  }
134
133
  };
135
134
  }
136
- get restingControlsLabel() {
137
- return 'View controls';
138
- }
139
- get restingGestureLabels() {
140
- return {
141
- activate: 'Press to use it',
142
- twoFinger: 'Use two fingers to move it',
143
- using: 'In use. Press Escape to scroll the page again.',
144
- wheel: 'Use {key} and scroll to zoom',
145
- };
146
- }
135
+ static { this.words = ''; }
147
136
  get gestureMode() {
148
137
  if (this.gestures === 'cooperative') {
149
138
  return 'cooperative';
@@ -215,7 +204,7 @@ export const MixinViewport = (Base) => {
215
204
  ?hidden=${this.hint === ''}
216
205
  style=${at ? `--_viewport-hint-x: ${(at.x * 100).toFixed(2)}%; --_viewport-hint-y: ${(at.y * 100).toFixed(2)}%` : nothing}
217
206
  >${this.hint}</div>
218
- <div part="gesture-status" aria-hidden="true" ?hidden=${!this.gesturesTaken}>${this.usingLabel ?? this.restingGestureLabels.using}</div>`;
207
+ <div part="gesture-status" aria-hidden="true" ?hidden=${!this.gesturesTaken}>${this.usingLabel}</div>`;
219
208
  }
220
209
  panEvent(_detail) {
221
210
  throw new Error(`<${this.localName}> composes MixinViewport and must implement panEvent(), the surface naming no event of its own.`);
@@ -287,7 +276,7 @@ export const MixinViewport = (Base) => {
287
276
  return nothing;
288
277
  }
289
278
  const [up, start, end, down] = ['up', 'start', 'end', 'down'].map((way) => this.panGlyph(way));
290
- return html `<div part="controls" role="group" aria-label=${this.controlsLabel ?? this.restingControlsLabel}>
279
+ return html `<div part="controls" role="group" aria-label=${this.controlsLabel}>
291
280
  ${pan
292
281
  ? html `<button part="pan-up" aria-label=${this.panLabel('up')} type="button" @click=${() => this.pan('up')}>
293
282
  <slot name="pan-up-icon">${up}</slot>
@@ -325,19 +314,14 @@ export const MixinViewport = (Base) => {
325
314
  </div>`;
326
315
  }
327
316
  }
328
- __decorate([
329
- property({ attribute: 'activate-hint-label' })
330
- ], Viewport.prototype, "activateHintLabel", void 0);
331
317
  __decorate([
332
318
  property({ reflect: true })
333
319
  ], Viewport.prototype, "controls", void 0);
334
- __decorate([
335
- property({ attribute: 'controls-label' })
336
- ], Viewport.prototype, "controlsLabel", void 0);
337
320
  __decorate([
338
321
  property({ attribute: 'controls-placement', reflect: true })
339
322
  ], Viewport.prototype, "controlsPlacement", void 0);
340
323
  __decorate([
324
+ words,
341
325
  property({ attribute: 'fullscreen-label' })
342
326
  ], Viewport.prototype, "fullscreenLabel", void 0);
343
327
  __decorate([
@@ -347,30 +331,26 @@ export const MixinViewport = (Base) => {
347
331
  property({ attribute: 'idle-delay', type: Number })
348
332
  ], Viewport.prototype, "idleDelay", void 0);
349
333
  __decorate([
334
+ words,
350
335
  property({ attribute: 'pan-down-label' })
351
336
  ], Viewport.prototype, "panDownLabel", void 0);
352
337
  __decorate([
338
+ words,
353
339
  property({ attribute: 'pan-left-label' })
354
340
  ], Viewport.prototype, "panLeftLabel", void 0);
355
341
  __decorate([
342
+ words,
356
343
  property({ attribute: 'pan-right-label' })
357
344
  ], Viewport.prototype, "panRightLabel", void 0);
358
345
  __decorate([
359
346
  property({ attribute: 'pan-step', type: Number })
360
347
  ], Viewport.prototype, "panStep", void 0);
361
348
  __decorate([
349
+ words,
362
350
  property({ attribute: 'pan-up-label' })
363
351
  ], Viewport.prototype, "panUpLabel", void 0);
364
352
  __decorate([
365
- property({ attribute: 'two-finger-hint-label' })
366
- ], Viewport.prototype, "twoFingerHintLabel", void 0);
367
- __decorate([
368
- property({ attribute: 'using-label' })
369
- ], Viewport.prototype, "usingLabel", void 0);
370
- __decorate([
371
- property({ attribute: 'wheel-hint-label' })
372
- ], Viewport.prototype, "wheelHintLabel", void 0);
373
- __decorate([
353
+ words,
374
354
  property({ attribute: 'zoom-in-label' })
375
355
  ], Viewport.prototype, "zoomInLabel", void 0);
376
356
  __decorate([
@@ -389,6 +369,7 @@ export const MixinViewport = (Base) => {
389
369
  state()
390
370
  ], Viewport.prototype, "hintAt", void 0);
391
371
  __decorate([
372
+ words,
392
373
  property({ attribute: 'zoom-out-label' })
393
374
  ], Viewport.prototype, "zoomOutLabel", void 0);
394
375
  return Viewport;
@@ -16,6 +16,33 @@ export declare const localToday: (now?: Date) => string;
16
16
  * neither, as Firefox does not, or where the tag cannot be read.
17
17
  */
18
18
  export declare const weekStartOf: (locale: string) => number;
19
+ /**
20
+ * The calendars a date family draws and writes: those whose months are the
21
+ * Gregorian months, so a grid of Gregorian days is one of their months.
22
+ *
23
+ * `buddhist` is Thailand's (2569 for 2026), `japanese` the Japanese eras
24
+ * (Reiwa 8), and `roc` Taiwan's Minguo years (115). They differ from
25
+ * `gregory` in the year alone, and `iso8601` in the order the parts are
26
+ * written.
27
+ */
28
+ export type DateCalendar = 'buddhist' | 'gregory' | 'iso8601' | 'japanese' | 'roc';
29
+ /**
30
+ * The language tag a date family formats with: `locale`, with the calendar it
31
+ * writes dates in.
32
+ *
33
+ * That is `calendar` where it is one of the five `DateCalendar` names, then
34
+ * the locale's own calendar where its months are the Gregorian months, then
35
+ * `gregory`. **A calendar with months of its own, such as `persian` for `fa-IR`
36
+ * or `islamic-umalqura` for `ar-SA` in some engines, is written as `gregory`.**
37
+ * The arithmetic is Gregorian, so a heading in Mehr 1405 over the days of
38
+ * October 2026 named a month the grid did not show.
39
+ *
40
+ * `locale` unchanged where nothing moves, so a page in a Gregorian locale
41
+ * formats exactly as it did. Empty is the runtime's own locale. A tag `Intl`
42
+ * refuses throws, as every formatter given one does. Remembered per locale and
43
+ * calendar, since reading a locale's calendar builds a formatter.
44
+ */
45
+ export declare const calendarLocale: (locale: string, calendar?: string) => string;
19
46
  /** A month as `YYYY-MM`. */
20
47
  export declare const monthOf: (date: Date) => string;
21
48
  /** A date some days away, which may be negative, at midnight UTC. */
@@ -15,6 +15,23 @@ export const weekStartOf = (locale) => {
15
15
  return 0;
16
16
  }
17
17
  };
18
+ const DRAWN = ['buddhist', 'gregory', 'iso8601', 'japanese', 'roc'];
19
+ const tagged = new Map();
20
+ export const calendarLocale = (locale, calendar = '') => {
21
+ const key = `${locale} ${calendar}`;
22
+ let tag = tagged.get(key);
23
+ if (tag === undefined) {
24
+ const own = new Intl.DateTimeFormat(locale || undefined).resolvedOptions();
25
+ const wanted = DRAWN.includes(calendar)
26
+ ? calendar
27
+ : DRAWN.includes(own.calendar)
28
+ ? ''
29
+ : 'gregory';
30
+ tag = wanted ? `${new Intl.Locale(locale || own.locale, { calendar: wanted })}` : locale;
31
+ tagged.set(key, tag);
32
+ }
33
+ return tag;
34
+ };
18
35
  export const monthOf = (date) => `${String(date.getUTCFullYear()).padStart(4, '0')}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
19
36
  export const addDays = (date, days) => utcDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days);
20
37
  export const sameMonth = (left, right) => left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth();
@@ -4,10 +4,13 @@
4
4
  *
5
5
  * **`Intl.PluralRules` picks the sentence**, because the line between "one"
6
6
  * and "other" is the language's: French says "0 résultat" where English says
7
- * "0 results". A language with more forms than two writes its `one` in the
8
- * first sentence and the rest in the second.
7
+ * "0 results". Each sentence is a message, so `other` can hold every form a
8
+ * language has, `{count, plural, one {# wynik} few {# wyniki} many {# wyników}
9
+ * other {# wyniku}}`, and an empty `one` leaves the choice to it.
9
10
  *
10
- * Data Table says how many rows a search left this way, and Listbox how many
11
- * options a filter left, which Combobox and Command hand down to it.
11
+ * Kept for the families whose singular property is still read. A family on the
12
+ * words registry formats its one plural message with `formatWords`. Listbox
13
+ * says how many options a filter left this way, which Combobox and Command
14
+ * hand down to it.
12
15
  */
13
16
  export declare const countLabel: (count: number, one: string, other: string, locale?: string) => string;
@@ -1,5 +1,2 @@
1
- import { fillTemplate } from '../fill-template/fill-template.js';
2
- import { formatNumbers } from '../format-numbers/format-numbers.js';
3
- export const countLabel = (count, one, other, locale = '') => fillTemplate(new Intl.PluralRules(locale || undefined).select(count) === 'one' ? one : other, {
4
- count: formatNumbers(locale)(count),
5
- });
1
+ import { formatMessage } from '../../words/message.js';
2
+ export const countLabel = (count, one, other, locale = '') => formatMessage(one && new Intl.PluralRules(locale || undefined).select(count) === 'one' ? one : other, { count }, locale);
@@ -26,7 +26,8 @@ export interface SegmentOptions {
26
26
  * between them, read off `Intl`'s own rendering of a sample.
27
27
  *
28
28
  * `en-US` is month, day, year with slashes; `de-DE` day, month, year with
29
- * dots; `ja-JP` year, month, day. A time follows the clock: a twelve hour
29
+ * dots; `ja-JP` year, month, day; the `iso8601` calendar year, month, day
30
+ * with hyphens. A time follows the clock: a twelve hour
30
31
  * locale ends with the half of the day.
31
32
  */
32
33
  export declare const segmentLayout: (kind: SegmentedKind, locale: string, options?: SegmentOptions) => SegmentSlot[];
@@ -14,6 +14,16 @@ export const segmentLayout = (kind, locale, options = {}) => {
14
14
  ...(options.seconds ? { second: '2-digit' } : {}),
15
15
  ...(options.hourCycle ? { hourCycle: options.hourCycle } : {}),
16
16
  });
17
+ if (kind === 'date' &&
18
+ (/-ca-iso8601/i.test(locale) || format.resolvedOptions().calendar === 'iso8601')) {
19
+ return [
20
+ { kind: 'year' },
21
+ { literal: '-' },
22
+ { kind: 'month' },
23
+ { literal: '-' },
24
+ { kind: 'day' },
25
+ ];
26
+ }
17
27
  const known = new Set(['day', 'dayPeriod', 'hour', 'minute', 'month', 'second', 'year']);
18
28
  const slots = [];
19
29
  for (const part of format.formatToParts(SAMPLE)) {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * The runtime's own locale: in a browser, the language its interface is set
3
+ * to, which is what every `Intl` call given no locale uses.
4
+ */
5
+ export declare const runtimeLocale: () => string;
1
6
  /**
2
7
  * The locale an element speaks: `own`, the element's own `locale` property,
3
8
  * where one is set, and otherwise the `lang` of the nearest element with one,
@@ -1,9 +1,15 @@
1
1
  let runtime;
2
+ export const runtimeLocale = () => {
3
+ runtime ??= new Intl.Locale(new Intl.DateTimeFormat().resolvedOptions().locale);
4
+ return runtime.toString();
5
+ };
2
6
  const usable = (lang) => {
3
7
  try {
4
8
  const asked = new Intl.Locale(lang);
5
- runtime ??= new Intl.Locale(new Intl.DateTimeFormat().resolvedOptions().locale);
6
- return asked.region === undefined && asked.language === runtime.language ? '' : asked.toString();
9
+ runtimeLocale();
10
+ return asked.region === undefined && asked.language === runtime?.language
11
+ ? ''
12
+ : asked.toString();
7
13
  }
8
14
  catch {
9
15
  return '';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The copy of an item a pointer drags: what the item draws, through every
3
+ * shadow root and slot in it, rebuilt as plain elements with the styles the
4
+ * browser computed for each written on it.
5
+ *
6
+ * `ReorderController` puts the copy in the host's own shadow root, in the top
7
+ * layer, so the page's markup is never touched and no scrolling box clips it.
8
+ * A copy is inert: it has no custom elements, no slots, and no attributes but
9
+ * a picture's, so nothing in it upgrades, takes the host's children, or joins
10
+ * a radio group. Pseudo-elements are not copied.
11
+ */
12
+ import { nothing, type TemplateResult } from 'lit';
13
+ /**
14
+ * The part a host draws for the drag preview: an empty manual popover,
15
+ * hidden from assistive technology, which `ReorderController` fills with a
16
+ * copy of the item a pointer drags and shows in the top layer. Closed, it
17
+ * takes no room. Drawn while `on`, which is while the host reorders.
18
+ */
19
+ export declare const drawDragPreview: (on: boolean) => TemplateResult | typeof nothing;
20
+ /** A point in the viewport, where the boxes of a copy are measured from. */
21
+ interface Origin {
22
+ x: number;
23
+ y: number;
24
+ }
25
+ /**
26
+ * The box an element draws: its own, or the boxes of what it draws together,
27
+ * for an element that is `display: contents`, such as a row of a grid.
28
+ */
29
+ export declare const boxOf: (element: Element) => DOMRect;
30
+ /**
31
+ * A copy of what a node draws, for a drag preview, with every style the
32
+ * browser computed for each part written on it. The custom properties are
33
+ * left out, having done their work, and so are the logical properties, whose
34
+ * physical twins are written: two of a pair in one style do not agree on
35
+ * which wins.
36
+ *
37
+ * A `placed` node is put where it is with `position: absolute`, measured from
38
+ * `origin`, the padding box it is placed in, rather than laid out again. The
39
+ * root of a preview is placed in the preview, whose padding box is the root's
40
+ * box as `boxOf` measures it, and so is whatever a grid lays out, since a
41
+ * subgrid has no tracks of its own once it is copied out of its grid. An
42
+ * element that is `display: contents`, such as a row whose cells sit in the
43
+ * grid of its table, has each of its cells put where it is.
44
+ */
45
+ export declare const dragPreviewOf: (node: Node, placed: boolean, origin: Origin) => Node[];
46
+ export {};
@@ -0,0 +1,58 @@
1
+ import { html, nothing } from 'lit';
2
+ export const drawDragPreview = (on) => on ? html `<div part="drag-preview" popover="manual" aria-hidden="true" inert></div>` : nothing;
3
+ const drawn = (node) => {
4
+ const assigned = node instanceof HTMLSlotElement ? node.assignedNodes({ flatten: true }) : [];
5
+ return [...(assigned.length ? assigned : (node.shadowRoot ?? node).childNodes)];
6
+ };
7
+ export const boxOf = (element) => {
8
+ if (getComputedStyle(element).display !== 'contents') {
9
+ return element.getBoundingClientRect();
10
+ }
11
+ let [left, top, right, bottom] = [Infinity, Infinity, -Infinity, -Infinity];
12
+ for (const node of drawn(element)) {
13
+ const box = node instanceof Element && boxOf(node);
14
+ if (box && (box.width || box.height)) {
15
+ left = Math.min(left, box.left);
16
+ top = Math.min(top, box.top);
17
+ right = Math.max(right, box.right);
18
+ bottom = Math.max(bottom, box.bottom);
19
+ }
20
+ }
21
+ return left <= right ? new DOMRect(left, top, right - left, bottom - top) : new DOMRect();
22
+ };
23
+ export const dragPreviewOf = (node, placed, origin) => {
24
+ if (node instanceof Text) {
25
+ return [node.cloneNode()];
26
+ }
27
+ const style = node instanceof Element ? getComputedStyle(node) : undefined;
28
+ if (!style || style.display === 'none') {
29
+ return [];
30
+ }
31
+ const element = node;
32
+ const tag = element.localName;
33
+ const whole = /^(img|svg)$/.test(tag);
34
+ const copy = whole
35
+ ? element.cloneNode(true)
36
+ : document.createElement(/-|^slot$/.test(tag) ? 'div' : tag);
37
+ const contents = style.display === 'contents';
38
+ const box = element.getBoundingClientRect();
39
+ let written = '';
40
+ for (let index = 0; index < style.length; index += 1) {
41
+ const name = style.item(index);
42
+ if (!/^--|block|inline|start-|end-/.test(name)) {
43
+ written += `${name}:${style.getPropertyValue(name)};`;
44
+ }
45
+ }
46
+ copy.style.cssText =
47
+ placed && !contents
48
+ ? `${written}position:absolute;inset:auto;left:${box.left - origin.x}px;top:${box.top - origin.y}px;width:${box.width}px;height:${box.height}px;margin:0;box-sizing:border-box;grid-area:auto`
49
+ : written;
50
+ if (!whole) {
51
+ const inner = !contents && (placed || style.position !== 'static')
52
+ ? { x: box.left + element.clientLeft, y: box.top + element.clientTop }
53
+ : origin;
54
+ const grid = contents ? placed : style.display.endsWith('grid');
55
+ copy.append(...drawn(element).flatMap((child) => dragPreviewOf(child, grid, inner)));
56
+ }
57
+ return [copy];
58
+ };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The state and the arithmetic of moving one item to a new place in its
3
+ * collection: rows of a list or a data table, or a table's columns.
4
+ *
5
+ * Pure, so every transition and every sum has a spec with no DOM.
6
+ * `ReorderController` holds the state, listens to the pointer and the keys, and
7
+ * runs the effects each change of state calls for.
8
+ */
9
+ /** Which way a collection runs: rows down the block axis, columns along the inline one. */
10
+ export type ReorderAxis = 'block' | 'inline';
11
+ /** What holds the item: a key or a press on its grip, or a pointer dragging it. */
12
+ export type ReorderHold = 'keyboard' | 'pointer';
13
+ /**
14
+ * Where a move stands.
15
+ *
16
+ * - `idle`: nothing is held.
17
+ * - `pressed`: a pointer is down on a grip and has not traveled far enough to
18
+ * be a drag.
19
+ * - `held`: an item is held, and `to` is where it would go.
20
+ * - `dropped` and `canceled`: the move has ended, for one step, so the
21
+ * controller can run what each calls for before it settles to `idle`.
22
+ *
23
+ * `from` and `to` count from 0 in the order the items are drawn.
24
+ */
25
+ export type ReorderState = {
26
+ phase: 'idle';
27
+ } | {
28
+ from: number;
29
+ key: string;
30
+ phase: 'pressed';
31
+ pointer: number;
32
+ x: number;
33
+ y: number;
34
+ } | {
35
+ by: ReorderHold;
36
+ from: number;
37
+ key: string;
38
+ phase: 'held';
39
+ to: number;
40
+ } | {
41
+ by: ReorderHold;
42
+ from: number;
43
+ key: string;
44
+ phase: 'dropped';
45
+ to: number;
46
+ } | {
47
+ by: ReorderHold;
48
+ key: string;
49
+ phase: 'canceled';
50
+ };
51
+ /**
52
+ * What can happen to a move.
53
+ *
54
+ * - `press`: a pointer went down on a grip.
55
+ * - `drag`: the pointer moved, and `to` is the place under it.
56
+ * - `release`: the pointer went up.
57
+ * - `pick`: a grip was pressed with a key or a click. It holds the item, or
58
+ * drops the item that grip already holds.
59
+ * - `step`: a key moved the held item to `to`.
60
+ * - `target`: a click on another item dropped the held one at `to`.
61
+ * - `drop` and `cancel`: Enter or Space, and Escape.
62
+ * - `settle`: the controller has run the effects of a drop or a cancel.
63
+ */
64
+ export type ReorderInput = {
65
+ from: number;
66
+ key: string;
67
+ pointer: number;
68
+ type: 'press';
69
+ x: number;
70
+ y: number;
71
+ } | {
72
+ slop: number;
73
+ to: number;
74
+ type: 'drag';
75
+ x: number;
76
+ y: number;
77
+ } | {
78
+ type: 'release';
79
+ } | {
80
+ from: number;
81
+ key: string;
82
+ type: 'pick';
83
+ } | {
84
+ to: number;
85
+ type: 'step';
86
+ } | {
87
+ to: number;
88
+ type: 'target';
89
+ } | {
90
+ type: 'cancel';
91
+ } | {
92
+ type: 'drop';
93
+ } | {
94
+ type: 'settle';
95
+ };
96
+ /** The state with nothing held. */
97
+ export declare const IDLE: ReorderState;
98
+ /**
99
+ * The state after an input, or `null` where the input changes nothing.
100
+ *
101
+ * A press becomes a drag only once the pointer has traveled `slop` pixels, so a
102
+ * click on a grip stays a click. A click on a grip holds the item for the
103
+ * keys, and a second click on the same grip drops it, which is the move a
104
+ * single pointer makes without dragging.
105
+ */
106
+ export declare const reorderStep: (state: ReorderState, input: ReorderInput) => ReorderState | null;
107
+ /**
108
+ * How many of the other items the pointer has passed: the place the held item
109
+ * goes, among the others.
110
+ *
111
+ * `centers` are the middles of the other items along the axis, in the order
112
+ * drawn. `reversed` is a row laid out from the right, a right-to-left page,
113
+ * where passing an item means being left of its middle.
114
+ */
115
+ export declare const passedCount: (centers: readonly number[], at: number, reversed?: boolean) => number;
116
+ /**
117
+ * The middles of the items along the axis, from their boxes, for
118
+ * `passedCount`.
119
+ *
120
+ * An item with no box is one a window did not draw. One before every drawn
121
+ * item counts as passed, one after them as not passed, and one between two
122
+ * drawn items as where the drawn one before it is. `reversed` is the same
123
+ * question on a row laid out from the right.
124
+ */
125
+ export declare const centersOf: (spans: readonly ({
126
+ end: number;
127
+ start: number;
128
+ } | undefined)[], reversed?: boolean) => number[];
129
+ /**
130
+ * Where a key moves a held item, or `null` for a key that does not move one.
131
+ *
132
+ * The arrows along the axis move it one place, and Home and End move it to
133
+ * either end. Along the inline axis the arrows follow the writing direction,
134
+ * so on a right-to-left page the left arrow moves it forward. The answer is
135
+ * held to the collection, so an arrow at an end returns where it already is.
136
+ */
137
+ export declare const reorderKey: (key: string, { axis, count, rtl, to }: {
138
+ axis: ReorderAxis;
139
+ count: number;
140
+ rtl: boolean;
141
+ to: number;
142
+ }) => number | null;
143
+ /**
144
+ * Where the drop indicator goes for a held item: before or after the item at
145
+ * an index of the order drawn, or nowhere while the item would stay where it
146
+ * is.
147
+ *
148
+ * Moving back, the item lands before the one now at `to`. Moving forward, it
149
+ * lands after it.
150
+ */
151
+ export declare const dropMark: (from: number, to: number) => {
152
+ edge: 'after' | 'before';
153
+ index: number;
154
+ } | undefined;
155
+ /**
156
+ * Every key in a new order: `key` taken out and put back at `to` among the
157
+ * shown ones.
158
+ *
159
+ * `all` is the whole order, which can hold keys that are not drawn: a hidden
160
+ * column, or a row a search left out. `shown` is the order drawn. The key goes
161
+ * after the shown one it now follows, or before the first shown one where it
162
+ * moves to the front, so a key that is not drawn keeps its place among the
163
+ * rest.
164
+ */
165
+ export declare const placeKey: (all: readonly string[], shown: readonly string[], key: string, to: number) => string[];
166
+ /**
167
+ * How far to scroll a box in one frame while a held item is near one of its
168
+ * edges, in pixels: negative toward the start, positive toward the end, and 0
169
+ * away from both.
170
+ *
171
+ * `at` is the pointer along the axis, and `start` and `end` are the box's
172
+ * edges. The speed rises from nothing at `zone` pixels in to `most` at the
173
+ * edge and past it.
174
+ */
175
+ export declare const edgeSpeed: (at: number, start: number, end: number, { most, zone }?: {
176
+ most?: number;
177
+ zone?: number;
178
+ }) => number;