@livenetworks/ashlar 2.0.6 → 2.0.8

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 (221) 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-accordion/src/ln-accordion.js +1 -0
  8. package/components/ln-ajax/ln-ajax-dev.scss +8 -8
  9. package/components/ln-ajax/ln-ajax.js +1 -1
  10. package/components/ln-ajax/ln-ajax.schema.json +30 -30
  11. package/components/ln-api-connector/ln-api-connector.js +1 -1
  12. package/components/ln-api-connector/ln-api-connector.schema.json +96 -96
  13. package/components/ln-api-connector/src/ln-api-connector.js +1 -0
  14. package/components/ln-api-queue/ln-api-queue.js +1 -1
  15. package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
  16. package/components/ln-api-queue/src/ln-api-queue.js +3 -13
  17. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
  18. package/components/ln-autoresize/ln-autoresize.js +1 -1
  19. package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
  20. package/components/ln-autosave/ln-autosave-dev.scss +13 -13
  21. package/components/ln-autosave/ln-autosave.js +1 -1
  22. package/components/ln-autosave/ln-autosave.schema.json +48 -48
  23. package/components/ln-autosave/src/autosave-model.js +34 -34
  24. package/components/ln-chart/ln-chart.js +1 -1
  25. package/components/ln-chart/ln-chart.schema.json +146 -146
  26. package/components/ln-chart/src/chart-model.js +154 -154
  27. package/components/ln-circular-progress/ln-circular-progress.js +1 -1
  28. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
  29. package/components/ln-confirm/ln-confirm-dev.scss +24 -24
  30. package/components/ln-confirm/ln-confirm.js +1 -1
  31. package/components/ln-confirm/ln-confirm.schema.json +66 -66
  32. package/components/ln-confirm/src/ln-confirm.js +178 -178
  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 +136 -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/ln-core.schema.json +109 -109
  40. package/components/ln-core/locale.js +55 -55
  41. package/components/ln-core/matching.js +78 -78
  42. package/components/ln-core/number.js +125 -125
  43. package/components/ln-core/template.js +206 -206
  44. package/components/ln-couchdb-connector/README.md +4 -0
  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 +19 -1
  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 +428 -1299
  54. package/components/ln-data-store/README.md +3 -2
  55. package/components/ln-data-store/ln-data-store-dev.scss +13 -13
  56. package/components/ln-data-store/ln-data-store.js +1 -1
  57. package/components/ln-data-store/ln-data-store.schema.json +84 -84
  58. package/components/ln-data-store/src/data-store-model.js +184 -155
  59. package/components/ln-data-store/src/ln-data-store.js +58 -11
  60. package/components/ln-date/README.md +4 -0
  61. package/components/ln-date/ln-date.js +1 -1
  62. package/components/ln-date/ln-date.schema.json +92 -92
  63. package/components/ln-date/src/date-model.js +186 -186
  64. package/components/ln-date/src/ln-date.js +3 -3
  65. package/components/ln-debug/ln-debug.js +1 -1
  66. package/components/ln-debug/ln-debug.schema.json +28 -28
  67. package/components/ln-debug/src/console-sink.js +19 -19
  68. package/components/ln-debug/src/gate.js +95 -95
  69. package/components/ln-debug/src/generated-attributes.js +2 -2
  70. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
  71. package/components/ln-dropdown/ln-dropdown.js +1 -1
  72. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  73. package/components/ln-editor/ln-editor.js +1 -1
  74. package/components/ln-editor/ln-editor.schema.json +58 -58
  75. package/components/ln-external-links/ln-external-links.js +1 -1
  76. package/components/ln-external-links/ln-external-links.schema.json +18 -18
  77. package/components/ln-fill/ln-fill.js +1 -1
  78. package/components/ln-fill/ln-fill.schema.json +23 -23
  79. package/components/ln-filter/ln-filter.js +1 -1
  80. package/components/ln-filter/ln-filter.schema.json +153 -153
  81. package/components/ln-filter/src/filter-model.js +185 -185
  82. package/components/ln-form/ln-form-dev.scss +8 -8
  83. package/components/ln-form/ln-form.js +1 -1
  84. package/components/ln-form/ln-form.schema.json +45 -45
  85. package/components/ln-http/ln-http.js +1 -1
  86. package/components/ln-http/ln-http.schema.json +6 -6
  87. package/components/ln-icon/ln-icon.schema.json +6 -6
  88. package/components/ln-include/ln-include.js +1 -1
  89. package/components/ln-include/ln-include.schema.json +23 -23
  90. package/components/ln-key/ln-key-dev.scss +18 -18
  91. package/components/ln-key/ln-key.js +1 -1
  92. package/components/ln-key/ln-key.schema.json +56 -56
  93. package/components/ln-key/src/ln-key.js +10 -10
  94. package/components/ln-link/ln-link-dev.scss +8 -8
  95. package/components/ln-link/ln-link.js +1 -1
  96. package/components/ln-link/ln-link.schema.json +22 -22
  97. package/components/ln-list/README.md +152 -152
  98. package/components/ln-list/ln-list-dev.scss +8 -8
  99. package/components/ln-list/ln-list.js +1 -1
  100. package/components/ln-list/ln-list.schema.json +166 -166
  101. package/components/ln-list/src/ln-list.js +4 -4
  102. package/components/ln-modal/README.md +4 -6
  103. package/components/ln-modal/ln-modal.js +1 -1
  104. package/components/ln-modal/ln-modal.schema.json +65 -65
  105. package/components/ln-modal/src/ln-modal.js +28 -8
  106. package/components/ln-nav/ln-nav.js +1 -1
  107. package/components/ln-nav/ln-nav.schema.json +25 -25
  108. package/components/ln-number/ln-number.js +1 -1
  109. package/components/ln-number/ln-number.schema.json +63 -63
  110. package/components/ln-number/src/number-model.js +31 -31
  111. package/components/ln-obfuscator/README.md +60 -60
  112. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  113. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
  114. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
  115. package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
  116. package/components/ln-options/ln-options.js +1 -1
  117. package/components/ln-options/ln-options.schema.json +41 -41
  118. package/components/ln-options/src/ln-options.js +1 -0
  119. package/components/ln-persist/README.md +151 -151
  120. package/components/ln-persist/ln-persist.js +1 -1
  121. package/components/ln-persist/ln-persist.schema.json +28 -28
  122. package/components/ln-persist/src/ln-persist.js +83 -83
  123. package/components/ln-picklist/README.md +230 -230
  124. package/components/ln-picklist/ln-picklist-dev.scss +22 -22
  125. package/components/ln-picklist/ln-picklist.js +1 -1
  126. package/components/ln-picklist/ln-picklist.schema.json +49 -49
  127. package/components/ln-picklist/src/ln-picklist.js +228 -228
  128. package/components/ln-popover/README.md +4 -0
  129. package/components/ln-popover/ln-popover-dev.scss +13 -13
  130. package/components/ln-popover/ln-popover.js +1 -1
  131. package/components/ln-popover/ln-popover.schema.json +82 -82
  132. package/components/ln-popover/src/ln-popover.js +9 -0
  133. package/components/ln-progress/ln-progress-dev.scss +8 -8
  134. package/components/ln-progress/ln-progress.js +1 -1
  135. package/components/ln-progress/ln-progress.schema.json +40 -40
  136. package/components/ln-router/ln-router.js +1 -1
  137. package/components/ln-router/ln-router.schema.json +76 -76
  138. package/components/ln-scroll/README.md +45 -45
  139. package/components/ln-scroll/ln-scroll.js +1 -1
  140. package/components/ln-scroll/ln-scroll.schema.json +85 -85
  141. package/components/ln-scroll/src/ln-scroll.js +150 -150
  142. package/components/ln-search/README.md +188 -188
  143. package/components/ln-search/ln-search.js +1 -1
  144. package/components/ln-search/ln-search.schema.json +103 -103
  145. package/components/ln-slug/ln-slug.js +1 -1
  146. package/components/ln-slug/ln-slug.schema.json +23 -23
  147. package/components/ln-sort/ln-sort.js +1 -1
  148. package/components/ln-sort/ln-sort.schema.json +104 -104
  149. package/components/ln-sort/src/ln-sort.js +321 -321
  150. package/components/ln-sort/src/sort-model.js +117 -117
  151. package/components/ln-sortable/ln-sortable-dev.scss +13 -13
  152. package/components/ln-sortable/ln-sortable.js +1 -1
  153. package/components/ln-sortable/ln-sortable.schema.json +36 -36
  154. package/components/ln-stat/ln-stat-dev.scss +8 -8
  155. package/components/ln-stat/ln-stat.js +1 -1
  156. package/components/ln-stat/ln-stat.schema.json +32 -32
  157. package/components/ln-stat/src/stat-model.js +28 -28
  158. package/components/ln-table/ln-table.js +1 -1
  159. package/components/ln-table/ln-table.schema.json +211 -211
  160. package/components/ln-table/src/ln-table.js +2 -0
  161. package/components/ln-table/src/table-model.js +98 -98
  162. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  163. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
  164. package/components/ln-table-coordinator/src/ln-table-coordinator.js +3 -3
  165. package/components/ln-tabs/README.md +2 -2
  166. package/components/ln-tabs/ln-tabs.js +1 -1
  167. package/components/ln-tabs/ln-tabs.schema.json +75 -75
  168. package/components/ln-tabs/src/ln-tabs.js +208 -207
  169. package/components/ln-tabs/src/tabs-model.js +82 -82
  170. package/components/ln-time/ln-time-dev.scss +13 -13
  171. package/components/ln-time/ln-time.js +1 -1
  172. package/components/ln-time/ln-time.schema.json +38 -38
  173. package/components/ln-toast/ln-toast.js +1 -1
  174. package/components/ln-toast/ln-toast.schema.json +65 -65
  175. package/components/ln-toggle/ln-toggle.js +1 -1
  176. package/components/ln-toggle/ln-toggle.schema.json +52 -52
  177. package/components/ln-tooltip/ln-tooltip.js +1 -1
  178. package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
  179. package/components/ln-translations/ln-translations.js +1 -1
  180. package/components/ln-translations/ln-translations.schema.json +120 -120
  181. package/components/ln-ui-coordinator/README.md +62 -62
  182. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
  183. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
  184. package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
  185. package/components/ln-upload/ln-upload.js +1 -1
  186. package/components/ln-upload/ln-upload.schema.json +193 -193
  187. package/components/ln-upload/src/upload-model.js +78 -78
  188. package/components/ln-validate/ln-validate.js +1 -1
  189. package/components/ln-validate/ln-validate.schema.json +50 -50
  190. package/components/ln-validate/src/validate-model.js +64 -64
  191. package/components/ln-websocket-connector/README.md +166 -166
  192. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
  193. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
  194. package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
  195. package/ln-ashlar.html-data.json +7 -8
  196. package/package.json +84 -84
  197. package/theme/brand.css +73 -73
  198. package/theme/components/_confirm.scss +6 -6
  199. package/theme/components/_picklist.scss +3 -3
  200. package/theme/config/_palette.scss +274 -274
  201. package/theme/config/_theme.scss +292 -292
  202. package/theme/config/_tokens.scss +285 -285
  203. package/theme/config/mixins/_index.scss +62 -62
  204. package/theme/config/mixins/_picklist.scss +66 -66
  205. package/theme/ln-ashlar-dev.scss +66 -66
  206. package/theme/ln-ashlar-landing.scss +56 -56
  207. package/theme/ln-ashlar-theme.scss +63 -63
  208. package/theme/presentation/_bento.scss +151 -151
  209. package/theme/presentation/_cta.scss +111 -111
  210. package/theme/presentation/_effects.scss +216 -216
  211. package/theme/presentation/_features.scss +108 -108
  212. package/theme/presentation/_footer.scss +129 -129
  213. package/theme/presentation/_hero.scss +158 -158
  214. package/theme/presentation/_layout.scss +170 -170
  215. package/theme/presentation/_navbar.scss +140 -140
  216. package/theme/presentation/_pricing.scss +262 -262
  217. package/theme/presentation/_stats.scss +87 -87
  218. package/theme/presentation/_testimonials.scss +124 -124
  219. package/theme/presentation/_tokens.scss +39 -39
  220. package/theme/presentation/_typography.scss +163 -163
  221. package/web-types.json +12 -12
