@limetech/lime-elements 40.12.2 → 40.13.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/{checkbox.template-_WUgtMJy.js → checkbox.template-CNJ29zDR.js} +1 -1
  3. package/dist/cjs/drag-to-reorder-BP5YkPuu.js +3092 -0
  4. package/dist/cjs/lime-elements.cjs.js +1 -1
  5. package/dist/cjs/limel-checkbox.cjs.entry.js +1 -1
  6. package/dist/cjs/limel-chip-set.cjs.entry.js +5 -2
  7. package/dist/cjs/{limel-scroller.cjs.entry.js → limel-drag-handle_2.cjs.entry.js} +38 -0
  8. package/dist/cjs/limel-file-viewer.cjs.entry.js +64 -55
  9. package/dist/cjs/limel-form.cjs.entry.js +539 -3171
  10. package/dist/cjs/limel-list-item.cjs.entry.js +1 -1
  11. package/dist/cjs/limel-select.cjs.entry.js +2 -2
  12. package/dist/cjs/limel-slider.cjs.entry.js +4 -4
  13. package/dist/cjs/limel-switch.cjs.entry.js +1 -1
  14. package/dist/cjs/limel-tab-bar.cjs.entry.js +120 -16
  15. package/dist/cjs/limel-tab-panel.cjs.entry.js +7 -1
  16. package/dist/cjs/loader.cjs.js +1 -1
  17. package/dist/collection/components/checkbox/checkbox.template.js +1 -1
  18. package/dist/collection/components/chip-set/chip-set.js +5 -2
  19. package/dist/collection/components/form/form.css +59 -0
  20. package/dist/collection/components/form/form.js +49 -2
  21. package/dist/collection/components/form/show-field.js +116 -0
  22. package/dist/collection/components/form/templates/array-field-collapsible-item.js +1 -0
  23. package/dist/collection/components/form/templates/array-field-item.js +5 -0
  24. package/dist/collection/components/form/templates/array-field.js +20 -158
  25. package/dist/collection/components/form/templates/field.js +17 -0
  26. package/dist/collection/components/select/select.template.js +2 -2
  27. package/dist/collection/components/slider/slider.js +4 -4
  28. package/dist/collection/components/switch/switch.js +1 -1
  29. package/dist/collection/components/tab-bar/tab-bar.css +68 -22
  30. package/dist/collection/components/tab-bar/tab-bar.js +131 -4
  31. package/dist/collection/components/tab-bar/tabs.js +23 -0
  32. package/dist/collection/components/tab-panel/tab-panel.js +54 -1
  33. package/dist/collection/util/drag-to-reorder.js +116 -0
  34. package/dist/esm/{_baseForOwn-D5j7fHlv.js → _baseForOwn-vKFWmmaS.js} +1 -1
  35. package/dist/esm/{_baseIsEqual-CyF4P8uM.js → _baseIsEqual-B_JRErB0.js} +1 -1
  36. package/dist/esm/{_baseIteratee-CpbczHY1.js → _baseIteratee-CVz7aes3.js} +1 -1
  37. package/dist/esm/{_baseSet-Cw2IE6ZF.js → _baseSet-BUAq8NGm.js} +1 -1
  38. package/dist/esm/{checkbox.template-BFL4SMzl.js → checkbox.template-CAja2LKZ.js} +1 -1
  39. package/dist/esm/{cloneDeep-DAbOtbs8.js → cloneDeep-Bcll-3f8.js} +1 -1
  40. package/dist/esm/drag-to-reorder-BXmXIwCa.js +3086 -0
  41. package/dist/esm/{isEqual-CE83yS8T.js → isEqual-C-pK6AAU.js} +1 -1
  42. package/dist/esm/lime-elements.js +1 -1
  43. package/dist/esm/limel-checkbox.entry.js +1 -1
  44. package/dist/esm/limel-chip-set.entry.js +7 -4
  45. package/dist/esm/limel-dialog.entry.js +2 -2
  46. package/dist/esm/{limel-scroller.entry.js → limel-drag-handle_2.entry.js} +38 -1
  47. package/dist/esm/limel-file-dropzone_2.entry.js +3 -3
  48. package/dist/esm/limel-file-viewer.entry.js +64 -55
  49. package/dist/esm/limel-form.entry.js +536 -3168
  50. package/dist/esm/limel-list-item.entry.js +1 -1
  51. package/dist/esm/limel-prosemirror-adapter.entry.js +3 -3
  52. package/dist/esm/limel-select.entry.js +2 -2
  53. package/dist/esm/limel-slider.entry.js +4 -4
  54. package/dist/esm/limel-switch.entry.js +1 -1
  55. package/dist/esm/limel-tab-bar.entry.js +117 -13
  56. package/dist/esm/limel-tab-panel.entry.js +7 -1
  57. package/dist/esm/limel-table.entry.js +4 -4
  58. package/dist/esm/loader.js +1 -1
  59. package/dist/lime-elements/lime-elements.esm.js +1 -1
  60. package/dist/lime-elements/{p-35bc5ed2.entry.js → p-13f2afc9.entry.js} +1 -1
  61. package/dist/lime-elements/{p-43c36a02.entry.js → p-268b4da1.entry.js} +1 -1
  62. package/dist/lime-elements/p-3b6d4934.entry.js +1 -0
  63. package/dist/lime-elements/p-55b56742.entry.js +1 -0
  64. package/dist/lime-elements/p-67767276.entry.js +1 -0
  65. package/dist/lime-elements/{p-fb15f608.entry.js → p-6d65ee35.entry.js} +1 -1
  66. package/dist/lime-elements/p-714ab66d.entry.js +16 -0
  67. package/dist/lime-elements/p-7ae71c13.entry.js +1 -0
  68. package/dist/lime-elements/{p-c492af1b.entry.js → p-828558cf.entry.js} +1 -1
  69. package/dist/lime-elements/p-95322502.entry.js +1 -0
  70. package/dist/lime-elements/p-BAqAt5It.js +1 -0
  71. package/dist/lime-elements/{p-1qqvOwzC.js → p-Cadm4-WZ.js} +1 -1
  72. package/dist/lime-elements/p-Cu9Ji1iJ.js +1 -0
  73. package/dist/lime-elements/{p-BY5NjPj8.js → p-D9Ezd43A.js} +1 -1
  74. package/dist/lime-elements/{p-nVemxylR.js → p-DFu1q54T.js} +1 -1
  75. package/dist/lime-elements/p-DdnBHYb-.js +7 -0
  76. package/dist/lime-elements/{p-B6-3WXAv.js → p-DeIIlp6m.js} +1 -1
  77. package/dist/lime-elements/p-Dq0uNStc.js +1 -0
  78. package/dist/lime-elements/p-b5bb124f.entry.js +1 -0
  79. package/dist/lime-elements/{p-9168793f.entry.js → p-bd58c761.entry.js} +1 -1
  80. package/dist/lime-elements/{p-458c131f.entry.js → p-c7e34502.entry.js} +1 -1
  81. package/dist/lime-elements/p-f4c9940e.entry.js +23 -0
  82. package/dist/lime-elements/p-fb526a05.entry.js +1 -0
  83. package/dist/types/components/chip-set/chip-set.d.ts +1 -0
  84. package/dist/types/components/form/form.d.ts +15 -0
  85. package/dist/types/components/form/show-field.d.ts +9 -0
  86. package/dist/types/components/form/templates/array-context.d.ts +4 -0
  87. package/dist/types/components/form/templates/array-field-collapsible-item.d.ts +5 -0
  88. package/dist/types/components/form/templates/array-field.d.ts +3 -13
  89. package/dist/types/components/form/templates/field.d.ts +1 -0
  90. package/dist/types/components/form/templates/object-field.d.ts +2 -2
  91. package/dist/types/components/tab-bar/tab-bar.d.ts +25 -0
  92. package/dist/types/components/tab-bar/tab.types.d.ts +18 -0
  93. package/dist/types/components/tab-bar/tabs.d.ts +12 -1
  94. package/dist/types/components/tab-panel/tab-panel.d.ts +18 -1
  95. package/dist/types/components.d.ts +80 -2
  96. package/dist/types/util/drag-to-reorder.d.ts +65 -0
  97. package/package.json +4 -4
  98. package/dist/cjs/isArrayLikeObject-U2Ddbw4n.js +0 -326
  99. package/dist/cjs/limel-drag-handle.cjs.entry.js +0 -42
  100. package/dist/esm/isArrayLikeObject-CJB8HbtH.js +0 -321
  101. package/dist/esm/limel-drag-handle.entry.js +0 -40
  102. package/dist/lime-elements/p-06850418.entry.js +0 -1
  103. package/dist/lime-elements/p-262a3d66.entry.js +0 -1
  104. package/dist/lime-elements/p-49eb2f64.entry.js +0 -13
  105. package/dist/lime-elements/p-646731b6.entry.js +0 -1
  106. package/dist/lime-elements/p-89d75e9c.entry.js +0 -1
  107. package/dist/lime-elements/p-8d567e42.entry.js +0 -1
  108. package/dist/lime-elements/p-92fab922.entry.js +0 -23
  109. package/dist/lime-elements/p-BQeIv4aA.js +0 -1
  110. package/dist/lime-elements/p-COdirXg1.js +0 -1
  111. package/dist/lime-elements/p-ORUNaKm-.js +0 -1
  112. package/dist/lime-elements/p-a5246373.entry.js +0 -1
  113. package/dist/lime-elements/p-b609552e.entry.js +0 -1
  114. package/dist/lime-elements/p-bf849dc9.entry.js +0 -1
  115. package/dist/lime-elements/p-pk5KIdKH.js +0 -1
