@livenetworks/ashlar 2.0.7 → 2.0.9

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 (213) hide show
  1. package/LICENSE +641 -641
  2. package/components/COMPONENTS.md +1116 -1116
  3. package/components/index.js +81 -81
  4. package/components/ln-accordion/ln-accordion-dev.scss +8 -8
  5. package/components/ln-accordion/ln-accordion.js +1 -1
  6. package/components/ln-accordion/ln-accordion.schema.json +30 -30
  7. package/components/ln-ajax/ln-ajax-dev.scss +8 -8
  8. package/components/ln-ajax/ln-ajax.js +1 -1
  9. package/components/ln-ajax/ln-ajax.schema.json +30 -30
  10. package/components/ln-api-connector/ln-api-connector.js +1 -1
  11. package/components/ln-api-connector/ln-api-connector.schema.json +9 -0
  12. package/components/ln-api-connector/src/ln-api-connector.js +2 -0
  13. package/components/ln-api-queue/ln-api-queue.js +1 -1
  14. package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
  15. package/components/ln-api-queue/src/ln-api-queue.js +3 -13
  16. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
  17. package/components/ln-autoresize/ln-autoresize.js +1 -1
  18. package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
  19. package/components/ln-autosave/ln-autosave-dev.scss +13 -13
  20. package/components/ln-autosave/ln-autosave.js +1 -1
  21. package/components/ln-autosave/ln-autosave.schema.json +48 -48
  22. package/components/ln-autosave/src/autosave-model.js +34 -34
  23. package/components/ln-chart/ln-chart.js +1 -1
  24. package/components/ln-chart/ln-chart.schema.json +146 -146
  25. package/components/ln-chart/src/chart-model.js +154 -154
  26. package/components/ln-circular-progress/ln-circular-progress.js +1 -1
  27. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
  28. package/components/ln-confirm/ln-confirm-dev.scss +24 -24
  29. package/components/ln-confirm/ln-confirm.js +1 -1
  30. package/components/ln-confirm/ln-confirm.schema.json +66 -66
  31. package/components/ln-confirm/src/ln-confirm.js +178 -178
  32. package/components/ln-core/attrs.js +13 -0
  33. package/components/ln-core/crypto.js +286 -265
  34. package/components/ln-core/dom.js +115 -115
  35. package/components/ln-core/form.js +146 -136
  36. package/components/ln-core/helpers.js +33 -33
  37. package/components/ln-core/http.js +48 -48
  38. package/components/ln-core/index.js +1 -1
  39. package/components/ln-core/lifecycle.js +5 -0
  40. package/components/ln-core/ln-core.schema.json +3 -2
  41. package/components/ln-core/locale.js +55 -55
  42. package/components/ln-core/matching.js +78 -78
  43. package/components/ln-core/number.js +125 -125
  44. package/components/ln-core/template.js +206 -206
  45. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  46. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
  47. package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
  48. package/components/ln-data-coordinator/README.md +9 -38
  49. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
  50. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  51. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
  52. package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
  53. package/components/ln-data-coordinator/src/ln-data-coordinator.js +432 -1308
  54. package/components/ln-data-store/ln-data-store-dev.scss +13 -13
  55. package/components/ln-data-store/ln-data-store.js +1 -1
  56. package/components/ln-data-store/ln-data-store.schema.json +84 -84
  57. package/components/ln-data-store/src/data-store-model.js +184 -184
  58. package/components/ln-data-store/src/ln-data-store.js +1 -1
  59. package/components/ln-date/ln-date.js +1 -1
  60. package/components/ln-date/ln-date.schema.json +92 -92
  61. package/components/ln-date/src/date-model.js +186 -186
  62. package/components/ln-debug/ln-debug.js +1 -1
  63. package/components/ln-debug/ln-debug.schema.json +28 -28
  64. package/components/ln-debug/src/console-sink.js +19 -19
  65. package/components/ln-debug/src/gate.js +95 -95
  66. package/components/ln-debug/src/generated-attributes.js +4 -2
  67. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
  68. package/components/ln-dropdown/ln-dropdown.js +1 -1
  69. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  70. package/components/ln-editor/ln-editor.js +1 -1
  71. package/components/ln-editor/ln-editor.schema.json +58 -58
  72. package/components/ln-external-links/ln-external-links.js +1 -1
  73. package/components/ln-external-links/ln-external-links.schema.json +18 -18
  74. package/components/ln-fill/ln-fill.js +1 -1
  75. package/components/ln-fill/ln-fill.schema.json +23 -23
  76. package/components/ln-filter/ln-filter.js +1 -1
  77. package/components/ln-filter/ln-filter.schema.json +153 -153
  78. package/components/ln-filter/src/filter-model.js +185 -185
  79. package/components/ln-form/ln-form-dev.scss +8 -8
  80. package/components/ln-form/ln-form.js +1 -1
  81. package/components/ln-form/ln-form.schema.json +6 -0
  82. package/components/ln-form/src/ln-form.js +21 -0
  83. package/components/ln-http/ln-http.js +1 -1
  84. package/components/ln-http/ln-http.schema.json +6 -6
  85. package/components/ln-icon/ln-icon.schema.json +6 -6
  86. package/components/ln-include/ln-include.js +1 -1
  87. package/components/ln-include/ln-include.schema.json +23 -23
  88. package/components/ln-key/ln-key-dev.scss +18 -18
  89. package/components/ln-key/ln-key.js +1 -1
  90. package/components/ln-key/ln-key.schema.json +56 -56
  91. package/components/ln-key/src/ln-key.js +10 -10
  92. package/components/ln-link/ln-link-dev.scss +8 -8
  93. package/components/ln-link/ln-link.js +1 -1
  94. package/components/ln-link/ln-link.schema.json +22 -22
  95. package/components/ln-list/README.md +152 -152
  96. package/components/ln-list/ln-list-dev.scss +8 -8
  97. package/components/ln-list/ln-list.js +1 -1
  98. package/components/ln-list/ln-list.schema.json +166 -166
  99. package/components/ln-modal/ln-modal.js +1 -1
  100. package/components/ln-modal/ln-modal.schema.json +65 -65
  101. package/components/ln-nav/ln-nav.js +1 -1
  102. package/components/ln-nav/ln-nav.schema.json +25 -25
  103. package/components/ln-number/ln-number.js +1 -1
  104. package/components/ln-number/ln-number.schema.json +63 -63
  105. package/components/ln-number/src/ln-number.js +26 -0
  106. package/components/ln-number/src/number-model.js +31 -31
  107. package/components/ln-obfuscator/README.md +60 -60
  108. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  109. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
  110. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
  111. package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
  112. package/components/ln-options/ln-options.js +1 -1
  113. package/components/ln-options/ln-options.schema.json +6 -0
  114. package/components/ln-options/src/ln-options.js +4 -4
  115. package/components/ln-persist/README.md +151 -151
  116. package/components/ln-persist/ln-persist.js +1 -1
  117. package/components/ln-persist/ln-persist.schema.json +28 -28
  118. package/components/ln-persist/src/ln-persist.js +83 -83
  119. package/components/ln-picklist/README.md +230 -230
  120. package/components/ln-picklist/ln-picklist-dev.scss +22 -22
  121. package/components/ln-picklist/ln-picklist.js +1 -1
  122. package/components/ln-picklist/ln-picklist.schema.json +49 -49
  123. package/components/ln-picklist/src/ln-picklist.js +228 -228
  124. package/components/ln-popover/ln-popover-dev.scss +13 -13
  125. package/components/ln-popover/ln-popover.js +1 -1
  126. package/components/ln-popover/ln-popover.schema.json +82 -82
  127. package/components/ln-progress/ln-progress-dev.scss +8 -8
  128. package/components/ln-progress/ln-progress.js +1 -1
  129. package/components/ln-progress/ln-progress.schema.json +40 -40
  130. package/components/ln-router/ln-router.js +1 -1
  131. package/components/ln-router/ln-router.schema.json +76 -76
  132. package/components/ln-scroll/README.md +45 -45
  133. package/components/ln-scroll/ln-scroll.js +1 -1
  134. package/components/ln-scroll/ln-scroll.schema.json +85 -85
  135. package/components/ln-scroll/src/ln-scroll.js +150 -150
  136. package/components/ln-search/README.md +188 -188
  137. package/components/ln-search/ln-search.js +1 -1
  138. package/components/ln-search/ln-search.schema.json +103 -103
  139. package/components/ln-slug/ln-slug.js +1 -1
  140. package/components/ln-slug/ln-slug.schema.json +23 -23
  141. package/components/ln-slug/src/ln-slug.js +25 -1
  142. package/components/ln-sort/ln-sort.js +1 -1
  143. package/components/ln-sort/ln-sort.schema.json +104 -104
  144. package/components/ln-sort/src/ln-sort.js +321 -321
  145. package/components/ln-sort/src/sort-model.js +117 -117
  146. package/components/ln-sortable/ln-sortable-dev.scss +13 -13
  147. package/components/ln-sortable/ln-sortable.js +1 -1
  148. package/components/ln-sortable/ln-sortable.schema.json +36 -36
  149. package/components/ln-stat/ln-stat-dev.scss +8 -8
  150. package/components/ln-stat/ln-stat.js +1 -1
  151. package/components/ln-stat/ln-stat.schema.json +32 -32
  152. package/components/ln-stat/src/stat-model.js +28 -28
  153. package/components/ln-table/ln-table.js +1 -1
  154. package/components/ln-table/ln-table.schema.json +211 -211
  155. package/components/ln-table/src/table-model.js +98 -98
  156. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  157. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
  158. package/components/ln-tabs/ln-tabs.js +1 -1
  159. package/components/ln-tabs/ln-tabs.schema.json +75 -75
  160. package/components/ln-tabs/src/ln-tabs.js +208 -208
  161. package/components/ln-tabs/src/tabs-model.js +82 -82
  162. package/components/ln-time/ln-time-dev.scss +13 -13
  163. package/components/ln-time/ln-time.js +1 -1
  164. package/components/ln-time/ln-time.schema.json +38 -38
  165. package/components/ln-toast/ln-toast.js +1 -1
  166. package/components/ln-toast/ln-toast.schema.json +65 -65
  167. package/components/ln-toggle/ln-toggle.js +1 -1
  168. package/components/ln-toggle/ln-toggle.schema.json +52 -52
  169. package/components/ln-tooltip/ln-tooltip.js +1 -1
  170. package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
  171. package/components/ln-translations/ln-translations.js +1 -1
  172. package/components/ln-translations/ln-translations.schema.json +120 -120
  173. package/components/ln-ui-coordinator/README.md +62 -62
  174. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
  175. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
  176. package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
  177. package/components/ln-upload/ln-upload.js +1 -1
  178. package/components/ln-upload/ln-upload.schema.json +193 -193
  179. package/components/ln-upload/src/upload-model.js +78 -78
  180. package/components/ln-validate/ln-validate.js +1 -1
  181. package/components/ln-validate/ln-validate.schema.json +50 -50
  182. package/components/ln-validate/src/validate-model.js +64 -64
  183. package/components/ln-websocket-connector/README.md +166 -166
  184. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
  185. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
  186. package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
  187. package/ln-ashlar.html-data.json +15 -8
  188. package/package.json +85 -84
  189. package/theme/brand.css +73 -73
  190. package/theme/components/_confirm.scss +6 -6
  191. package/theme/components/_picklist.scss +3 -3
  192. package/theme/config/_palette.scss +274 -274
  193. package/theme/config/_theme.scss +292 -292
  194. package/theme/config/_tokens.scss +285 -285
  195. package/theme/config/mixins/_index.scss +62 -62
  196. package/theme/config/mixins/_picklist.scss +66 -66
  197. package/theme/ln-ashlar-dev.scss +66 -66
  198. package/theme/ln-ashlar-landing.scss +56 -56
  199. package/theme/ln-ashlar-theme.scss +63 -63
  200. package/theme/presentation/_bento.scss +151 -151
  201. package/theme/presentation/_cta.scss +111 -111
  202. package/theme/presentation/_effects.scss +216 -216
  203. package/theme/presentation/_features.scss +108 -108
  204. package/theme/presentation/_footer.scss +129 -129
  205. package/theme/presentation/_hero.scss +158 -158
  206. package/theme/presentation/_layout.scss +170 -170
  207. package/theme/presentation/_navbar.scss +140 -140
  208. package/theme/presentation/_pricing.scss +262 -262
  209. package/theme/presentation/_stats.scss +87 -87
  210. package/theme/presentation/_testimonials.scss +124 -124
  211. package/theme/presentation/_tokens.scss +39 -39
  212. package/theme/presentation/_typography.scss +163 -163
  213. package/web-types.json +26 -12
