@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.
- package/LICENSE +641 -641
- package/components/COMPONENTS.md +1116 -1116
- package/components/index.js +81 -81
- package/components/ln-accordion/ln-accordion-dev.scss +8 -8
- package/components/ln-accordion/ln-accordion.js +1 -1
- package/components/ln-accordion/ln-accordion.schema.json +30 -30
- package/components/ln-ajax/ln-ajax-dev.scss +8 -8
- package/components/ln-ajax/ln-ajax.js +1 -1
- package/components/ln-ajax/ln-ajax.schema.json +30 -30
- package/components/ln-api-connector/ln-api-connector.js +1 -1
- package/components/ln-api-connector/ln-api-connector.schema.json +9 -0
- package/components/ln-api-connector/src/ln-api-connector.js +2 -0
- package/components/ln-api-queue/ln-api-queue.js +1 -1
- package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
- package/components/ln-api-queue/src/ln-api-queue.js +3 -13
- package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
- package/components/ln-autoresize/ln-autoresize.js +1 -1
- package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
- package/components/ln-autosave/ln-autosave-dev.scss +13 -13
- package/components/ln-autosave/ln-autosave.js +1 -1
- package/components/ln-autosave/ln-autosave.schema.json +48 -48
- package/components/ln-autosave/src/autosave-model.js +34 -34
- package/components/ln-chart/ln-chart.js +1 -1
- package/components/ln-chart/ln-chart.schema.json +146 -146
- package/components/ln-chart/src/chart-model.js +154 -154
- package/components/ln-circular-progress/ln-circular-progress.js +1 -1
- package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
- package/components/ln-confirm/ln-confirm-dev.scss +24 -24
- package/components/ln-confirm/ln-confirm.js +1 -1
- package/components/ln-confirm/ln-confirm.schema.json +66 -66
- package/components/ln-confirm/src/ln-confirm.js +178 -178
- package/components/ln-core/attrs.js +13 -0
- package/components/ln-core/crypto.js +286 -265
- package/components/ln-core/dom.js +115 -115
- package/components/ln-core/form.js +146 -136
- package/components/ln-core/helpers.js +33 -33
- package/components/ln-core/http.js +48 -48
- package/components/ln-core/index.js +1 -1
- package/components/ln-core/lifecycle.js +5 -0
- package/components/ln-core/ln-core.schema.json +3 -2
- package/components/ln-core/locale.js +55 -55
- package/components/ln-core/matching.js +78 -78
- package/components/ln-core/number.js +125 -125
- package/components/ln-core/template.js +206 -206
- package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
- package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
- package/components/ln-data-coordinator/README.md +9 -38
- package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
- package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
- package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
- package/components/ln-data-coordinator/src/ln-data-coordinator.js +432 -1308
- package/components/ln-data-store/ln-data-store-dev.scss +13 -13
- package/components/ln-data-store/ln-data-store.js +1 -1
- package/components/ln-data-store/ln-data-store.schema.json +84 -84
- package/components/ln-data-store/src/data-store-model.js +184 -184
- package/components/ln-data-store/src/ln-data-store.js +1 -1
- package/components/ln-date/ln-date.js +1 -1
- package/components/ln-date/ln-date.schema.json +92 -92
- package/components/ln-date/src/date-model.js +186 -186
- package/components/ln-debug/ln-debug.js +1 -1
- package/components/ln-debug/ln-debug.schema.json +28 -28
- package/components/ln-debug/src/console-sink.js +19 -19
- package/components/ln-debug/src/gate.js +95 -95
- package/components/ln-debug/src/generated-attributes.js +4 -2
- package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
- package/components/ln-dropdown/ln-dropdown.js +1 -1
- package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
- package/components/ln-editor/ln-editor.js +1 -1
- package/components/ln-editor/ln-editor.schema.json +58 -58
- package/components/ln-external-links/ln-external-links.js +1 -1
- package/components/ln-external-links/ln-external-links.schema.json +18 -18
- package/components/ln-fill/ln-fill.js +1 -1
- package/components/ln-fill/ln-fill.schema.json +23 -23
- package/components/ln-filter/ln-filter.js +1 -1
- package/components/ln-filter/ln-filter.schema.json +153 -153
- package/components/ln-filter/src/filter-model.js +185 -185
- package/components/ln-form/ln-form-dev.scss +8 -8
- package/components/ln-form/ln-form.js +1 -1
- package/components/ln-form/ln-form.schema.json +6 -0
- package/components/ln-form/src/ln-form.js +21 -0
- package/components/ln-http/ln-http.js +1 -1
- package/components/ln-http/ln-http.schema.json +6 -6
- package/components/ln-icon/ln-icon.schema.json +6 -6
- package/components/ln-include/ln-include.js +1 -1
- package/components/ln-include/ln-include.schema.json +23 -23
- package/components/ln-key/ln-key-dev.scss +18 -18
- package/components/ln-key/ln-key.js +1 -1
- package/components/ln-key/ln-key.schema.json +56 -56
- package/components/ln-key/src/ln-key.js +10 -10
- package/components/ln-link/ln-link-dev.scss +8 -8
- package/components/ln-link/ln-link.js +1 -1
- package/components/ln-link/ln-link.schema.json +22 -22
- package/components/ln-list/README.md +152 -152
- package/components/ln-list/ln-list-dev.scss +8 -8
- package/components/ln-list/ln-list.js +1 -1
- package/components/ln-list/ln-list.schema.json +166 -166
- package/components/ln-modal/ln-modal.js +1 -1
- package/components/ln-modal/ln-modal.schema.json +65 -65
- package/components/ln-nav/ln-nav.js +1 -1
- package/components/ln-nav/ln-nav.schema.json +25 -25
- package/components/ln-number/ln-number.js +1 -1
- package/components/ln-number/ln-number.schema.json +63 -63
- package/components/ln-number/src/ln-number.js +26 -0
- package/components/ln-number/src/number-model.js +31 -31
- package/components/ln-obfuscator/README.md +60 -60
- package/components/ln-obfuscator/ln-obfuscator.js +1 -1
- package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
- package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
- package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
- package/components/ln-options/ln-options.js +1 -1
- package/components/ln-options/ln-options.schema.json +6 -0
- package/components/ln-options/src/ln-options.js +4 -4
- package/components/ln-persist/README.md +151 -151
- package/components/ln-persist/ln-persist.js +1 -1
- package/components/ln-persist/ln-persist.schema.json +28 -28
- package/components/ln-persist/src/ln-persist.js +83 -83
- package/components/ln-picklist/README.md +230 -230
- package/components/ln-picklist/ln-picklist-dev.scss +22 -22
- package/components/ln-picklist/ln-picklist.js +1 -1
- package/components/ln-picklist/ln-picklist.schema.json +49 -49
- package/components/ln-picklist/src/ln-picklist.js +228 -228
- package/components/ln-popover/ln-popover-dev.scss +13 -13
- package/components/ln-popover/ln-popover.js +1 -1
- package/components/ln-popover/ln-popover.schema.json +82 -82
- package/components/ln-progress/ln-progress-dev.scss +8 -8
- package/components/ln-progress/ln-progress.js +1 -1
- package/components/ln-progress/ln-progress.schema.json +40 -40
- package/components/ln-router/ln-router.js +1 -1
- package/components/ln-router/ln-router.schema.json +76 -76
- package/components/ln-scroll/README.md +45 -45
- package/components/ln-scroll/ln-scroll.js +1 -1
- package/components/ln-scroll/ln-scroll.schema.json +85 -85
- package/components/ln-scroll/src/ln-scroll.js +150 -150
- package/components/ln-search/README.md +188 -188
- package/components/ln-search/ln-search.js +1 -1
- package/components/ln-search/ln-search.schema.json +103 -103
- package/components/ln-slug/ln-slug.js +1 -1
- package/components/ln-slug/ln-slug.schema.json +23 -23
- package/components/ln-slug/src/ln-slug.js +25 -1
- package/components/ln-sort/ln-sort.js +1 -1
- package/components/ln-sort/ln-sort.schema.json +104 -104
- package/components/ln-sort/src/ln-sort.js +321 -321
- package/components/ln-sort/src/sort-model.js +117 -117
- package/components/ln-sortable/ln-sortable-dev.scss +13 -13
- package/components/ln-sortable/ln-sortable.js +1 -1
- package/components/ln-sortable/ln-sortable.schema.json +36 -36
- package/components/ln-stat/ln-stat-dev.scss +8 -8
- package/components/ln-stat/ln-stat.js +1 -1
- package/components/ln-stat/ln-stat.schema.json +32 -32
- package/components/ln-stat/src/stat-model.js +28 -28
- package/components/ln-table/ln-table.js +1 -1
- package/components/ln-table/ln-table.schema.json +211 -211
- package/components/ln-table/src/table-model.js +98 -98
- package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
- package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
- package/components/ln-tabs/ln-tabs.js +1 -1
- package/components/ln-tabs/ln-tabs.schema.json +75 -75
- package/components/ln-tabs/src/ln-tabs.js +208 -208
- package/components/ln-tabs/src/tabs-model.js +82 -82
- package/components/ln-time/ln-time-dev.scss +13 -13
- package/components/ln-time/ln-time.js +1 -1
- package/components/ln-time/ln-time.schema.json +38 -38
- package/components/ln-toast/ln-toast.js +1 -1
- package/components/ln-toast/ln-toast.schema.json +65 -65
- package/components/ln-toggle/ln-toggle.js +1 -1
- package/components/ln-toggle/ln-toggle.schema.json +52 -52
- package/components/ln-tooltip/ln-tooltip.js +1 -1
- package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
- package/components/ln-translations/ln-translations.js +1 -1
- package/components/ln-translations/ln-translations.schema.json +120 -120
- package/components/ln-ui-coordinator/README.md +62 -62
- package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
- package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
- package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
- package/components/ln-upload/ln-upload.js +1 -1
- package/components/ln-upload/ln-upload.schema.json +193 -193
- package/components/ln-upload/src/upload-model.js +78 -78
- package/components/ln-validate/ln-validate.js +1 -1
- package/components/ln-validate/ln-validate.schema.json +50 -50
- package/components/ln-validate/src/validate-model.js +64 -64
- package/components/ln-websocket-connector/README.md +166 -166
- package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
- package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
- package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
- package/ln-ashlar.html-data.json +15 -8
- package/package.json +85 -84
- package/theme/brand.css +73 -73
- package/theme/components/_confirm.scss +6 -6
- package/theme/components/_picklist.scss +3 -3
- package/theme/config/_palette.scss +274 -274
- package/theme/config/_theme.scss +292 -292
- package/theme/config/_tokens.scss +285 -285
- package/theme/config/mixins/_index.scss +62 -62
- package/theme/config/mixins/_picklist.scss +66 -66
- package/theme/ln-ashlar-dev.scss +66 -66
- package/theme/ln-ashlar-landing.scss +56 -56
- package/theme/ln-ashlar-theme.scss +63 -63
- package/theme/presentation/_bento.scss +151 -151
- package/theme/presentation/_cta.scss +111 -111
- package/theme/presentation/_effects.scss +216 -216
- package/theme/presentation/_features.scss +108 -108
- package/theme/presentation/_footer.scss +129 -129
- package/theme/presentation/_hero.scss +158 -158
- package/theme/presentation/_layout.scss +170 -170
- package/theme/presentation/_navbar.scss +140 -140
- package/theme/presentation/_pricing.scss +262 -262
- package/theme/presentation/_stats.scss +87 -87
- package/theme/presentation/_testimonials.scss +124 -124
- package/theme/presentation/_tokens.scss +39 -39
- package/theme/presentation/_typography.scss +163 -163
- 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
|
+
}
|