@@ -434,4 +434,63 @@ limel-markdown {
434
434
  }
435
435
  .rjsf-field .rjsf-field .rjsf-field .rjsf-field .rjsf-field .rjsf-field h1:before {
436
436
  display: none;
437
+ }
438
+
439
+ .limel-form-field--highlighted {
440
+ position: relative;
441
+ }
442
+ .limel-form-field--highlighted::after {
443
+ content: "";
444
+ position: absolute;
445
+ inset: -0.25rem;
446
+ border-radius: 0.5rem;
447
+ pointer-events: none;
448
+ animation-name: highlight-field-background, highlight-field-shadow;
449
+ animation-duration: 6s;
450
+ animation-delay: 0.1s, 0.3s;
451
+ animation-timing-function: ease-in-out;
452
+ }
453
+ @media (prefers-reduced-motion) {
454
+ .limel-form-field--highlighted::after {
455
+ animation-duration: 4s;
456
+ animation-delay: 0s;
457
+ }
458
+ }
459
+
460
+ @keyframes highlight-field-background {
461
+ 0% {
462
+ background-color: transparent;
463
+ }
464
+ 12.5% {
465
+ background-color: color-mix(in srgb, var(--lime-primary-color, var(--limel-theme-primary-color)), transparent 65%);
466
+ }
467
+ 45%, 100% {
468
+ background-color: transparent;
469
+ }
470
+ }
471
+ @keyframes highlight-field-shadow {
472
+ 0%, 25%, 50% {
473
+ box-shadow: none;
474
+ }
475
+ 12.5%, 37.5% {
476
+ box-shadow: var(--shadow-depth-8-focused);
477
+ }
478
+ 62.5%, 72.5% {
479
+ box-shadow: var(--shadow-depth-8-focused);
480
+ animation-timing-function: ease-out;
481
+ }
482
+ 100% {
483
+ box-shadow: none;
484
+ }
485
+ }
486
+ @media (prefers-reduced-motion) {
487
+ @keyframes highlight-field-shadow {
488
+ 0%, 25% {
489
+ box-shadow: var(--shadow-depth-8-focused);
490
+ animation-timing-function: ease-out;
491
+ }
492
+ 100% {
493
+ box-shadow: none;
494
+ }
495
+ }
437
496
  }