@@ -1,49 +1,49 @@
1
- {
2
- "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
- "component": "ln-picklist",
4
- "generator": "scripts/sync-ln-schemas.mjs",
5
- "attributes": {
6
- "data-ln-debug": {
7
- "direction": null,
8
- "sources": [
9
- "ln-picklist-dev.scss"
10
- ]
11
- },
12
- "data-ln-picklist": {
13
- "direction": "both",
14
- "sources": [
15
- "ln-picklist-dev.scss",
16
- "src/ln-picklist.js"
17
- ],
18
- "type": "enum",
19
- "values": [
20
- "enabled",
21
- "disabled"
22
- ],
23
- "fallback": "enabled",
24
- "description": "Controls enabled/disabled state of the dual-list picklist"
25
- },
26
- "data-ln-picklist-list": {
27
- "direction": "author",
28
- "sources": [
29
- "ln-picklist-dev.scss",
30
- "src/ln-picklist.js"
31
- ],
32
- "type": "enum",
33
- "values": [
34
- "available",
35
- "selected"
36
- ],
37
- "description": "Role marker for available or selected picklist columns"
38
- },
39
- "data-ln-picklist-max": {
40
- "direction": "author",
41
- "sources": [
42
- "src/ln-picklist.js"
43
- ],
44
- "type": "integer",
45
- "min": 1,
46
- "description": "Maximum selectable items in the selected list"
47
- }
48
- }
49
- }
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-picklist",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-debug": {
7
+ "direction": null,
8
+ "sources": [
9
+ "ln-picklist-dev.scss"
10
+ ]
11
+ },
12
+ "data-ln-picklist": {
13
+ "direction": "both",
14
+ "sources": [
15
+ "ln-picklist-dev.scss",
16
+ "src/ln-picklist.js"
17
+ ],
18
+ "type": "enum",
19
+ "values": [
20
+ "enabled",
21
+ "disabled"
22
+ ],
23
+ "fallback": "enabled",
24
+ "description": "Controls enabled/disabled state of the dual-list picklist"
25
+ },
26
+ "data-ln-picklist-list": {
27
+ "direction": "author",
28
+ "sources": [
29
+ "ln-picklist-dev.scss",
30
+ "src/ln-picklist.js"
31
+ ],
32
+ "type": "enum",
33
+ "values": [
34
+ "available",
35
+ "selected"
36
+ ],
37
+ "description": "Role marker for available or selected picklist columns"
38
+ },
39
+ "data-ln-picklist-max": {
40
+ "direction": "author",
41
+ "sources": [
42
+ "src/ln-picklist.js"
43
+ ],
44
+ "type": "integer",
45
+ "min": 1,
46
+ "description": "Maximum selectable items in the selected list"
47
+ }
48
+ }
49
+ }
@@ -1,228 +1,228 @@
1
- import { registerComponent, dispatch, dispatchCancelable } from '../../ln-core';
2
-
3
- (function () {
4
- 'use strict';
5
-
6
- const DOM_SELECTOR = 'data-ln-picklist';
7
- const DOM_ATTRIBUTE = 'lnPicklist';
8
- const LIST_ATTR = 'data-ln-picklist-list';
9
- const MAX_ATTR = 'data-ln-picklist-max';
10
-
11
- if (window[DOM_ATTRIBUTE] !== undefined) return;
12
-
13
- // ─── Attribute Contract (SSOT) ──────────────────────────
14
- const ATTRIBUTES = {
15
- 'data-ln-picklist': { type: 'enum', values: ['enabled', 'disabled'], fallback: 'enabled', effect: _syncEnabled, description: 'Controls enabled/disabled state of the dual-list picklist' },
16
- 'data-ln-picklist-max': { type: 'integer', fallback: Infinity, min: 1, effect: _syncMax, description: 'Maximum selectable items in the selected list' },
17
- 'data-ln-picklist-list': { type: 'enum', values: ['available', 'selected'], description: 'Role marker for available or selected picklist columns' }
18
- };
19
-
20
- // ─── Component ─────────────────────────────────────────────
21
- //
22
- // The checkbox IS the state. Its `checked` property and the list the item
23
- // currently sits in are the same fact, and `name`/`value` carry the
24
- // selection to the server natively — unchecked boxes are not submitted.
25
- // Hence no hidden inputs, no state attribute and no JS mirror.
26
-
27
- function _component(dom) {
28
- this.dom = dom;
29
- this.isEnabled = dom.getAttribute(DOM_SELECTOR) !== 'disabled';
30
- this.max = _readMax(dom);
31
-
32
- this.available = dom.querySelector('[' + LIST_ATTR + '="available"]');
33
- this.selected = dom.querySelector('[' + LIST_ATTR + '="selected"]');
34
-
35
- // Report the gap rather than failing on the first click.
36
- if (!this.available || !this.selected) {
37
- console.warn('[ln-picklist] requires both [' + LIST_ATTR + '="available"] and [' + LIST_ATTR + '="selected"]', dom);
38
- return this;
39
- }
40
-
41
- // Bound once so destroy() can detach the same reference.
42
- this._onChange = this._onChange.bind(this);
43
- dom.addEventListener('change', this._onChange);
44
-
45
- // Snapshot the authored items in document order.
46
- this._initial = [];
47
- for (const list of [this.available, this.selected]) {
48
- for (const item of list.children) {
49
- this._initial.push(item);
50
- }
51
- }
52
-
53
- // Initial sync: transfer checked items to selected, unchecked to available.
54
- this.sync();
55
-
56
- this._form = dom.closest('form');
57
- if (this._form) {
58
- this._onFormReset = this._onFormReset.bind(this);
59
- this._form.addEventListener('reset', this._onFormReset);
60
- }
61
-
62
- return this;
63
- }
64
-
65
- // ─── Lifecycle ─────────────────────────────────────────────
66
-
67
- _component.prototype.destroy = function () {
68
- if (!this.dom[DOM_ATTRIBUTE]) return;
69
- this._destroyed = true;
70
- if (this._onChange) this.dom.removeEventListener('change', this._onChange);
71
- if (this._form) this._form.removeEventListener('reset', this._onFormReset);
72
- delete this.dom[DOM_ATTRIBUTE];
73
- };
74
-
75
- // ─── Instance API ──────────────────────────────────────────
76
-
77
- _component.prototype.enable = function () {
78
- this.dom.setAttribute(DOM_SELECTOR, '');
79
- };
80
-
81
- _component.prototype.disable = function () {
82
- this.dom.setAttribute(DOM_SELECTOR, 'disabled');
83
- };
84
-
85
- _component.prototype.sync = function () {
86
- if (!this.available || !this.selected) return;
87
-
88
- // Include any dynamically added items not yet tracked
89
- const currentItems = Array.from(this.available.children).concat(Array.from(this.selected.children));
90
- for (let i = 0; i < currentItems.length; i++) {
91
- if (!this._initial.includes(currentItems[i])) {
92
- this._initial.push(currentItems[i]);
93
- }
94
- }
95
-
96
- let selectedCount = 0;
97
- for (let i = 0; i < this._initial.length; i++) {
98
- const item = this._initial[i];
99
- if (!item.isConnected) continue;
100
- const checkbox = item.querySelector('input[type="checkbox"]');
101
- if (!checkbox) continue;
102
-
103
- let targetList;
104
- if (checkbox.checked) {
105
- if (this.max === null || selectedCount < this.max) {
106
- targetList = this.selected;
107
- selectedCount++;
108
- } else {
109
- checkbox.checked = false;
110
- targetList = this.available;
111
- }
112
- } else {
113
- targetList = this.available;
114
- }
115
- targetList.appendChild(item);
116
- }
117
- };
118
-
119
- // ─── Form Reset ────────────────────────────────────────────
120
- //
121
- // Resetting a form restores every control to its default SILENTLY — the
122
- // spec fires no `change` and no `input` for them, only `reset` on the form
123
- // itself. Re-syncing in recorded order rebuilds both lists according to
124
- // defaultChecked.
125
-
126
- _component.prototype._onFormReset = function (e) {
127
- const self = this;
128
-
129
- // `reset` fires BEFORE the controls are restored, and it is cancelable,
130
- // so the work is deferred and re-checked — the same shape ln-editor
131
- // uses to re-read its textarea.
132
- setTimeout(function () {
133
- if (self._destroyed || e.defaultPrevented) return;
134
- self.sync();
135
- }, 0);
136
- };
137
-
138
- // ─── Change Handler ────────────────────────────────────────
139
-
140
- _component.prototype._onChange = function (e) {
141
- // `change` bubbles, so anything the author composes inside the root —
142
- // an ln-search input, a select — reaches this listener. Filter hard.
143
- const checkbox = e.target.closest('input[type="checkbox"]');
144
- if (!checkbox) return;
145
-
146
- const item = checkbox.closest('li');
147
- if (!item) return;
148
-
149
- const from = item.parentElement;
150
- if (from !== this.available && from !== this.selected) return;
151
-
152
- // The browser has already toggled the box by the time this runs, so a
153
- // bare return would leave it checked while the item stays put — which
154
- // is exactly the contradiction this component exists to avoid.
155
- if (!this.isEnabled) {
156
- checkbox.checked = !checkbox.checked;
157
- return;
158
- }
159
-
160
- const to = checkbox.checked ? this.selected : this.available;
161
-
162
- // Inconsistent authored markup (a checked item rendered on the left),
163
- // or a synthetic change that did not flip the box, would otherwise
164
- // re-append in place and emit a move that never happened.
165
- if (from === to) return;
166
-
167
- // Enforce maximum selection limit if configured
168
- if (to === this.selected && this.max !== null && this.selected.children.length >= this.max) {
169
- checkbox.checked = !checkbox.checked;
170
- dispatch(this.dom, 'ln-picklist:max-reached', {
171
- max: this.max,
172
- item: item,
173
- checkbox: checkbox,
174
- count: this.selected.children.length
175
- });
176
- return;
177
- }
178
-
179
- // One record, both events — they describe the same move.
180
- const detail = { item: item, from: from, to: to, checkbox: checkbox };
181
-
182
- if (dispatchCancelable(this.dom, 'ln-picklist:before-move', detail).defaultPrevented) {
183
- checkbox.checked = !checkbox.checked;
184
- return;
185
- }
186
-
187
- // Re-parenting a node that holds focus blurs it to <body> in Chromium.
188
- // Only restore when the focus really was here: a synthetic change from
189
- // a script must not steal focus away from whatever the user is using.
190
- const hadFocus = document.activeElement === checkbox;
191
-
192
- to.appendChild(item);
193
-
194
- if (hadFocus) checkbox.focus();
195
-
196
- dispatch(this.dom, 'ln-picklist:move', detail);
197
- };
198
-
199
- // ─── Attribute Sync ────────────────────────────────────────
200
-
201
- function _readMax(el) {
202
- const raw = el.getAttribute(MAX_ATTR);
203
- if (raw === null || raw === '') return null;
204
- const parsed = parseInt(raw, 10);
205
- return isNaN(parsed) || parsed < 0 ? null : parsed;
206
- }
207
-
208
- function _syncEnabled(el) {
209
- const instance = el[DOM_ATTRIBUTE];
210
- if (!instance) return;
211
- const shouldBeEnabled = el.getAttribute(DOM_SELECTOR) !== 'disabled';
212
- if (shouldBeEnabled === instance.isEnabled) return;
213
- instance.isEnabled = shouldBeEnabled;
214
- dispatch(el, shouldBeEnabled ? 'ln-picklist:enabled' : 'ln-picklist:disabled', { target: el });
215
- }
216
-
217
- function _syncMax(el) {
218
- const instance = el[DOM_ATTRIBUTE];
219
- if (!instance) return;
220
- instance.max = _readMax(el);
221
- }
222
-
223
- // ─── Init ──────────────────────────────────────────────────
224
-
225
- registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-picklist', {
226
- attributes: ATTRIBUTES
227
- });
228
- })();
1
+ import { registerComponent, dispatch, dispatchCancelable } from '../../ln-core';
2
+
3
+ (function () {
4
+ 'use strict';
5
+
6
+ const DOM_SELECTOR = 'data-ln-picklist';
7
+ const DOM_ATTRIBUTE = 'lnPicklist';
8
+ const LIST_ATTR = 'data-ln-picklist-list';
9
+ const MAX_ATTR = 'data-ln-picklist-max';
10
+
11
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
12
+
13
+ // ─── Attribute Contract (SSOT) ──────────────────────────
14
+ const ATTRIBUTES = {
15
+ 'data-ln-picklist': { type: 'enum', values: ['enabled', 'disabled'], fallback: 'enabled', effect: _syncEnabled, description: 'Controls enabled/disabled state of the dual-list picklist' },
16
+ 'data-ln-picklist-max': { type: 'integer', fallback: Infinity, min: 1, effect: _syncMax, description: 'Maximum selectable items in the selected list' },
17
+ 'data-ln-picklist-list': { type: 'enum', values: ['available', 'selected'], description: 'Role marker for available or selected picklist columns' }
18
+ };
19
+
20
+ // ─── Component ─────────────────────────────────────────────
21
+ //
22
+ // The checkbox IS the state. Its `checked` property and the list the item
23
+ // currently sits in are the same fact, and `name`/`value` carry the
24
+ // selection to the server natively — unchecked boxes are not submitted.
25
+ // Hence no hidden inputs, no state attribute and no JS mirror.
26
+
27
+ function _component(dom) {
28
+ this.dom = dom;
29
+ this.isEnabled = dom.getAttribute(DOM_SELECTOR) !== 'disabled';
30
+ this.max = _readMax(dom);
31
+
32
+ this.available = dom.querySelector('[' + LIST_ATTR + '="available"]');
33
+ this.selected = dom.querySelector('[' + LIST_ATTR + '="selected"]');
34
+
35
+ // Report the gap rather than failing on the first click.
36
+ if (!this.available || !this.selected) {
37
+ console.warn('[ln-picklist] requires both [' + LIST_ATTR + '="available"] and [' + LIST_ATTR + '="selected"]', dom);
38
+ return this;
39
+ }
40
+
41
+ // Bound once so destroy() can detach the same reference.
42
+ this._onChange = this._onChange.bind(this);
43
+ dom.addEventListener('change', this._onChange);
44
+
45
+ // Snapshot the authored items in document order.
46
+ this._initial = [];
47
+ for (const list of [this.available, this.selected]) {
48
+ for (const item of list.children) {
49
+ this._initial.push(item);
50
+ }
51
+ }
52
+
53
+ // Initial sync: transfer checked items to selected, unchecked to available.
54
+ this.sync();
55
+
56
+ this._form = dom.closest('form');
57
+ if (this._form) {
58
+ this._onFormReset = this._onFormReset.bind(this);
59
+ this._form.addEventListener('reset', this._onFormReset);
60
+ }
61
+
62
+ return this;
63
+ }
64
+
65
+ // ─── Lifecycle ─────────────────────────────────────────────
66
+
67
+ _component.prototype.destroy = function () {
68
+ if (!this.dom[DOM_ATTRIBUTE]) return;
69
+ this._destroyed = true;
70
+ if (this._onChange) this.dom.removeEventListener('change', this._onChange);
71
+ if (this._form) this._form.removeEventListener('reset', this._onFormReset);
72
+ delete this.dom[DOM_ATTRIBUTE];
73
+ };
74
+
75
+ // ─── Instance API ──────────────────────────────────────────
76
+
77
+ _component.prototype.enable = function () {
78
+ this.dom.setAttribute(DOM_SELECTOR, '');
79
+ };
80
+
81
+ _component.prototype.disable = function () {
82
+ this.dom.setAttribute(DOM_SELECTOR, 'disabled');
83
+ };
84
+
85
+ _component.prototype.sync = function () {
86
+ if (!this.available || !this.selected) return;
87
+
88
+ // Include any dynamically added items not yet tracked
89
+ const currentItems = Array.from(this.available.children).concat(Array.from(this.selected.children));
90
+ for (let i = 0; i < currentItems.length; i++) {
91
+ if (!this._initial.includes(currentItems[i])) {
92
+ this._initial.push(currentItems[i]);
93
+ }
94
+ }
95
+
96
+ let selectedCount = 0;
97
+ for (let i = 0; i < this._initial.length; i++) {
98
+ const item = this._initial[i];
99
+ if (!item.isConnected) continue;
100
+ const checkbox = item.querySelector('input[type="checkbox"]');
101
+ if (!checkbox) continue;
102
+
103
+ let targetList;
104
+ if (checkbox.checked) {
105
+ if (this.max === null || selectedCount < this.max) {
106
+ targetList = this.selected;
107
+ selectedCount++;
108
+ } else {
109
+ checkbox.checked = false;
110
+ targetList = this.available;
111
+ }
112
+ } else {
113
+ targetList = this.available;
114
+ }
115
+ targetList.appendChild(item);
116
+ }
117
+ };
118
+
119
+ // ─── Form Reset ────────────────────────────────────────────
120
+ //
121
+ // Resetting a form restores every control to its default SILENTLY — the
122
+ // spec fires no `change` and no `input` for them, only `reset` on the form
123
+ // itself. Re-syncing in recorded order rebuilds both lists according to
124
+ // defaultChecked.
125
+
126
+ _component.prototype._onFormReset = function (e) {
127
+ const self = this;
128
+
129
+ // `reset` fires BEFORE the controls are restored, and it is cancelable,
130
+ // so the work is deferred and re-checked — the same shape ln-editor
131
+ // uses to re-read its textarea.
132
+ setTimeout(function () {
133
+ if (self._destroyed || e.defaultPrevented) return;
134
+ self.sync();
135
+ }, 0);
136
+ };
137
+
138
+ // ─── Change Handler ────────────────────────────────────────
139
+
140
+ _component.prototype._onChange = function (e) {
141
+ // `change` bubbles, so anything the author composes inside the root —
142
+ // an ln-search input, a select — reaches this listener. Filter hard.
143
+ const checkbox = e.target.closest('input[type="checkbox"]');
144
+ if (!checkbox) return;
145
+
146
+ const item = checkbox.closest('li');
147
+ if (!item) return;
148
+
149
+ const from = item.parentElement;
150
+ if (from !== this.available && from !== this.selected) return;
151
+
152
+ // The browser has already toggled the box by the time this runs, so a
153
+ // bare return would leave it checked while the item stays put — which
154
+ // is exactly the contradiction this component exists to avoid.
155
+ if (!this.isEnabled) {
156
+ checkbox.checked = !checkbox.checked;
157
+ return;
158
+ }
159
+
160
+ const to = checkbox.checked ? this.selected : this.available;
161
+
162
+ // Inconsistent authored markup (a checked item rendered on the left),
163
+ // or a synthetic change that did not flip the box, would otherwise
164
+ // re-append in place and emit a move that never happened.
165
+ if (from === to) return;
166
+
167
+ // Enforce maximum selection limit if configured
168
+ if (to === this.selected && this.max !== null && this.selected.children.length >= this.max) {
169
+ checkbox.checked = !checkbox.checked;
170
+ dispatch(this.dom, 'ln-picklist:max-reached', {
171
+ max: this.max,
172
+ item: item,
173
+ checkbox: checkbox,
174
+ count: this.selected.children.length
175
+ });
176
+ return;
177
+ }
178
+
179
+ // One record, both events — they describe the same move.
180
+ const detail = { item: item, from: from, to: to, checkbox: checkbox };
181
+
182
+ if (dispatchCancelable(this.dom, 'ln-picklist:before-move', detail).defaultPrevented) {
183
+ checkbox.checked = !checkbox.checked;
184
+ return;
185
+ }
186
+
187
+ // Re-parenting a node that holds focus blurs it to <body> in Chromium.
188
+ // Only restore when the focus really was here: a synthetic change from
189
+ // a script must not steal focus away from whatever the user is using.
190
+ const hadFocus = document.activeElement === checkbox;
191
+
192
+ to.appendChild(item);
193
+
194
+ if (hadFocus) checkbox.focus();
195
+
196
+ dispatch(this.dom, 'ln-picklist:move', detail);
197
+ };
198
+
199
+ // ─── Attribute Sync ────────────────────────────────────────
200
+
201
+ function _readMax(el) {
202
+ const raw = el.getAttribute(MAX_ATTR);
203
+ if (raw === null || raw === '') return null;
204
+ const parsed = parseInt(raw, 10);
205
+ return isNaN(parsed) || parsed < 0 ? null : parsed;
206
+ }
207
+
208
+ function _syncEnabled(el) {
209
+ const instance = el[DOM_ATTRIBUTE];
210
+ if (!instance) return;
211
+ const shouldBeEnabled = el.getAttribute(DOM_SELECTOR) !== 'disabled';
212
+ if (shouldBeEnabled === instance.isEnabled) return;
213
+ instance.isEnabled = shouldBeEnabled;
214
+ dispatch(el, shouldBeEnabled ? 'ln-picklist:enabled' : 'ln-picklist:disabled', { target: el });
215
+ }
216
+
217
+ function _syncMax(el) {
218
+ const instance = el[DOM_ATTRIBUTE];
219
+ if (!instance) return;
220
+ instance.max = _readMax(el);
221
+ }
222
+
223
+ // ─── Init ──────────────────────────────────────────────────
224
+
225
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-picklist', {
226
+ attributes: ATTRIBUTES
227
+ });
228
+ })();
@@ -1,13 +1,13 @@
1
- @use '../../theme/config/mixins' as *;
2
-
3
- [data-ln-debug] {
4
- // 1. Popover Missing ID: popovers controlled by data-ln-popover must have an id for trigger linking
5
- [data-ln-popover]:not([id]) {
6
- @include dev-dom-error("[data-ln-popover] is missing a required 'id' attribute for trigger linking.");
7
- }
8
-
9
- // 2. Popover Trigger Empty ID: trigger elements must point to a target popover ID
10
- [data-ln-popover-for=""] {
11
- @include dev-inline-error("data-ln-popover-for target ID cannot be empty");
12
- }
13
- }
1
+ @use '../../theme/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Popover Missing ID: popovers controlled by data-ln-popover must have an id for trigger linking
5
+ [data-ln-popover]:not([id]) {
6
+ @include dev-dom-error("[data-ln-popover] is missing a required 'id' attribute for trigger linking.");
7
+ }
8
+
9
+ // 2. Popover Trigger Empty ID: trigger elements must point to a target popover ID
10
+ [data-ln-popover-for=""] {
11
+ @include dev-inline-error("data-ln-popover-for target ID cannot be empty");
12
+ }
13
+ }