@@ -1,41 +1,41 @@
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-options",
4
- "generator": "scripts/sync-ln-schemas.mjs",
5
- "attributes": {
6
- "data-ln-debug": {
7
- "direction": null,
8
- "sources": [
9
- "ln-options-dev.scss"
10
- ]
11
- },
12
- "data-ln-options": {
13
- "direction": null,
14
- "sources": [
15
- "ln-options-dev.scss",
16
- "src/ln-options.js"
17
- ],
18
- "type": "string",
19
- "fallback": "",
20
- "description": "Store or coordinator addressing to populate options from"
21
- },
22
- "data-ln-options-label": {
23
- "direction": null,
24
- "sources": [
25
- "src/ln-options.js"
26
- ],
27
- "type": "string",
28
- "fallback": "name",
29
- "description": "Field name used for option visible label text"
30
- },
31
- "data-ln-options-value": {
32
- "direction": null,
33
- "sources": [
34
- "src/ln-options.js"
35
- ],
36
- "type": "string",
37
- "fallback": "id",
38
- "description": "Field name used for option value attribute"
39
- }
40
- }
41
- }
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-options",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-debug": {
7
+ "direction": null,
8
+ "sources": [
9
+ "ln-options-dev.scss"
10
+ ]
11
+ },
12
+ "data-ln-options": {
13
+ "direction": null,
14
+ "sources": [
15
+ "ln-options-dev.scss",
16
+ "src/ln-options.js"
17
+ ],
18
+ "type": "string",
19
+ "fallback": "",
20
+ "description": "Store or coordinator addressing to populate options from"
21
+ },
22
+ "data-ln-options-label": {
23
+ "direction": null,
24
+ "sources": [
25
+ "src/ln-options.js"
26
+ ],
27
+ "type": "string",
28
+ "fallback": "name",
29
+ "description": "Field name used for option visible label text"
30
+ },
31
+ "data-ln-options-value": {
32
+ "direction": null,
33
+ "sources": [
34
+ "src/ln-options.js"
35
+ ],
36
+ "type": "string",
37
+ "fallback": "id",
38
+ "description": "Field name used for option value attribute"
39
+ }
40
+ }
41
+ }
@@ -23,6 +23,7 @@ import { registerComponent, dispatch, defineAttrs, attrSpec, attrStr } from '../
23
23
  const self = this;