@@ -20,7 +20,8 @@ import { ArrayField as CustomArrayField } from "./fields/array-field";
20
20
  import { ObjectField as CustomObjectField } from "./fields/object-field";
21
21
  import { widgets } from "./widgets";
22
22
  import { rjsfValidator } from "./validator";
23
- import { mapValues } from "lodash-es";
23
+ import { isEqual, mapValues } from "lodash-es";
24
+ import { showField } from "./show-field";
24
25
  /**
25
26
  * @exampleComponent limel-example-form
26
27
  * @exampleComponent limel-example-nested-form
@@ -38,6 +39,7 @@ import { mapValues } from "lodash-es";
38
39
  * @exampleComponent limel-example-form-row-layout
39
40
  * @exampleComponent limel-example-builtin-field-types-form
40
41
  * @exampleComponent limel-example-code-editor-form
42
+ * @exampleComponent limel-example-form-highlighted-field
41
43
  */
42
44
  export class Form {
43
45
  constructor() {
@@ -66,6 +68,13 @@ export class Form {
66
68
  }
67
69
  componentDidLoad() {
68
70
  this.initialize();
71
+ this.showHighlightedField();
72
+ }
73
+ handleHighlightedFieldChange(newPath, oldPath) {
74
+ // A host that renders again passes an equal path in a new array.
75
+ if (!isEqual(newPath, oldPath)) {
76
+ this.showHighlightedField();
77
+ }
69
78
  }
70
79
  initialize() {
71
80
  if (this.initialized || !this.host.shadowRoot.querySelector('.root')) {
@@ -87,7 +96,7 @@ export class Form {
87
96
  this.initialized = false;
88
97
  }
89
98
  render() {
90
- return h("div", { key: '9c5db05ffcc594473a3ca7b2e3962f1659d3601d', class: "root" });
99
+ return h("div", { key: 'ffbfb1d5d0377c01a29740b7a08fdfb3532e470f', class: "root" });
91
100
  }
92
101
  reactRender() {
93
102
  if (!this.root) {
@@ -134,6 +143,13 @@ export class Form {
134
143
  },
135
144
  }, []));
136
145
  }
146
+ showHighlightedField() {
147
+ if (!this.highlightedField) {
148
+ return;
149
+ }
150
+ const root = this.host.shadowRoot.querySelector('.root');
151
+ void showField(root, this.highlightedField);
152
+ }
137
153
  handleChange(event) {
138
154
  this.change.emit(event.formData);
139
155
  }
@@ -386,6 +402,31 @@ export class Form {
386
402
  "reflect": false,
387
403
  "attribute": "reveal-errors",
388
404
  "defaultValue": "false"
405
+ },
406
+ "highlightedField": {
407
+ "type": "unknown",
408
+ "mutable": false,
409
+ "complexType": {
410
+ "original": "Array<string | number>",
411
+ "resolved": "(string | number)[]",
412
+ "references": {
413
+ "Array": {
414
+ "location": "global",
415
+ "id": "global::Array"
416
+ }
417
+ }
418
+ },
419
+ "required": false,
420
+ "optional": true,
421
+ "docs": {
422
+ "tags": [{
423
+ "name": "beta",
424
+ "text": undefined
425
+ }],
426
+ "text": "The path in the value of a field to take the user to, such as\n`['heroes', 2, 'name']`. When it changes, the form opens the sections\nand list items the field is in, scrolls it into view, focuses it and\nbriefly highlights it.\n\nIf the path goes further than the form can show, such as into a custom\ncomponent, the closest field above the end of the path is shown. To show\nthe same field again, set it to `undefined` first."
427
+ },
428
+ "getter": false,
429
+ "setter": false
389
430
  }
390
431
  };
391
432
  }
@@ -430,4 +471,10 @@ export class Form {
430
471
  }];
431
472
  }
432
473
  static get elementRef() { return "host"; }
474
+ static get watchers() {
475
+ return [{
476
+ "propName": "highlightedField",
477
+ "methodName": "handleHighlightedFieldChange"
478
+ }];
479
+ }
433
480
  }