24
24
 
25
25
  this._onSetData = function (e) {
26
+ if (!e.detail) return;
26
27
  self._rebuild(e.detail.data || []);
27
28
  };
28
29
  dom.addEventListener('ln-options:set-data', this._onSetData);
@@ -1,151 +1,151 @@
1
- # ln-persist
2
-
3
- > Applied as a declarative decorator attribute (`data-ln-persist`) to an element owned by a persist-enabled component.
4
- > On initialization, it reads stored state from `localStorage.getItem(storageKey)` and applies it via `el.setAttribute(attr, value)`.
5
- > A `MutationObserver` watches the component's state attribute: whenever it changes, it synchronizes the new value back to
6
- > `localStorage.setItem(storageKey, value)` with zero imperative JS API calls.
7
-
8
- `data-ln-persist` only has an effect on an element owned by a component that
9
- declares `options.persist` — currently `ln-toggle`, `ln-tabs`, `ln-sort`,
10
- `ln-search`, `ln-filter`. On any other element the attribute is a silent
11
- no-op.
12
-
13
- ---
14
-
15
- ## 🧭 Philosophy
16
-
17
- Persistence used to be five components hand-calling `persistGet`/`persistSet`
18
- from `ln-core/persist.js` inside their own constructors and attribute-sync
19
- handlers — coupling every persistence-aware component to that module's exact
20
- function signatures and JSON-value shape. `ln-persist` inverts this: each
21
- owning component declares a `persist: { attr, hashActive }` block when it
22
- calls `registerComponent(...)`, and `ln-persist` does the restoring and
23
- saving from the outside, through the same shared `findElements` funnel every
24
- component instance is already constructed through. No owning component
25
- imports anything from `ln-persist`, and `ln-persist` never imports any
26
- owning component.
27
-
28
- ---
29
-
30
- ## 🛠️ Declarative API Contract
31
-
32
- ### HTML Attributes
33
-
34
- | Attribute | Elements | Description |
35
- | :--- | :--- | :--- |
36
- | `data-ln-persist` | Any element owned by a persist-declaring component | Opt-in. Bare = use `el.id` as the storage key. `data-ln-persist="key"` = explicit key override. |
37
- | `data-ln-persist-scope` | Same as `data-ln-persist` | Opt-in, independent attribute. `"page"` scopes the stored key to the current URL pathname. Absent = global (same key on every page). |
38
-
39
- There is no Programmatic JS API — `persistGet`/`persistSet`/`persistRemove`/
40
- `persistClear` do not exist. The attribute is the sole contract.
41
-
42
- ### Storage key format
43
-
44
- Global by default:
45
-
46
- ```
47
- ln:{id}:{attr}
48
- ```
49
-
50
- Page-scoped, via `data-ln-persist-scope="page"`:
51
-
52
- ```
53
- ln:{id}:{pagePath}:{attr}
54
- ```
55
-
56
- - `id` — the `data-ln-persist="key"` value if non-empty, else `el.id`. Missing
57
- both → `console.warn('[ln-persist] Element requires id or data-ln-persist="key"', el)`, persistence skipped.
58
- - `attr` — the owning component's declared `persist.attr` (e.g.
59
- `data-ln-toggle`, `data-ln-tabs-active`, `data-ln-sort-state`,
60
- `data-ln-search`, `data-ln-filter-values`).
61
- - `pagePath` — `location.pathname`, lowercase, trailing slash stripped, or
62
- `/` for root.
63
-
64
- This orphans every key written under the previous
65
- `ln:{component}:{pathname}:{id}` format — harmless leftover localStorage,
66
- never read again.
67
-
68
- Persisted values are raw attribute strings — no `JSON.stringify`/`parse`.
69
- The restore/save unit is always "one DOM attribute's string value," not the
70
- old composite objects (`{field,column,direction,targetId}` for sort,
71
- `{key,values}` for filter).
72
-
73
- ### Hash wins
74
-
75
- When the owning component's `persist.hashActive(el)` returns `true`,
76
- `ln-persist` skips both restore and save for that element entirely — the URL
77
- hash is authoritative, never double-written to both hash and `localStorage`.
78
- This is a behaviour change for `ln-search`/`ln-filter`, which previously
79
- wrote both; `ln-tabs` was already fully hash-gated on save, `ln-sort` only on
80
- restore.
81
-
82
- ---
83
-
84
- ## ⚠️ Common Pitfalls
85
-
86
- - **Persisting without a stable key.** `data-ln-persist` with no value falls
87
- back to `el.id`; an element with neither silently skips persistence
88
- (`console.warn`s once).
89
- - **Expecting `data-ln-persist-scope="page"` alone to persist anything.**
90
- It only changes the key shape of an element that already has
91
- `data-ln-persist` — it's not a standalone opt-in.
92
- - **Expecting a composite persisted value.** Every owner persists exactly one
93
- attribute's string value now, not an object.
94
-
95
- ---
96
-
97
- ## 🔧 Internals
98
-
99
- Source: `components/ln-persist/src/ln-persist.js`. No `DOM_SELECTOR`,
100
- `DOM_ATTRIBUTE`, or instance — registry-driven, sink-filling module.
101
- Structurally closest existing precedent: `components/ln-debug/src/gate.js`
102
- (nullable sink, single module-level idempotency guard, no instance).
103
-
104
- ### The sink
105
-
106
- `ln-core` exposes a nullable `setPersistSink(sink)`, mirroring
107
- `setDebugSink` exactly. `findElements` — the single funnel every component
108
- instance in the library is constructed through (boot sweep, each
109
- component's own `childList` observer, and the shared observer's legacy
110
- upgrade path) — calls `window.lnCore._persistSink(el, selector)` for any
111
- element carrying `data-ln-persist`, immediately before constructing that
112
- element's owning component instance. `ln-persist` fills the slot with
113
- `_persistSink`, which looks up the matching entry in `registry.persist`
114
- (populated by `registerComponent`'s `persist:` option) by `selector`, and —
115
- unless `persist.hashActive(el)` is true — writes the stored value onto `el`
116
- with `setAttribute`, synchronously, before the owner's constructor runs.
117
-
118
- ### Ordering — no boot gate
119
-
120
- `_registerAttrEntry(...)` always runs before that same component's own
121
- `boot()` — see `ln-core`'s `registerComponent`. So by the time any
122
- owner's `findElements` call reaches the sink, that owner's own `persist:`
123
- declaration is already in `registry.persist`, regardless of what order the
124
- other four owners import in. `ln-persist` only needs to precede the *first*
125
- owner's import so `setPersistSink(...)` has already run — a plain
126
- import-order invariant (`components/index.js` imports `ln-persist` before
127
- `ln-tabs`, the earliest of the five owners), not a gate on anything else's
128
- boot timing.
129
-
130
- ### Path 3 — elements inserted after initial boot
131
-
132
- A node inserted later by `ln-include`, `ln-router`, or an `ln-table` render
133
- hits the same owning component's `childList` observer, which calls the same
134
- `findElements`, which runs the same sink check. Restores correctly for
135
- free — no separate mechanism, no gap.
136
-
137
- ### Save-path wiring
138
-
139
- Wired lazily, the first time the sink is invoked for a given `persist.attr`
140
- — not at `ln-persist`'s own module evaluation, since no owner has
141
- registered yet at that point. `observeAttributes([persist.attr], ...)` is
142
- called once per `persist.attr`, guarded by a module-level `_wiredAttrs` Set.
143
- The save handler re-derives the key, checks `hashActive`, and writes or
144
- removes the `localStorage` entry on every subsequent mutation of that
145
- attribute.
146
-
147
- ### Destroy
148
-
149
- None — no per-element instance to tear down. This module installs a
150
- document-level sink for the life of the page, same category as `ln-debug`'s
151
- gate.
1
+ # ln-persist
2
+
3
+ > Applied as a declarative decorator attribute (`data-ln-persist`) to an element owned by a persist-enabled component.
4
+ > On initialization, it reads stored state from `localStorage.getItem(storageKey)` and applies it via `el.setAttribute(attr, value)`.
5
+ > A `MutationObserver` watches the component's state attribute: whenever it changes, it synchronizes the new value back to
6
+ > `localStorage.setItem(storageKey, value)` with zero imperative JS API calls.
7
+
8
+ `data-ln-persist` only has an effect on an element owned by a component that
9
+ declares `options.persist` — currently `ln-toggle`, `ln-tabs`, `ln-sort`,
10
+ `ln-search`, `ln-filter`. On any other element the attribute is a silent
11
+ no-op.
12
+
13
+ ---
14
+
15
+ ## 🧭 Philosophy
16
+
17
+ Persistence used to be five components hand-calling `persistGet`/`persistSet`
18
+ from `ln-core/persist.js` inside their own constructors and attribute-sync
19
+ handlers — coupling every persistence-aware component to that module's exact
20
+ function signatures and JSON-value shape. `ln-persist` inverts this: each
21
+ owning component declares a `persist: { attr, hashActive }` block when it
22
+ calls `registerComponent(...)`, and `ln-persist` does the restoring and
23
+ saving from the outside, through the same shared `findElements` funnel every
24
+ component instance is already constructed through. No owning component
25
+ imports anything from `ln-persist`, and `ln-persist` never imports any
26
+ owning component.
27
+
28
+ ---
29
+
30
+ ## 🛠️ Declarative API Contract
31
+
32
+ ### HTML Attributes
33
+
34
+ | Attribute | Elements | Description |
35
+ | :--- | :--- | :--- |
36
+ | `data-ln-persist` | Any element owned by a persist-declaring component | Opt-in. Bare = use `el.id` as the storage key. `data-ln-persist="key"` = explicit key override. |
37
+ | `data-ln-persist-scope` | Same as `data-ln-persist` | Opt-in, independent attribute. `"page"` scopes the stored key to the current URL pathname. Absent = global (same key on every page). |
38
+
39
+ There is no Programmatic JS API — `persistGet`/`persistSet`/`persistRemove`/
40
+ `persistClear` do not exist. The attribute is the sole contract.
41
+
42
+ ### Storage key format
43
+
44
+ Global by default:
45
+
46
+ ```
47
+ ln:{id}:{attr}
48
+ ```
49
+
50
+ Page-scoped, via `data-ln-persist-scope="page"`:
51
+
52
+ ```
53
+ ln:{id}:{pagePath}:{attr}
54
+ ```
55
+
56
+ - `id` — the `data-ln-persist="key"` value if non-empty, else `el.id`. Missing
57
+ both → `console.warn('[ln-persist] Element requires id or data-ln-persist="key"', el)`, persistence skipped.
58
+ - `attr` — the owning component's declared `persist.attr` (e.g.
59
+ `data-ln-toggle`, `data-ln-tabs-active`, `data-ln-sort-state`,
60
+ `data-ln-search`, `data-ln-filter-values`).
61
+ - `pagePath` — `location.pathname`, lowercase, trailing slash stripped, or
62
+ `/` for root.
63
+
64
+ This orphans every key written under the previous
65
+ `ln:{component}:{pathname}:{id}` format — harmless leftover localStorage,
66
+ never read again.
67
+
68
+ Persisted values are raw attribute strings — no `JSON.stringify`/`parse`.
69
+ The restore/save unit is always "one DOM attribute's string value," not the
70
+ old composite objects (`{field,column,direction,targetId}` for sort,
71
+ `{key,values}` for filter).
72
+
73
+ ### Hash wins
74
+
75
+ When the owning component's `persist.hashActive(el)` returns `true`,
76
+ `ln-persist` skips both restore and save for that element entirely — the URL
77
+ hash is authoritative, never double-written to both hash and `localStorage`.
78
+ This is a behaviour change for `ln-search`/`ln-filter`, which previously
79
+ wrote both; `ln-tabs` was already fully hash-gated on save, `ln-sort` only on
80
+ restore.
81
+
82
+ ---
83
+
84
+ ## ⚠️ Common Pitfalls
85
+
86
+ - **Persisting without a stable key.** `data-ln-persist` with no value falls
87
+ back to `el.id`; an element with neither silently skips persistence
88
+ (`console.warn`s once).
89
+ - **Expecting `data-ln-persist-scope="page"` alone to persist anything.**
90
+ It only changes the key shape of an element that already has
91
+ `data-ln-persist` — it's not a standalone opt-in.
92
+ - **Expecting a composite persisted value.** Every owner persists exactly one
93
+ attribute's string value now, not an object.
94
+
95
+ ---
96
+
97
+ ## 🔧 Internals
98
+
99
+ Source: `components/ln-persist/src/ln-persist.js`. No `DOM_SELECTOR`,
100
+ `DOM_ATTRIBUTE`, or instance — registry-driven, sink-filling module.
101
+ Structurally closest existing precedent: `components/ln-debug/src/gate.js`
102
+ (nullable sink, single module-level idempotency guard, no instance).
103
+
104
+ ### The sink
105
+
106
+ `ln-core` exposes a nullable `setPersistSink(sink)`, mirroring
107
+ `setDebugSink` exactly. `findElements` — the single funnel every component
108
+ instance in the library is constructed through (boot sweep, each
109
+ component's own `childList` observer, and the shared observer's legacy
110
+ upgrade path) — calls `window.lnCore._persistSink(el, selector)` for any
111
+ element carrying `data-ln-persist`, immediately before constructing that
112
+ element's owning component instance. `ln-persist` fills the slot with
113
+ `_persistSink`, which looks up the matching entry in `registry.persist`
114
+ (populated by `registerComponent`'s `persist:` option) by `selector`, and —
115
+ unless `persist.hashActive(el)` is true — writes the stored value onto `el`
116
+ with `setAttribute`, synchronously, before the owner's constructor runs.
117
+
118
+ ### Ordering — no boot gate
119
+
120
+ `_registerAttrEntry(...)` always runs before that same component's own
121
+ `boot()` — see `ln-core`'s `registerComponent`. So by the time any
122
+ owner's `findElements` call reaches the sink, that owner's own `persist:`
123
+ declaration is already in `registry.persist`, regardless of what order the
124
+ other four owners import in. `ln-persist` only needs to precede the *first*
125
+ owner's import so `setPersistSink(...)` has already run — a plain
126
+ import-order invariant (`components/index.js` imports `ln-persist` before
127
+ `ln-tabs`, the earliest of the five owners), not a gate on anything else's
128
+ boot timing.
129
+
130
+ ### Path 3 — elements inserted after initial boot
131
+
132
+ A node inserted later by `ln-include`, `ln-router`, or an `ln-table` render
133
+ hits the same owning component's `childList` observer, which calls the same
134
+ `findElements`, which runs the same sink check. Restores correctly for
135
+ free — no separate mechanism, no gap.
136
+
137
+ ### Save-path wiring
138
+
139
+ Wired lazily, the first time the sink is invoked for a given `persist.attr`
140
+ — not at `ln-persist`'s own module evaluation, since no owner has
141
+ registered yet at that point. `observeAttributes([persist.attr], ...)` is
142
+ called once per `persist.attr`, guarded by a module-level `_wiredAttrs` Set.
143
+ The save handler re-derives the key, checks `hashActive`, and writes or
144
+ removes the `localStorage` entry on every subsequent mutation of that
145
+ attribute.
146
+
147
+ ### Destroy
148
+
149
+ None — no per-element instance to tear down. This module installs a
150
+ document-level sink for the life of the page, same category as `ln-debug`'s
151
+ gate.
@@ -1 +1 @@
1
- (function(){"use strict";function D(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function C(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let o=null;for(let s=1;s<t.length;s++)if(t[s]&&t[s].nodeType===1){o=t[s];break}if(!C(o))return}e.apply(console,t)}}function L(e){window.lnCore=window.lnCore||{},window.lnCore._persistSink=e}function I(e,t,n){const o=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,o),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:o}))}function b(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){b(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const m={};function F(e,t){m[e]=t}function E(e){return m[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=F,window.lnCore.getDataMapper=E);function R(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,b(function(){new MutationObserver(function(){I(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const y={};function T(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];y[n]=t}function W(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return y[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=T,window.lnCore.getLocaleFallback=W,window.lnCore.ensureLocaleObserver=R);const h={};function B(e,t){h[e]||(h[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=h[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function A(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let r=0;r<n.length;r++){const a=n[r],l=a.getAttribute("data-ln-field");t[l]!=null&&(a.textContent=t[l])}const o=e.querySelectorAll("[data-ln-attr]");for(let r=0;r<o.length;r++){const a=o[r],l=a.getAttribute("data-ln-attr").split(",");for(let u=0;u<l.length;u++){const d=l[u].trim().split(":");if(d.length!==2)continue;const c=d[0].trim(),f=d[1].trim();t[f]!=null&&a.setAttribute(c,t[f])}}const s=e.querySelectorAll("[data-ln-show]");for(let r=0;r<s.length;r++){const a=s[r],l=a.getAttribute("data-ln-show");l in t&&a.classList.toggle("hidden",!t[l])}const i=e.querySelectorAll("[data-ln-class]");for(let r=0;r<i.length;r++){const a=i[r],l=a.getAttribute("data-ln-class").split(",");for(let u=0;u<l.length;u++){const d=l[u].trim().split(":");if(d.length!==2)continue;const c=d[0].trim(),f=d[1].trim();f in t&&a.classList.toggle(c,!!t[f])}}return e}function M(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let o=0;o<n.length;o++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[o],t??null),n[o].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)A(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function _(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const i=n.currentNode;i.textContent.indexOf("{{")!==-1&&(i.textContent=i.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(r,a){return t[a]!==void 0?t[a]:""}))}const o=function(i,r){return t[r]!==void 0?t[r]:""},s=Array.from(e.querySelectorAll("*"));e.nodeType===1&&s.push(e);for(let i=0;i<s.length;i++){const r=s[i],a=r.attributes;for(let l=0;l<a.length;l++){const u=a[l];u.value.indexOf("{{")!==-1&&r.setAttribute(u.name,u.value.replace(/\{\{\s*(\w+)\s*\}\}/g,o))}}return e}function q(e,t,n,o,s,i){const r={};for(let l=0;l<e.children.length;l++){const u=e.children[l],d=u.getAttribute("data-ln-render-key");d&&(r[d]=u)}const a=document.createDocumentFragment();for(let l=0;l<t.length;l++){const u=t[l],d=String(o(u));let c=r[d];if(c)s(c,u,l);else{const f=B(n,i);if(!f||(_(f,u),c=f.firstElementChild,!c))continue;c.setAttribute("data-ln-render-key",d),s(c,u,l)}a.appendChild(c)}e.textContent="",e.appendChild(a)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=_,window.lnCore.fill=A,window.lnCore.lnFill=M,window.lnCore.renderList=q);function V(e,t,n,o,s){if(!e||t===null)return!0;const i=o||"ln-component",r=e.type;if(r==="trigger"||r==="marker"||r==="string"||r==="list"||!r||t===""&&e.fallback!==void 0)return!0;const a=l=>{s?console.warn(l,s):console.warn(l)};if(r==="boolean"){const l=t.trim().toLowerCase();return l!==""&&l!=="true"&&l!=="false"&&l!=="1"&&l!=="0"?(a(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(r==="enum"){const l=e.values||[];return l.includes(t)?!0:(a(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${l.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(r==="integer"){if(!/^-?\d+$/.test(t))return a(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const l=parseInt(t,10);return e.min!==void 0&&l<e.min?(a(`[${i}] Value ${l} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&l>e.max?(a(`[${i}] Value ${l} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(r==="float")return isNaN(Number(t))?(a(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(r==="json")try{return JSON.parse(t),!0}catch(l){return a(`[${i}] Invalid JSON for attribute "${n}": ${l.message}. Fallback: ${e.fallback}.`),!1}return!0}function j(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function N(e,t,n,o){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",r=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&r.push(e);for(const a of r)if(!a[n]){window.lnCore._persistSink&&a.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(a,t);try{a[n]=new o(a)}catch(l){console.error("["+n+"] init failed",a,l)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function v(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function K(e){const t=v(),n=e.observed||[];for(let o=0;o<n.length;o++){const s=n[o];t.byAttr.has(s)||t.byAttr.set(s,[]),t.byAttr.get(s).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const o in e.attributes){t.byDeclaredAttr.has(o)||t.byDeclaredAttr.set(o,[]);const s=t.byDeclaredAttr.get(o);s.some(i=>i.componentTag===e.componentTag)||s.push({spec:e.attributes[o],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const o=j(e);if(o===null)t.reactiveWildcard.push(e);else for(const s of o)t.byReactive.has(s)||t.byReactive.set(s,[]),t.byReactive.get(s).push(e)}e.persist&&t.persist.push(e)}function k(e,t,n,o){for(let s=0;s<e.length;s++){const i=e[s];if(!t[i.attribute])continue;const r=i.effects&&i.effects[n];r?r(t,n,o):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,o)}}function H(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const o=v(),s=o.byAttr.get(n);if(D()&&o.byDeclaredAttr&&o.byDeclaredAttr.has(n)&&C(t)){const i=o.byDeclaredAttr.get(n),r=t.getAttribute(n);for(let a=0;a<i.length;a++)V(i[a].spec,r,n,i[a].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||s)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=o.byReactive.get(n);i&&k(i,t,n,e.oldValue),o.reactiveWildcard.length&&k(o.reactiveWildcard,t,n,e.oldValue)}if(s)for(let i=0;i<s.length;i++){const r=s[i];if(r.handler){r.handler(t,n,e.oldValue);continue}r.onAttributeChange&&t[r.attribute]?r.onAttributeChange(t,n):(N(t,r.selector,r.attribute,r.ComponentFn),r.onInit&&r.onInit(t))}}function U(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,b(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{H(t[n])}catch(o){console.error("[ln-core] mutation handler failed",t[n].target,o)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function G(e,t){K({observed:e,handler:t}),U()}function S(e){return(e||"").replace(/^#/,"")}function p(e){const t=e===void 0?location.hash:e,n={},o=S(t);if(!o)return n;const s=o.split("&");for(let i=0;i<s.length;i++){const r=s[i];if(!r)continue;const a=r.indexOf(":"),l=a>-1?r.slice(0,a):r,u=a>-1?r.slice(a+1):"";if(l)try{n[l]=decodeURIComponent(u)}catch{n[l]=u}}return n}function J(e){if(!e)return null;const t=p();return e in t?t[e]:null}function P(e,t){if(!e)return;const n=p();t==null?delete n[e]:n[e]=String(t);const s=Object.keys(n).map(function(i){const r=n[i];return r===""?i:i+":"+encodeURIComponent(r)}).join("&");S(location.hash)!==s&&(location.hash=s)}function Q(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function X(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const o=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return o?t?o+"-"+t:o:t||null}function z(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function Y(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function Z(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function ee(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),o=e.slice(t+1),s=o?o.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:s}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=p,window.lnCore.hashGet=J,window.lnCore.hashSet=P,window.lnCore.hashLinkClick=Q,window.lnCore.resolveHashNamespace=X,window.lnCore.hashSortEncode=z,window.lnCore.hashSortDecode=Y,window.lnCore.hashFilterEncode=Z,window.lnCore.hashFilterDecode=ee),(function(){if(window.lnCore&&window.lnCore._persistBound)return;window.lnCore=window.lnCore||{},window.lnCore._persistBound=!0;function e(){return location.pathname.replace(/\/+$/,"").toLowerCase()||"/"}function t(r,a){const l=r.getAttribute("data-ln-persist"),u=l!==null&&l!==""?l:r.id;return u?r.getAttribute("data-ln-persist-scope")==="page"?"ln:"+u+":"+e()+":"+a:"ln:"+u+":"+a:(console.warn('[ln-persist] Element requires id or data-ln-persist="key"',r),null)}let n=null;function o(){if(n!==null)return n;try{if(typeof localStorage>"u")return n=!1;const r="__ln_persist_test__";return localStorage.setItem(r,r),localStorage.removeItem(r),n=!0}catch{return n=!1}}const s=new Set;function i(r,a){const l=window.lnCore&&window.lnCore._attrRegistry,u=l&&l.persist||[];let d=null;for(let w=0;w<u.length;w++)if(u[w].selector===a){d=u[w];break}if(!d)return;const c=d.persist;if(s.has(c.attr)||(s.add(c.attr),G([c.attr],function(w,x){if(!w.hasAttribute("data-ln-persist")||c.hashActive&&c.hashActive(w))return;const g=t(w,x);if(!g||!o())return;const O=w.getAttribute(x);try{O===null?localStorage.removeItem(g):localStorage.setItem(g,O)}catch{}})),c.hashActive&&c.hashActive(r))return;const f=t(r,c.attr);if(!f||!o())return;const $=localStorage.getItem(f);$!==null&&r.setAttribute(c.attr,$)}L(i)})()})();
1
+ (function(){"use strict";function O(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function C(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let o=null;for(let s=1;s<t.length;s++)if(t[s]&&t[s].nodeType===1){o=t[s];break}if(!C(o))return}e.apply(console,t)}}function I(e){window.lnCore=window.lnCore||{},window.lnCore._persistSink=e}function L(e,t,n){const o=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,o),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:o}))}function p(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){p(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const m={};function F(e,t){m[e]=t}function E(e){return m[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=F,window.lnCore.getDataMapper=E);function R(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,p(function(){new MutationObserver(function(){L(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const y={};function T(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];y[n]=t}function W(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return y[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=T,window.lnCore.getLocaleFallback=W,window.lnCore.ensureLocaleObserver=R);const b={};function M(e,t){b[e]||(b[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=b[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function A(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let r=0;r<n.length;r++){const a=n[r],l=a.getAttribute("data-ln-field");t[l]!=null&&(a.textContent=t[l])}const o=e.querySelectorAll("[data-ln-attr]");for(let r=0;r<o.length;r++){const a=o[r],l=a.getAttribute("data-ln-attr").split(",");for(let u=0;u<l.length;u++){const d=l[u].trim().split(":");if(d.length!==2)continue;const c=d[0].trim(),f=d[1].trim();t[f]!=null&&a.setAttribute(c,t[f])}}const s=e.querySelectorAll("[data-ln-show]");for(let r=0;r<s.length;r++){const a=s[r],l=a.getAttribute("data-ln-show");l in t&&a.classList.toggle("hidden",!t[l])}const i=e.querySelectorAll("[data-ln-class]");for(let r=0;r<i.length;r++){const a=i[r],l=a.getAttribute("data-ln-class").split(",");for(let u=0;u<l.length;u++){const d=l[u].trim().split(":");if(d.length!==2)continue;const c=d[0].trim(),f=d[1].trim();f in t&&a.classList.toggle(c,!!t[f])}}return e}function B(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let o=0;o<n.length;o++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[o],t??null),n[o].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)A(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function x(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const i=n.currentNode;i.textContent.indexOf("{{")!==-1&&(i.textContent=i.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(r,a){return t[a]!==void 0?t[a]:""}))}const o=function(i,r){return t[r]!==void 0?t[r]:""},s=Array.from(e.querySelectorAll("*"));e.nodeType===1&&s.push(e);for(let i=0;i<s.length;i++){const r=s[i],a=r.attributes;for(let l=0;l<a.length;l++){const u=a[l];u.value.indexOf("{{")!==-1&&r.setAttribute(u.name,u.value.replace(/\{\{\s*(\w+)\s*\}\}/g,o))}}return e}function q(e,t,n,o,s,i){const r={};for(let l=0;l<e.children.length;l++){const u=e.children[l],d=u.getAttribute("data-ln-render-key");d&&(r[d]=u)}const a=document.createDocumentFragment();for(let l=0;l<t.length;l++){const u=t[l],d=String(o(u));let c=r[d];if(c)s(c,u,l);else{const f=M(n,i);if(!f||(x(f,u),c=f.firstElementChild,!c))continue;c.setAttribute("data-ln-render-key",d),s(c,u,l)}a.appendChild(c)}e.textContent="",e.appendChild(a)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=x,window.lnCore.fill=A,window.lnCore.lnFill=B,window.lnCore.renderList=q);function V(e,t,n,o,s){if(!e||t===null)return!0;const i=o||"ln-component",r=e.type;if(r==="trigger"||r==="marker"||r==="string"||r==="list"||!r||t===""&&e.fallback!==void 0)return!0;const a=l=>{s?console.warn(l,s):console.warn(l)};if(r==="boolean"){const l=t.trim().toLowerCase();return l!==""&&l!=="true"&&l!=="false"&&l!=="1"&&l!=="0"?(a(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(r==="enum"){const l=e.values||[];return l.includes(t)?!0:(a(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${l.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(r==="integer"){if(!/^-?\d+$/.test(t))return a(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const l=parseInt(t,10);return e.min!==void 0&&l<e.min?(a(`[${i}] Value ${l} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&l>e.max?(a(`[${i}] Value ${l} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(r==="float")return isNaN(Number(t))?(a(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(r==="json")try{return JSON.parse(t),!0}catch(l){return a(`[${i}] Invalid JSON for attribute "${n}": ${l.message}. Fallback: ${e.fallback}.`),!1}return!0}function j(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function N(e,t,n,o){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",r=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&r.push(e);for(const a of r)if(!a[n]){window.lnCore._persistSink&&a.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(a,t);try{a[n]=new o(a)}catch(l){console.error("["+n+"] init failed",a,l)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function _(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function U(e){const t=_(),n=e.observed||[];for(let o=0;o<n.length;o++){const s=n[o];t.byAttr.has(s)||t.byAttr.set(s,[]),t.byAttr.get(s).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const o in e.attributes){t.byDeclaredAttr.has(o)||t.byDeclaredAttr.set(o,[]);const s=t.byDeclaredAttr.get(o);s.some(i=>i.componentTag===e.componentTag)||s.push({spec:e.attributes[o],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const o=j(e);if(o===null)t.reactiveWildcard.push(e);else for(const s of o)t.byReactive.has(s)||t.byReactive.set(s,[]),t.byReactive.get(s).push(e)}e.persist&&t.persist.push(e)}function v(e,t,n,o){for(let s=0;s<e.length;s++){const i=e[s];if(!t[i.attribute])continue;const r=i.effects&&i.effects[n];r?r(t,n,o):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,o)}}function K(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const o=_(),s=o.byAttr.get(n);if(O()&&o.byDeclaredAttr&&o.byDeclaredAttr.has(n)&&C(t)){const i=o.byDeclaredAttr.get(n),r=t.getAttribute(n);for(let a=0;a<i.length;a++)V(i[a].spec,r,n,i[a].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||s)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=o.byReactive.get(n);i&&v(i,t,n,e.oldValue),o.reactiveWildcard.length&&v(o.reactiveWildcard,t,n,e.oldValue)}if(s)for(let i=0;i<s.length;i++){const r=s[i];if(r.handler){r.handler(t,n,e.oldValue);continue}r.onAttributeChange&&t[r.attribute]?r.onAttributeChange(t,n):(N(t,r.selector,r.attribute,r.ComponentFn),r.onInit&&r.onInit(t))}}function H(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,p(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{K(t[n])}catch(o){console.error("[ln-core] mutation handler failed",t[n].target,o)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function G(e,t){U({observed:e,handler:t}),H()}function k(e){return(e||"").replace(/^#/,"")}function h(e){const t=e===void 0?location.hash:e,n={},o=k(t);if(!o)return n;const s=o.split("&");for(let i=0;i<s.length;i++){const r=s[i];if(!r)continue;const a=r.indexOf(":"),l=a>-1?r.slice(0,a):r,u=a>-1?r.slice(a+1):"";if(l)try{n[l]=decodeURIComponent(u)}catch{n[l]=u}}return n}function J(e){if(!e)return null;const t=h();return e in t?t[e]:null}function P(e,t){if(!e)return;const n=h();t==null?delete n[e]:n[e]=String(t);const s=Object.keys(n).map(function(i){const r=n[i];return r===""?i:i+":"+encodeURIComponent(r)}).join("&");k(location.hash)!==s&&(location.hash=s)}function Q(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function X(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const o=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return o?t?o+"-"+t:o:t||null}function z(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function Y(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function Z(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function ee(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),o=e.slice(t+1),s=o?o.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:s}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=h,window.lnCore.hashGet=J,window.lnCore.hashSet=P,window.lnCore.hashLinkClick=Q,window.lnCore.resolveHashNamespace=X,window.lnCore.hashSortEncode=z,window.lnCore.hashSortDecode=Y,window.lnCore.hashFilterEncode=Z,window.lnCore.hashFilterDecode=ee);function te(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.uuid=te),(function(){if(window.lnCore&&window.lnCore._persistBound)return;window.lnCore=window.lnCore||{},window.lnCore._persistBound=!0;function e(){return location.pathname.replace(/\/+$/,"").toLowerCase()||"/"}function t(r,a){const l=r.getAttribute("data-ln-persist"),u=l!==null&&l!==""?l:r.id;return u?r.getAttribute("data-ln-persist-scope")==="page"?"ln:"+u+":"+e()+":"+a:"ln:"+u+":"+a:(console.warn('[ln-persist] Element requires id or data-ln-persist="key"',r),null)}let n=null;function o(){if(n!==null)return n;try{if(typeof localStorage>"u")return n=!1;const r="__ln_persist_test__";return localStorage.setItem(r,r),localStorage.removeItem(r),n=!0}catch{return n=!1}}const s=new Set;function i(r,a){const l=window.lnCore&&window.lnCore._attrRegistry,u=l&&l.persist||[];let d=null;for(let w=0;w<u.length;w++)if(u[w].selector===a){d=u[w];break}if(!d)return;const c=d.persist;if(s.has(c.attr)||(s.add(c.attr),G([c.attr],function(w,$){if(!w.hasAttribute("data-ln-persist")||c.hashActive&&c.hashActive(w))return;const g=t(w,$);if(!g||!o())return;const D=w.getAttribute($);try{D===null?localStorage.removeItem(g):localStorage.setItem(g,D)}catch{}})),c.hashActive&&c.hashActive(r))return;const f=t(r,c.attr);if(!f||!o())return;const S=localStorage.getItem(f);S!==null&&r.setAttribute(c.attr,S)}I(i)})()})();
@@ -1,28 +1,28 @@
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-persist",
4
- "generator": "scripts/sync-ln-schemas.mjs",
5
- "attributes": {
6
- "data-ln-persist": {
7
- "direction": "author",
8
- "sources": [
9
- "src/ln-persist.js"
10
- ],
11
- "type": "string",
12
- "description": "Storage key identifier for persisting component state in localStorage"
13
- },
14
- "data-ln-persist-scope": {
15
- "direction": "author",
16
- "sources": [
17
- "src/ln-persist.js"
18
- ],
19
- "type": "enum",
20
- "values": [
21
- "global",
22
- "page"
23
- ],
24
- "fallback": "global",
25
- "description": "Persistence scope namespace (global or per-page path)"
26
- }
27
- }
28
- }
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-persist",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-persist": {
7
+ "direction": "author",
8
+ "sources": [
9
+ "src/ln-persist.js"
10
+ ],
11
+ "type": "string",
12
+ "description": "Storage key identifier for persisting component state in localStorage"
13
+ },
14
+ "data-ln-persist-scope": {
15
+ "direction": "author",
16
+ "sources": [
17
+ "src/ln-persist.js"
18
+ ],
19
+ "type": "enum",
20
+ "values": [
21
+ "global",
22
+ "page"
23
+ ],
24
+ "fallback": "global",
25
+ "description": "Persistence scope namespace (global or per-page path)"
26
+ }
27
+ }
28
+ }