@@ -0,0 +1,116 @@
1
+ import { flushSync } from "react-dom";
2
+ import { toSchemaPath } from "./templates/object-field";
3
+ import { getFocusTarget } from "../../util/get-focus-target";
4
+ const HIGHLIGHT_CLASS = 'limel-form-field--highlighted';
5
+ const STABLE_FRAMES = 3;
6
+ const MAX_SETTLE_MS = 2000;
7
+ const MAX_RENDER_FRAMES = 30;
8
+ /**
9
+ * Open what is closed above a field, scroll it into view, focus it and
10
+ * highlight it.
11
+ *
12
+ * @param root - the element the form is rendered in
13
+ * @param path - the path of the field in the value of the form
14
+ */
15
+ export async function showField(root, path) {
16
+ if (!(await waitUntilRendered(root, path))) {
17
+ return;
18
+ }
19
+ let target;
20
+ for (let depth = 1; depth <= path.length; depth++) {
21
+ const element = findByPath(root, path.slice(0, depth));
22
+ if (!element) {
23
+ break;
24
+ }
25
+ target = element;
26
+ openSectionsAround(root, element);
27
+ }
28
+ if (!target) {
29
+ return;
30
+ }
31
+ // The components inside the field have to be drawn, and the sections
32
+ // around it opened, before the field is where it will stay.
33
+ const focusTarget = await getFocusTarget(target);
34
+ await waitForLayoutToSettle(target);
35
+ target.scrollIntoView({
36
+ block: 'center',
37
+ behavior: prefersReducedMotion() ? 'instant' : 'smooth',
38
+ });
39
+ focusTarget === null || focusTarget === void 0 ? void 0 : focusTarget.focus({ preventScroll: true });
40
+ await waitForLayoutToSettle(target);
41
+ highlight(target);
42
+ }
43
+ function findByPath(root, path) {
44
+ const schemaPath = toSchemaPath(path.map(String));
45
+ return root.querySelector(`[data-schema-path="${CSS.escape(schemaPath)}"]`);
46
+ }
47
+ // The form is drawn a moment after its value or schema is set, so give it a few
48
+ // frames to show the start of the path.
49
+ async function waitUntilRendered(root, path) {
50
+ for (let frame = 0; frame < MAX_RENDER_FRAMES; frame++) {
51
+ if (findByPath(root, path.slice(0, 1))) {
52
+ return true;
53
+ }
54
+ await nextFrame();
55
+ }
56
+ return false;
57
+ }
58
+ function openSectionsAround(root, element) {
59
+ for (let node = element; node && node !== root; node = node.parentElement) {
60
+ if (isClosedSection(node)) {
61
+ open(node);
62
+ }
63
+ }
64
+ }
65
+ function isClosedSection(element) {
66
+ return (element.tagName === 'LIMEL-COLLAPSIBLE-SECTION' &&
67
+ !element.isOpen);
68
+ }
69
+ // A collapsed list item renders its fields only once its section says it has
70
+ // been opened, and they have to be in place before the next step is looked up.
71
+ function open(section) {
72
+ section.isOpen = true;
73
+ flushSync(() => {
74
+ section.dispatchEvent(new CustomEvent('open'));
75
+ });
76
+ }
77
+ // Wait until the field has a size and has stayed in place for a few frames, so
78
+ // it is not scrolled to while the form still grows, nor flashed while scrolling.
79
+ async function waitForLayoutToSettle(element) {
80
+ let previous = '';
81
+ let stableFrames = 0;
82
+ const deadline = performance.now() + MAX_SETTLE_MS;
83
+ while (performance.now() < deadline) {
84
+ await nextFrame();
85
+ const { top, height } = element.getBoundingClientRect();
86
+ const current = `${top}:${height}`;
87
+ const isStill = height > 0 && current === previous;
88
+ stableFrames = isStill ? stableFrames + 1 : 0;
89
+ if (stableFrames === STABLE_FRAMES) {
90
+ return;
91
+ }
92
+ previous = current;
93
+ }
94
+ }
95
+ function highlight(element) {
96
+ // Removed for a frame first, so showing the same field again restarts it.
97
+ element.classList.remove(HIGHLIGHT_CLASS);
98
+ const removeHighlight = (event) => {
99
+ // Animations of what the field holds end here too.
100
+ if (event.animationName !== 'highlight-field-shadow') {
101
+ return;
102
+ }
103
+ element.classList.remove(HIGHLIGHT_CLASS);
104
+ element.removeEventListener('animationend', removeHighlight);
105
+ };
106
+ requestAnimationFrame(() => {
107
+ element.classList.add(HIGHLIGHT_CLASS);
108
+ element.addEventListener('animationend', removeHighlight);
109
+ });
110
+ }
111
+ function prefersReducedMotion() {
112
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
113
+ }
114
+ function nextFrame() {
115
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
116
+ }
@@ -54,6 +54,7 @@ export class CollapsibleItemTemplate extends React.Component {
54
54
  this.section = section;
55
55
  },
56
56
  'is-open': this.state.isOpen,
57
+ 'data-schema-path': this.props.schemaPath,
57
58
  'data-reorder-id': String(this.props.index),
58
59
  'data-reorderable': this.props.allowItemReorder
59
60
  ? 'true'
@@ -3,6 +3,7 @@ import { isObjectType } from "../schema";
3
3
  import { CollapsibleItemTemplate } from "./array-field-collapsible-item";
4
4
  import { SimpleItemTemplate } from "./array-field-simple-item";
5
5
  import { ArrayFieldContext, ArrayItemErrorsContext } from "./array-context";
6
+ import { toSchemaPath } from "./object-field";
6
7
  export const ArrayFieldItemTemplate = (props) => {
7
8
  const arrayContext = useContext(ArrayFieldContext);
8
9
  const itemErrors = useContext(ArrayItemErrorsContext);
@@ -26,6 +27,10 @@ export const ArrayFieldItemTemplate = (props) => {
26
27
  allowItemReorder: allowItemReorder,
27
28
  errorSchema: itemErrors === null || itemErrors === void 0 ? void 0 : itemErrors[props.index],
28
29
  revealErrors: (formContext === null || formContext === void 0 ? void 0 : formContext.revealErrors) === true,
30
+ schemaPath: toSchemaPath([
31
+ ...arrayContext.schemaPath,
32
+ String(props.index),
33
+ ]),
29
34
  }, props.children);
30
35
  }
31
36
  return React.createElement(SimpleItemTemplate, {
@@ -1,14 +1,10 @@
1
1
  import React from "react";
2
2
  import { renderDescription, renderTitle } from "./common";
3
- import Sortable from "sortablejs";
3
+ import { toSchemaPath } from "./object-field";
4
+ import { DragToReorder } from "../../../util/drag-to-reorder";
4
5
  import { ArrayFieldContext } from "./array-context";
5
6
  export const ARRAY_REORDER_EVENT = 'arrayReorder';
6
- const DRAG_HANDLE_SELECTOR = '[data-drag-handle]';
7
7
  const DRAGGABLE_ITEM_SELECTOR = '.array-item[data-reorderable="true"]';
8
- const DEFAULT_CONTAINER_CLASS = 'has-an-item-which-is-being-dragged';
9
- const DEFAULT_DROP_ELEVATION_CLASS = 'is-elevated';
10
- const DROP_ELEVATION_DURATION = 1000;
11
- const TOUCH_DRAG_DELAY_MS = 200; // Adds a short hold on touch (long-press) so scroll gestures do not reorder items.
12
8
  export class ArrayFieldTemplate extends React.Component {
13
9
  constructor(props) {
14
10
  super(props);
@@ -21,68 +17,28 @@ export class ArrayFieldTemplate extends React.Component {
21
17
  if (this.container === element) {
22
18
  return;
23
19
  }
24
- this.teardownDragController();
20
+ this.dragToReorder.teardown();
25
21
  this.container = element !== null && element !== void 0 ? element : undefined;
26
22
  this.setupDragController();
27
23
  };
28
- this.handleSortStart = (event) => {
29
- if (!this.canReorderItems()) {
30
- return;
31
- }
32
- this.dragSnapshot = [...this.state.order];
33
- this.draggedItemIndex = this.getReorderId(event.item);
34
- if (this.container) {
35
- this.container.classList.add(DEFAULT_CONTAINER_CLASS);
36
- }
37
- if (event.item instanceof HTMLElement) {
38
- this.applyDropElevation(event.item);
39
- }
40
- };
41
- this.handleSortEnd = (event) => {
24
+ this.handleReorder = ({ order, fromIndex, toIndex, }) => {
42
25
  var _a;
43
- if (!this.canReorderItems()) {
44
- return;
45
- }
46
- if (this.container) {
47
- this.container.classList.remove(DEFAULT_CONTAINER_CLASS);
48
- }
49
- const snapshot = this.dragSnapshot;
50
- const draggedItemIndex = this.draggedItemIndex;
51
- this.dragSnapshot = undefined;
52
- this.draggedItemIndex = undefined;
53
- if (event.item instanceof HTMLElement) {
54
- this.dropElevationTarget = event.item;
55
- }
56
- const finalOrder = this.readOrderFromDom();
57
- if (!this.arraysEqual(this.state.order, finalOrder)) {
58
- this.setState({ order: finalOrder });
59
- }
60
- if (snapshot === undefined ||
61
- draggedItemIndex === undefined ||
62
- !(event.item instanceof HTMLElement)) {
63
- this.scheduleDropElevationRemoval();
64
- return;
65
- }
66
- const fromPosition = snapshot.indexOf(draggedItemIndex);
67
- const toPosition = finalOrder.indexOf(draggedItemIndex);
68
- if (fromPosition === -1 ||
69
- toPosition === -1 ||
70
- fromPosition === toPosition) {
71
- this.scheduleDropElevationRemoval();
72
- return;
73
- }
26
+ this.setState({ order: order.map(Number) });
74
27
  (_a = this.container) === null || _a === void 0 ? void 0 : _a.dispatchEvent(new CustomEvent(ARRAY_REORDER_EVENT, {
75
28
  bubbles: true,
76
- detail: { fromIndex: fromPosition, toIndex: toPosition },
29
+ detail: { fromIndex, toIndex },
77
30
  }));
78
- this.scheduleDropElevationRemoval();
79
31
  };
80
32
  this.state = {
81
33
  order: this.extractIndices(props.items),
82
34
  };
35
+ this.dragToReorder = new DragToReorder({
36
+ draggable: DRAGGABLE_ITEM_SELECTOR,
37
+ onReorder: this.handleReorder,
38
+ });
83
39
  }
84
40
  componentWillUnmount() {
85
- this.teardownDragController();
41
+ this.dragToReorder.teardown();
86
42
  }
87
43
  componentDidMount() {
88
44
  this.setupDragController();
@@ -151,47 +107,10 @@ export class ArrayFieldTemplate extends React.Component {
151
107
  }
152
108
  setupDragController() {
153
109
  if (!this.container || !this.canReorderItems()) {
154
- this.teardownDragController();
155
- return;
156
- }
157
- const reorderableItems = this.container.querySelectorAll(`:scope > ${DRAGGABLE_ITEM_SELECTOR}`);
158
- if (reorderableItems.length < 2) {
159
- this.teardownDragController();
110
+ this.dragToReorder.teardown();
160
111
  return;
161
112
  }
162
- if (this.sortable) {
163
- this.sortable.option('handle', DRAG_HANDLE_SELECTOR);
164
- this.sortable.option('draggable', DRAGGABLE_ITEM_SELECTOR);
165
- this.sortable.option('disabled', false);
166
- this.sortable.option('delay', TOUCH_DRAG_DELAY_MS);
167
- this.sortable.option('delayOnTouchOnly', true);
168
- return;
169
- }
170
- this.sortable = Sortable.create(this.container, {
171
- animation: 150,
172
- handle: DRAG_HANDLE_SELECTOR,
173
- draggable: DRAGGABLE_ITEM_SELECTOR,
174
- delay: TOUCH_DRAG_DELAY_MS,
175
- delayOnTouchOnly: true,
176
- onStart: this.handleSortStart,
177
- onEnd: this.handleSortEnd,
178
- });
179
- }
180
- teardownDragController() {
181
- if (this.sortable) {
182
- this.sortable.destroy();
183
- this.sortable = undefined;
184
- }
185
- this.clearDropElevationTimer();
186
- if (this.dropElevationTarget) {
187
- this.dropElevationTarget.classList.remove(DEFAULT_DROP_ELEVATION_CLASS);
188
- this.dropElevationTarget = undefined;
189
- }
190
- if (this.container) {
191
- this.container.classList.remove(DEFAULT_CONTAINER_CLASS);
192
- }
193
- this.dragSnapshot = undefined;
194
- this.draggedItemIndex = undefined;
113
+ this.dragToReorder.setup(this.container);
195
114
  }
196
115
  canReorderItems() {
197
116
  if (this.props.disabled || this.props.readonly) {
@@ -201,67 +120,6 @@ export class ArrayFieldTemplate extends React.Component {
201
120
  const limeOptions = (schema === null || schema === void 0 ? void 0 : schema.lime) || {};
202
121
  return limeOptions.allowItemReorder !== false;
203
122
  }
204
- readOrderFromDom() {
205
- if (!this.container) {
206
- return [];
207
- }
208
- // Only read the order of the direct children of this array field.
209
- // Nested array fields may render `.array-item` elements inside items,
210
- // and those must not affect the parent order.
211
- const items = [...this.container.children].filter((element) => {
212
- var _a, _b;
213
- return Boolean((_b = (_a = element === null || element === void 0 ? void 0 : element.classList) === null || _a === void 0 ? void 0 : _a.contains) === null || _b === void 0 ? void 0 : _b.call(_a, 'array-item'));
214
- });
215
- const order = [];
216
- for (const element of items) {
217
- const index = this.getReorderId(element);
218
- if (index !== undefined) {
219
- order.push(index);
220
- }
221
- }
222
- return order;
223
- }
224
- getReorderId(element) {
225
- var _a;
226
- if (!element) {
227
- return undefined;
228
- }
229
- const value = (_a = element === null || element === void 0 ? void 0 : element.dataset) === null || _a === void 0 ? void 0 : _a.reorderId;
230
- if (value === undefined) {
231
- return undefined;
232
- }
233
- const parsed = Number.parseInt(value, 10);
234
- return Number.isNaN(parsed) ? undefined : parsed;
235
- }
236
- applyDropElevation(item) {
237
- if (this.dropElevationTarget && this.dropElevationTarget !== item) {
238
- this.dropElevationTarget.classList.remove(DEFAULT_DROP_ELEVATION_CLASS);
239
- }
240
- this.clearDropElevationTimer();
241
- item.classList.add(DEFAULT_DROP_ELEVATION_CLASS);
242
- this.dropElevationTarget = item;
243
- }
244
- scheduleDropElevationRemoval() {
245
- if (!this.dropElevationTarget) {
246
- return;
247
- }
248
- const target = this.dropElevationTarget;
249
- this.clearDropElevationTimer();
250
- this.dropElevationTimeout = setTimeout(() => {
251
- target.classList.remove(DEFAULT_DROP_ELEVATION_CLASS);
252
- if (this.dropElevationTarget === target) {
253
- this.dropElevationTarget = undefined;
254
- }
255
- this.dropElevationTimeout = undefined;
256
- }, DROP_ELEVATION_DURATION);
257
- }
258
- clearDropElevationTimer() {
259
- if (this.dropElevationTimeout === undefined) {
260
- return;
261
- }
262
- clearTimeout(this.dropElevationTimeout);
263
- this.dropElevationTimeout = undefined;
264
- }
265
123
  arraysEqual(a, b) {
266
124
  if (a.length !== b.length) {
267
125
  return false;
@@ -279,17 +137,21 @@ export class ArrayFieldTemplate extends React.Component {
279
137
  return (items !== null && items !== void 0 ? items : []).map((_, index) => index);
280
138
  }
281
139
  getContextValue() {
282
- var _a;
140
+ var _a, _b, _c;
283
141
  const schema = this.props.schema;
284
142
  const formData = ((_a = this.props.formData) !== null && _a !== void 0 ? _a : []);
143
+ const schemaPath = ((_c = (_b = this.props.fieldPathId) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : []).map(String);
144
+ const path = toSchemaPath(schemaPath);
285
145
  if (this.cachedContextValue &&
286
146
  this.cachedSchema === schema &&
287
- this.cachedFormData === formData) {
147
+ this.cachedFormData === formData &&
148
+ this.cachedPath === path) {
288
149
  return this.cachedContextValue;
289
150
  }
290
151
  this.cachedSchema = schema;
291
152
  this.cachedFormData = formData;
292
- this.cachedContextValue = { arraySchema: schema, formData };
153
+ this.cachedPath = path;
154
+ this.cachedContextValue = { arraySchema: schema, formData, schemaPath };
293
155
  return this.cachedContextValue;
294
156
  }
295
157
  }
@@ -1,5 +1,7 @@
1
1
  import React from "react";
2
2
  import { isObjectType } from "../schema";
3
+ import { toSchemaPath } from "./object-field";
4
+ import { isCustomObjectSchema } from "../fields/field-helpers";
3
5
  export const FieldTemplate = (props) => {
4
6
  let classNames = props.classNames;
5
7
  const colSpan = getColSpan(props.schema);
@@ -17,8 +19,23 @@ export const FieldTemplate = (props) => {
17
19
  return React.createElement('div', {
18
20
  className: classNames,
19
21
  style: style,
22
+ 'data-schema-path': getFieldSchemaPath(props),
20
23
  }, props.children);
21
24
  };
25
+ // An object marks its own layout wrapper, unless a component or the code
26
+ // editor draws it instead. It lets a host find the element of one field by its path.
27
+ function getFieldSchemaPath(props) {
28
+ var _a;
29
+ if (isObjectType(props.schema) && hasOwnLayout(props.schema)) {
30
+ return undefined;
31
+ }
32
+ const path = (_a = props.fieldPathId) === null || _a === void 0 ? void 0 : _a.path;
33
+ return path ? toSchemaPath(path.map(String)) : undefined;
34
+ }
35
+ function hasOwnLayout(schema) {
36
+ var _a, _b;
37
+ return !((_b = (_a = schema.lime) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.name) && !isCustomObjectSchema(schema);
38
+ }
22
39
  function getColSpan(schema) {
23
40
  var _a;
24
41
  const layout = (_a = schema.lime) === null || _a === void 0 ? void 0 : _a.layout;
@@ -35,7 +35,7 @@ export const SelectTemplate = (props) => {
35
35
  };
36
36
  return [
37
37
  h("div", { class: fieldClassList }, h("limel-notched-outline", { class: classList, labelId: TRIGGER_ID, labelElementId: LABEL_ID, label: props.label, required: props.required, invalid: !isValid, disabled: props.disabled, readonly: props.readonly, hasValue: hasValue, hasFloatingLabel: floatLabelAbove(props) }, h(SelectValue, Object.assign({}, props, { hasValue: hasValue, isValid: isValid, hasEmptyText: hasEmptyText }))), h(HelperText, { text: props.helperText, isValid: isValid })),
38
- h(SelectDropdown, Object.assign({}, props)),
38
+ h(SelectDropdown, Object.assign({}, props, { isValid: isValid })),
39
39
  ];
40
40
  };
41
41
  const getDescribedBy = (props) => {
@@ -97,7 +97,7 @@ const NativeDropdown = (props) => {
97
97
  const options = props.options
98
98
  .filter((option) => !('separator' in option))
99
99
  .map(renderOption(props.value));
100
- return (h("select", { required: props.required, "aria-labelledby": props.label ? LABEL_ID : undefined, "aria-describedby": getDescribedBy(props), "aria-disabled": props.disabled, "aria-required": props.required, onChange: props.onNativeChange, onFocus: props.open, onBlur: props.close, class: "limel-select__native-control", disabled: props.disabled, multiple: props.multiple }, options));
100
+ return (h("select", { required: props.required, "aria-labelledby": props.label ? LABEL_ID : undefined, "aria-describedby": getDescribedBy(props), "aria-disabled": props.disabled, "aria-required": props.required, "aria-invalid": props.isValid ? undefined : 'true', onChange: props.onNativeChange, onFocus: props.open, onBlur: props.close, class: "limel-select__native-control", disabled: props.disabled, multiple: props.multiple }, options));
101
101
  };
102
102
  const renderOption = (selectedOption) => {
103
103
  return (option) => {
@@ -270,15 +270,15 @@ export class Slider {
270
270
  if (this.disabled || this.readonly) {
271
271
  inputProps.disabled = true;
272
272
  }
273
- return (h(Host, { key: '6d910879894ffc7ff36df228e4252e4758622ab9', class: this.getContainerClassList() }, h("limel-notched-outline", { key: '56e3b4dbc963b6923c63a33acb21895f4e86c947', labelId: this.inputId, label: this.label, required: this.required, invalid: this.invalid, disabled: this.disabled, readonly: this.readonly, hasValue: !this.isUnset, hasFloatingLabel: true }, h("div", { key: 'f74ef31e0b7d67dcc8a231a05f9b00b84de50b33', slot: "content" }, h("div", { key: '4a4018ac095977f89cba99a0222603469d11ce30', class: "slider", style: { '--slider-fraction': `${fraction}` } }, h("input", Object.assign({ key: '588a4049b70749cf54a9faf08d6f0514cbfd2d6c', id: this.inputId, type: "range", min: min, max: max, value: this.displayValue, "aria-describedby": this.helperText
273
+ return (h(Host, { key: '6d910879894ffc7ff36df228e4252e4758622ab9', class: this.getContainerClassList() }, h("limel-notched-outline", { key: '56e3b4dbc963b6923c63a33acb21895f4e86c947', labelId: this.inputId, label: this.label, required: this.required, invalid: this.invalid, disabled: this.disabled, readonly: this.readonly, hasValue: !this.isUnset, hasFloatingLabel: true }, h("div", { key: 'f74ef31e0b7d67dcc8a231a05f9b00b84de50b33', slot: "content" }, h("div", { key: '4a4018ac095977f89cba99a0222603469d11ce30', class: "slider", style: { '--slider-fraction': `${fraction}` } }, h("input", Object.assign({ key: '3c9dc1fb20dd26ae0ea6cbab0914d5012a9c0184', id: this.inputId, type: "range", min: min, max: max, value: this.displayValue, "aria-describedby": this.helperText
274
274
  ? this.helperTextId
275
- : undefined, "aria-valuetext": this.isUnset
275
+ : undefined, "aria-invalid": this.invalid ? 'true' : undefined, "aria-valuetext": this.isUnset
276
276
  ? translate.get('value-not-set', this.language)
277
277
  : undefined, ref: (el) => {
278
278
  this.inputElement = el;
279
- }, onInput: this.handleInput, onChange: this.handleChange, onClick: this.handleClick }, inputProps)), h("div", { key: '48f58e7fe170d8fe9d01fcd938b53088ff9b796d', class: "track" }, h("div", { key: '4f488b62a840c0346de6157d7d1ce7bdba26b357', class: "active" }), this.renderStepDots(min, max)), h("div", { key: '4234e2d0711fa650b1d0b7c05eb9d52d327818fc', class: "thumb" }, h("div", { key: '28c9d98e694cdaa0927ef05b396199b24d6b6e16', class: "knob" }), h("div", { key: 'cde9b95313351dd37794e32f2a107426a5bca7dc', class: "indicator", "aria-hidden": "true" }, this.isUnset
279
+ }, onInput: this.handleInput, onChange: this.handleChange, onClick: this.handleClick }, inputProps)), h("div", { key: '4873567abad9bd78cd44cf244bb5c41e87c10e12', class: "track" }, h("div", { key: '165858a4a1ee9ff35ed17b3f31c358b77143af47', class: "active" }), this.renderStepDots(min, max)), h("div", { key: 'ad6a62247047caad0332385713acf0660473589c', class: "thumb" }, h("div", { key: '10a5b6171a86d156de78987caf36c6256b1d3db7', class: "knob" }), h("div", { key: 'dac6f4e48e2345091c58433124afeb29cca52e02', class: "indicator", "aria-hidden": "true" }, this.isUnset
280
280
  ? '\u{2194}\u{FE0E}'
281
- : this.displayValue))), h("div", { key: '0fa3c9c45697c787376880734f8be0127927eb30', class: "range-labels" }, h("span", { key: '9dc7d1b38bcf62369e827519b919c9cd69676b4d', class: "min" }, this.multiplyByFactor(this.valuemin), this.unit), h("span", { key: '6cf1350f909d119da15e46599dbada96f2e9f7dc', class: "max" }, this.multiplyByFactor(this.valuemax), this.unit)))), this.renderClearButton(), this.renderHelperLine()));
281
+ : this.displayValue))), h("div", { key: 'b62ca0a0dc80b5d6c370d477a7cb340c5c1fb79d', class: "range-labels" }, h("span", { key: '4455915693d0c95db8f5a05ac344e8de3c7efe4d', class: "min" }, this.multiplyByFactor(this.valuemin), this.unit), h("span", { key: 'ef141dec4f89ad937e521cb82b94ad3348904e49', class: "max" }, this.multiplyByFactor(this.valuemax), this.unit)))), this.renderClearButton(), this.renderHelperLine()));
282
282
  }
283
283
  watchValue() {
284
284
  this.syncStateFromValue();
@@ -55,7 +55,7 @@ export class Switch {
55
55
  return (h("limel-dynamic-label", { value: this.value, "aria-describedby": this.ariaDescribedBy, defaultLabel: { text: this.label, icon: icon }, labels: this.readonlyLabels }));
56
56
  };
57
57
  this.renderInteractive = () => {
58
- return (h("div", { class: "switch" }, h("button", { id: this.fieldId, type: "button", role: "switch", "aria-checked": String(this.value), disabled: this.disabled, onClick: this.handleClick, "aria-describedby": this.ariaDescribedBy }, h("span", { class: "handle" })), h("label", { htmlFor: this.fieldId }, this.label)));
58
+ return (h("div", { class: "switch" }, h("button", { id: this.fieldId, type: "button", role: "switch", "aria-checked": String(this.value), disabled: this.disabled, onClick: this.handleClick, "aria-describedby": this.ariaDescribedBy, "aria-invalid": this.invalid ? 'true' : undefined }, h("span", { class: "handle" })), h("label", { htmlFor: this.fieldId }, this.label)));
59
59
  };
60
60
  this.renderHelperLine = () => {
61
61
  if (!this.hasHelperText()) {