@livenetworks/ashlar 2.0.7 → 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 (207) hide show
  1. package/LICENSE +641 -641
  2. package/components/COMPONENTS.md +1116 -1116
  3. package/components/index.js +81 -81
  4. package/components/ln-accordion/ln-accordion-dev.scss +8 -8
  5. package/components/ln-accordion/ln-accordion.js +1 -1
  6. package/components/ln-accordion/ln-accordion.schema.json +30 -30
  7. package/components/ln-ajax/ln-ajax-dev.scss +8 -8
  8. package/components/ln-ajax/ln-ajax.js +1 -1
  9. package/components/ln-ajax/ln-ajax.schema.json +30 -30
  10. package/components/ln-api-connector/ln-api-connector.js +1 -1
  11. package/components/ln-api-connector/ln-api-connector.schema.json +96 -96
  12. package/components/ln-api-connector/src/ln-api-connector.js +1 -0
  13. package/components/ln-api-queue/ln-api-queue.js +1 -1
  14. package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
  15. package/components/ln-api-queue/src/ln-api-queue.js +3 -13
  16. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
  17. package/components/ln-autoresize/ln-autoresize.js +1 -1
  18. package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
  19. package/components/ln-autosave/ln-autosave-dev.scss +13 -13
  20. package/components/ln-autosave/ln-autosave.js +1 -1
  21. package/components/ln-autosave/ln-autosave.schema.json +48 -48
  22. package/components/ln-autosave/src/autosave-model.js +34 -34
  23. package/components/ln-chart/ln-chart.js +1 -1
  24. package/components/ln-chart/ln-chart.schema.json +146 -146
  25. package/components/ln-chart/src/chart-model.js +154 -154
  26. package/components/ln-circular-progress/ln-circular-progress.js +1 -1
  27. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
  28. package/components/ln-confirm/ln-confirm-dev.scss +24 -24
  29. package/components/ln-confirm/ln-confirm.js +1 -1
  30. package/components/ln-confirm/ln-confirm.schema.json +66 -66
  31. package/components/ln-confirm/src/ln-confirm.js +178 -178
  32. package/components/ln-core/crypto.js +286 -265
  33. package/components/ln-core/dom.js +115 -115
  34. package/components/ln-core/form.js +136 -136
  35. package/components/ln-core/helpers.js +33 -33
  36. package/components/ln-core/http.js +48 -48
  37. package/components/ln-core/index.js +1 -1
  38. package/components/ln-core/ln-core.schema.json +109 -109
  39. package/components/ln-core/locale.js +55 -55
  40. package/components/ln-core/matching.js +78 -78
  41. package/components/ln-core/number.js +125 -125
  42. package/components/ln-core/template.js +206 -206
  43. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  44. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
  45. package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
  46. package/components/ln-data-coordinator/README.md +9 -38
  47. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
  48. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  49. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
  50. package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
  51. package/components/ln-data-coordinator/src/ln-data-coordinator.js +428 -1308
  52. package/components/ln-data-store/ln-data-store-dev.scss +13 -13
  53. package/components/ln-data-store/ln-data-store.js +1 -1
  54. package/components/ln-data-store/ln-data-store.schema.json +84 -84
  55. package/components/ln-data-store/src/data-store-model.js +184 -184
  56. package/components/ln-data-store/src/ln-data-store.js +1 -1
  57. package/components/ln-date/ln-date.js +1 -1
  58. package/components/ln-date/ln-date.schema.json +92 -92
  59. package/components/ln-date/src/date-model.js +186 -186
  60. package/components/ln-debug/ln-debug.js +1 -1
  61. package/components/ln-debug/ln-debug.schema.json +28 -28
  62. package/components/ln-debug/src/console-sink.js +19 -19
  63. package/components/ln-debug/src/gate.js +95 -95
  64. package/components/ln-debug/src/generated-attributes.js +2 -2
  65. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
  66. package/components/ln-dropdown/ln-dropdown.js +1 -1
  67. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  68. package/components/ln-editor/ln-editor.js +1 -1
  69. package/components/ln-editor/ln-editor.schema.json +58 -58
  70. package/components/ln-external-links/ln-external-links.js +1 -1
  71. package/components/ln-external-links/ln-external-links.schema.json +18 -18
  72. package/components/ln-fill/ln-fill.js +1 -1
  73. package/components/ln-fill/ln-fill.schema.json +23 -23
  74. package/components/ln-filter/ln-filter.js +1 -1
  75. package/components/ln-filter/ln-filter.schema.json +153 -153
  76. package/components/ln-filter/src/filter-model.js +185 -185
  77. package/components/ln-form/ln-form-dev.scss +8 -8
  78. package/components/ln-form/ln-form.js +1 -1
  79. package/components/ln-form/ln-form.schema.json +45 -45
  80. package/components/ln-http/ln-http.js +1 -1
  81. package/components/ln-http/ln-http.schema.json +6 -6
  82. package/components/ln-icon/ln-icon.schema.json +6 -6
  83. package/components/ln-include/ln-include.js +1 -1
  84. package/components/ln-include/ln-include.schema.json +23 -23
  85. package/components/ln-key/ln-key-dev.scss +18 -18
  86. package/components/ln-key/ln-key.js +1 -1
  87. package/components/ln-key/ln-key.schema.json +56 -56
  88. package/components/ln-key/src/ln-key.js +10 -10
  89. package/components/ln-link/ln-link-dev.scss +8 -8
  90. package/components/ln-link/ln-link.js +1 -1
  91. package/components/ln-link/ln-link.schema.json +22 -22
  92. package/components/ln-list/README.md +152 -152
  93. package/components/ln-list/ln-list-dev.scss +8 -8
  94. package/components/ln-list/ln-list.js +1 -1
  95. package/components/ln-list/ln-list.schema.json +166 -166
  96. package/components/ln-modal/ln-modal.js +1 -1
  97. package/components/ln-modal/ln-modal.schema.json +65 -65
  98. package/components/ln-nav/ln-nav.js +1 -1
  99. package/components/ln-nav/ln-nav.schema.json +25 -25
  100. package/components/ln-number/ln-number.js +1 -1
  101. package/components/ln-number/ln-number.schema.json +63 -63
  102. package/components/ln-number/src/number-model.js +31 -31
  103. package/components/ln-obfuscator/README.md +60 -60
  104. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  105. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
  106. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
  107. package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
  108. package/components/ln-options/ln-options.js +1 -1
  109. package/components/ln-options/ln-options.schema.json +41 -41
  110. package/components/ln-persist/README.md +151 -151
  111. package/components/ln-persist/ln-persist.js +1 -1
  112. package/components/ln-persist/ln-persist.schema.json +28 -28
  113. package/components/ln-persist/src/ln-persist.js +83 -83
  114. package/components/ln-picklist/README.md +230 -230
  115. package/components/ln-picklist/ln-picklist-dev.scss +22 -22
  116. package/components/ln-picklist/ln-picklist.js +1 -1
  117. package/components/ln-picklist/ln-picklist.schema.json +49 -49
  118. package/components/ln-picklist/src/ln-picklist.js +228 -228
  119. package/components/ln-popover/ln-popover-dev.scss +13 -13
  120. package/components/ln-popover/ln-popover.js +1 -1
  121. package/components/ln-popover/ln-popover.schema.json +82 -82
  122. package/components/ln-progress/ln-progress-dev.scss +8 -8
  123. package/components/ln-progress/ln-progress.js +1 -1
  124. package/components/ln-progress/ln-progress.schema.json +40 -40
  125. package/components/ln-router/ln-router.js +1 -1
  126. package/components/ln-router/ln-router.schema.json +76 -76
  127. package/components/ln-scroll/README.md +45 -45
  128. package/components/ln-scroll/ln-scroll.js +1 -1
  129. package/components/ln-scroll/ln-scroll.schema.json +85 -85
  130. package/components/ln-scroll/src/ln-scroll.js +150 -150
  131. package/components/ln-search/README.md +188 -188
  132. package/components/ln-search/ln-search.js +1 -1
  133. package/components/ln-search/ln-search.schema.json +103 -103
  134. package/components/ln-slug/ln-slug.js +1 -1
  135. package/components/ln-slug/ln-slug.schema.json +23 -23
  136. package/components/ln-sort/ln-sort.js +1 -1
  137. package/components/ln-sort/ln-sort.schema.json +104 -104
  138. package/components/ln-sort/src/ln-sort.js +321 -321
  139. package/components/ln-sort/src/sort-model.js +117 -117
  140. package/components/ln-sortable/ln-sortable-dev.scss +13 -13
  141. package/components/ln-sortable/ln-sortable.js +1 -1
  142. package/components/ln-sortable/ln-sortable.schema.json +36 -36
  143. package/components/ln-stat/ln-stat-dev.scss +8 -8
  144. package/components/ln-stat/ln-stat.js +1 -1
  145. package/components/ln-stat/ln-stat.schema.json +32 -32
  146. package/components/ln-stat/src/stat-model.js +28 -28
  147. package/components/ln-table/ln-table.js +1 -1
  148. package/components/ln-table/ln-table.schema.json +211 -211
  149. package/components/ln-table/src/table-model.js +98 -98
  150. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  151. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
  152. package/components/ln-tabs/ln-tabs.js +1 -1
  153. package/components/ln-tabs/ln-tabs.schema.json +75 -75
  154. package/components/ln-tabs/src/ln-tabs.js +208 -208
  155. package/components/ln-tabs/src/tabs-model.js +82 -82
  156. package/components/ln-time/ln-time-dev.scss +13 -13
  157. package/components/ln-time/ln-time.js +1 -1
  158. package/components/ln-time/ln-time.schema.json +38 -38
  159. package/components/ln-toast/ln-toast.js +1 -1
  160. package/components/ln-toast/ln-toast.schema.json +65 -65
  161. package/components/ln-toggle/ln-toggle.js +1 -1
  162. package/components/ln-toggle/ln-toggle.schema.json +52 -52
  163. package/components/ln-tooltip/ln-tooltip.js +1 -1
  164. package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
  165. package/components/ln-translations/ln-translations.js +1 -1
  166. package/components/ln-translations/ln-translations.schema.json +120 -120
  167. package/components/ln-ui-coordinator/README.md +62 -62
  168. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
  169. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
  170. package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
  171. package/components/ln-upload/ln-upload.js +1 -1
  172. package/components/ln-upload/ln-upload.schema.json +193 -193
  173. package/components/ln-upload/src/upload-model.js +78 -78
  174. package/components/ln-validate/ln-validate.js +1 -1
  175. package/components/ln-validate/ln-validate.schema.json +50 -50
  176. package/components/ln-validate/src/validate-model.js +64 -64
  177. package/components/ln-websocket-connector/README.md +166 -166
  178. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
  179. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
  180. package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
  181. package/ln-ashlar.html-data.json +7 -8
  182. package/package.json +84 -84
  183. package/theme/brand.css +73 -73
  184. package/theme/components/_confirm.scss +6 -6
  185. package/theme/components/_picklist.scss +3 -3
  186. package/theme/config/_palette.scss +274 -274
  187. package/theme/config/_theme.scss +292 -292
  188. package/theme/config/_tokens.scss +285 -285
  189. package/theme/config/mixins/_index.scss +62 -62
  190. package/theme/config/mixins/_picklist.scss +66 -66
  191. package/theme/ln-ashlar-dev.scss +66 -66
  192. package/theme/ln-ashlar-landing.scss +56 -56
  193. package/theme/ln-ashlar-theme.scss +63 -63
  194. package/theme/presentation/_bento.scss +151 -151
  195. package/theme/presentation/_cta.scss +111 -111
  196. package/theme/presentation/_effects.scss +216 -216
  197. package/theme/presentation/_features.scss +108 -108
  198. package/theme/presentation/_footer.scss +129 -129
  199. package/theme/presentation/_hero.scss +158 -158
  200. package/theme/presentation/_layout.scss +170 -170
  201. package/theme/presentation/_navbar.scss +140 -140
  202. package/theme/presentation/_pricing.scss +262 -262
  203. package/theme/presentation/_stats.scss +87 -87
  204. package/theme/presentation/_testimonials.scss +124 -124
  205. package/theme/presentation/_tokens.scss +39 -39
  206. package/theme/presentation/_typography.scss +163 -163
  207. package/web-types.json +12 -12
@@ -1,188 +1,188 @@
1
- # ln-search
2
-
3
- > Applied via a two-host bridge: an `<input type="search" data-ln-search-for="targetId">` controls an element
4
- > (`data-ln-search id="targetId"`). Typing fires native `input` events that write `target.setAttribute('data-ln-search', input.value)`.
5
- > A `MutationObserver` on the target observes attribute changes, matches items against search tokens, and adds `data-ln-search-hide`
6
- > and `.hidden` to non-matching elements. Clicking `[data-ln-search-clear]` sets `data-ln-search=""` and clears the input.
7
-
8
- ---
9
-
10
- ## ✅ Canonical Markup — Copy This (REQUIRED)
11
-
12
- **Hard rule — non-negotiable.** Every search or filter text input MUST use the full `<search>` chrome below: the leading magnifier icon **and** the clear ("x") button.
13
-
14
- ```html
15
- <search aria-label="Search records">
16
- <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-search"></use></svg>
17
- <input type="search" placeholder="Search …" data-ln-search-for="<targetId>">
18
- <button type="button" data-ln-search-clear aria-label="Clear search">
19
- <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg>
20
- </button>
21
- </search>
22
-
23
- <ul id="<targetId>" data-ln-search="">
24
- <li>Item Alpha</li>
25
- <li>Item Beta</li>
26
- <li data-ln-search-exclude>Pinned Item (Always Visible)</li>
27
- </ul>
28
- ```
29
-
30
- For deep targeting (e.g. table rows or checkbox lists) add `data-ln-search-items="<selector>"` on the **target element**:
31
-
32
- ```html
33
- <table id="<targetId>" data-ln-search="" data-ln-search-items="tbody tr">
34
- ...
35
- </table>
36
- ```
37
-
38
- ---
39
-
40
- ## 📋 SSR Table Search — Without `ln-table` (REQUIRED shape)
41
-
42
- When a table's rows are already printed in `<tbody>` by the server and the
43
- **only** interaction is text search — no sort, no column filters, no
44
- data-driven mode — `ln-search` binds directly to the `<table>`. Do **not**
45
- add `data-ln-table`.
46
-
47
- ```html
48
- <header class="ln-table__toolbar">
49
- <search aria-label="Product search">
50
- <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-search"></use></svg>
51
- <input type="search" data-ln-search-for="demo-table" aria-label="Search products">
52
- <button type="button" data-ln-search-clear aria-label="Clear search">
53
- <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg>
54
- </button>
55
- </search>
56
- </header>
57
-
58
- <!-- data-ln-persist: the term survives a page reload. Drop it when the
59
- search should start empty every visit. -->
60
- <table data-ln-search="" data-ln-search-items="tbody tr" data-ln-persist id="demo-table">
61
- ...
62
- </table>
63
- ```
64
-
65
- Three rules govern this shape:
66
-
67
- 1. `data-ln-search` goes on the `<table>` itself, not a wrapper `<div>` —
68
- `data-ln-table` is absent.
69
- 2. `data-ln-search-items="tbody tr"` is required. Without it, `_apply()`
70
- falls back to `dom.children` — the `<thead>` and `<tbody>`, not rows. It
71
- must be `tbody tr`, not bare `tr` — otherwise the header row's own `<tr>`
72
- gets `data-ln-search-hide` when nothing matches.
73
- 3. The toolbar is a `<header class="ln-table__toolbar">`, a sibling of the
74
- `<table>` — never a `<tr><th colspan="N">` row inside `<thead>`. Only that
75
- wrapper gets the compact toolbar layout from
76
- `theme/components/_ln-table.scss`.
77
-
78
- See `demo/admin/src/pages/table.html` for the live reference.
79
-
80
- **This is a different case from `ln-table`.** The moment the table also
81
- needs column filters (`data-ln-table-filter-col`) or data-driven rows
82
- (`data-ln-table-source`), `data-ln-table` goes on the wrapper and `ln-table`
83
- owns search instead — see [`ln-table`](../ln-table/README.md#search-integration-ln-search).
84
- `ln-table` listens for `ln-search:change` on itself and calls
85
- `preventDefault()` unconditionally; since `dispatchCancelable` bubbles, the
86
- generic `ln-search` DOM-filter path (including `data-ln-search-items`) never
87
- runs while `data-ln-table` is present. The two modes are not meant to be
88
- combined on the same target — pick one per table.
89
-
90
- ---
91
-
92
- ## 🧭 Philosophy & Architecture
93
-
94
- 1. **Two-Host Separation (Attribute Bridge):**
95
- - **Control (`data-ln-search-for="targetId"`):** Forwards user input and writes the raw value to `target.setAttribute('data-ln-search', rawValue)`.
96
- - **State Host (`data-ln-search="term"`):** Uses `MutationObserver` (`_syncAttribute`) to detect attribute changes, syncs all matching controls, and dispatches `ln-search:change`.
97
- 2. **Tokenized AND Matching:** By default, splits search queries on whitespace and tests tokens with substring checks (`indexOf`). An item matches only if every token is present in its text.
98
- 3. **Exemptions & Subtree Exclusion (`data-ln-search-exclude`):**
99
- - **On item root:** The item is exempt from filtering — always visible, never given `data-ln-search-hide`.
100
- - **On descendant:** The subtree text is excluded from search matching.
101
- 4. **Cancelable Change Event:** Consumers (`ln-table`, `ln-list`, `ln-data-store`) call `e.preventDefault()` on `ln-search:change` to handle their own records and skip default DOM show/hide.
102
- 5. **Boot Seeding:** Pre-filled or deep-linked `data-ln-search` attributes seed safely using `queueBoot`.
103
-
104
- ---
105
-
106
- ## 🛠️ Declarative API Contract
107
-
108
- ### HTML Attributes
109
-
110
- | Attribute | Element | Description |
111
- | :--- | :--- | :--- |
112
- | `data-ln-search-for="targetId"` | `<input>` / wrapper | Pointer to the target element ID. |
113
- | `data-ln-search="term"` | Target element | Search term state. Single source of truth. |
114
- | `data-ln-search-items="selector"` | Target element | Deep CSS selector (e.g. `tbody tr`) to query items instead of direct children. |
115
- | `data-ln-search-fields="a,b"` | Target element | Comma-separated list of field keys forwarded in event detail. |
116
- | `data-ln-search-exclude` | Item root or descendant | Excludes item from filtering (on root) or excludes subtree text (on descendant). |
117
- | `data-ln-search-clear` | `<button>` | Identifies a clear button (in search control or target empty state). Clears input and resets target state. |
118
- | `data-ln-search-clear-for="targetId"` | `<button>` | Remote clear button targeting a specific element ID anywhere on the page. |
119
- | `data-ln-search-hide="true"` | Items in target | State attribute automatically set on non-matching elements (`display: none !important`). |
120
- | `data-ln-hash` | Target / Control | Opt-in. Synchronizes search query to URL hash fragment (e.g. `#users-search:john`). Value is custom namespace; if empty defaults to `[targetId]-search`. |
121
- | `data-ln-persist` | Target element | **Recommended.** Persists the search term to `localStorage` and restores it before boot; omit it only when the search must start empty on every visit. Goes on `[data-ln-search]`, never on the input. The target needs an `id`, or give the attribute an explicit value (`data-ln-persist="key"`). No effect while `data-ln-hash` is active on the target — hash wins, never double-written. Stored key format: `ln:{id}:data-ln-search` (global by default); add `data-ln-persist-scope="page"` to opt into page-scoping, which stores `ln:{id}:{pathname}:data-ln-search`. |
122
- | `data-ln-persist-scope` | Target element | Opt-in, independent attribute. `"page"` scopes the persisted key to the current URL pathname. |
123
-
124
- ### JavaScript API
125
-
126
- | Instance | Property / Method | Description |
127
- | :--- | :--- | :--- |
128
- | `el.lnSearchControl` (Control) | `targetId`, `input`, `destroy()` | Control instance managing input. |
129
- | `el.lnSearch` (State Host) | `term`, `nsKey`, `hashEnabled`, `_apply()`, `destroy()` | State instance owning term, URL hash sync, and DOM filter logic. |
130
-
131
-
132
- ---
133
-
134
- ## ⚡ DOM Events
135
-
136
- ### `ln-search:change`
137
- Dispatched on the **target** element whenever the search term updates.
138
- - **Cancelable**: Yes (`e.preventDefault()` disables default DOM show/hide).
139
- - **Payload (`detail`)**:
140
- ```js
141
- {
142
- term: string, // Trimmed and lowercased string
143
- tokens: string[], // Array of whitespace-separated tokens
144
- targetId: string, // ID of the target
145
- fields: string[]|null // Forwarded field names
146
- }
147
- ```
148
-
149
- ---
150
-
151
- ## 🌐 Backend Search Contract (Whitespace & Wildcard Handling)
152
-
153
- To keep remote search consistent with client-side DOM and table filtering, backend APIs (e.g. `api.php`, SQL endpoints) must mirror the tokenized search contract:
154
-
155
- 1. **Whitespace Tokenization (`AND` Logic):** Split the query string on whitespace (`\s+`). Every token must be present in the record.
156
- 2. **Wildcard `*` Conversion:** Convert asterisks (`*`) into SQL `%` or Regex `.*`.
157
- 3. **Multi-Field Matching (`OR` per token):** Each token can match anywhere across the allowed searchable columns/fields.
158
-
159
- ```php
160
- // Reference PHP / SQLite PDO implementation
161
- $search = isset($_GET['search']) ? trim($_GET['search']) : (isset($_GET['q']) ? trim($_GET['q']) : '');
162
- if ($search !== '') {
163
- $tokens = preg_split('/\s+/', $search, -1, PREG_SPLIT_NO_EMPTY);
164
- $searchFields = ['title', 'department', 'owner', 'tags'];
165
-
166
- foreach ($tokens as $token) {
167
- $tokenPattern = '%' . str_replace('*', '%', $token) . '%';
168
- $tokenPattern = preg_replace('/%+/', '%', $tokenPattern);
169
-
170
- $fieldClauses = [];
171
- foreach ($searchFields as $field) {
172
- $fieldClauses[] = "$field LIKE ?";
173
- $params[] = $tokenPattern;
174
- }
175
- $where[] = '(' . implode(' OR ', $fieldClauses) . ')';
176
- }
177
- }
178
- ```
179
-
180
- ---
181
-
182
- ## ⚠️ Common Pitfalls
183
-
184
- - **Using `data-ln-search="tableId"` on inputs:** `data-ln-search` is the target state attribute. Inputs must use `data-ln-search-for="tableId"`.
185
- - **Programmatic Value Mutations:** Assigning `input.value = "text"` programmatically does not trigger native `input` events. Either dispatch a native `input` event on the control or set the attribute directly on the target:
186
- ```js
187
- document.getElementById('my-table').setAttribute('data-ln-search', 'query');
188
- ```
1
+ # ln-search
2
+
3
+ > Applied via a two-host bridge: an `<input type="search" data-ln-search-for="targetId">` controls an element
4
+ > (`data-ln-search id="targetId"`). Typing fires native `input` events that write `target.setAttribute('data-ln-search', input.value)`.
5
+ > A `MutationObserver` on the target observes attribute changes, matches items against search tokens, and adds `data-ln-search-hide`
6
+ > and `.hidden` to non-matching elements. Clicking `[data-ln-search-clear]` sets `data-ln-search=""` and clears the input.
7
+
8
+ ---
9
+
10
+ ## ✅ Canonical Markup — Copy This (REQUIRED)
11
+
12
+ **Hard rule — non-negotiable.** Every search or filter text input MUST use the full `<search>` chrome below: the leading magnifier icon **and** the clear ("x") button.
13
+
14
+ ```html
15
+ <search aria-label="Search records">
16
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-search"></use></svg>
17
+ <input type="search" placeholder="Search …" data-ln-search-for="<targetId>">
18
+ <button type="button" data-ln-search-clear aria-label="Clear search">
19
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg>
20
+ </button>
21
+ </search>
22
+
23
+ <ul id="<targetId>" data-ln-search="">
24
+ <li>Item Alpha</li>
25
+ <li>Item Beta</li>
26
+ <li data-ln-search-exclude>Pinned Item (Always Visible)</li>
27
+ </ul>
28
+ ```
29
+
30
+ For deep targeting (e.g. table rows or checkbox lists) add `data-ln-search-items="<selector>"` on the **target element**:
31
+
32
+ ```html
33
+ <table id="<targetId>" data-ln-search="" data-ln-search-items="tbody tr">
34
+ ...
35
+ </table>
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 📋 SSR Table Search — Without `ln-table` (REQUIRED shape)
41
+
42
+ When a table's rows are already printed in `<tbody>` by the server and the
43
+ **only** interaction is text search — no sort, no column filters, no
44
+ data-driven mode — `ln-search` binds directly to the `<table>`. Do **not**
45
+ add `data-ln-table`.
46
+
47
+ ```html
48
+ <header class="ln-table__toolbar">
49
+ <search aria-label="Product search">
50
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-search"></use></svg>
51
+ <input type="search" data-ln-search-for="demo-table" aria-label="Search products">
52
+ <button type="button" data-ln-search-clear aria-label="Clear search">
53
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg>
54
+ </button>
55
+ </search>
56
+ </header>
57
+
58
+ <!-- data-ln-persist: the term survives a page reload. Drop it when the
59
+ search should start empty every visit. -->
60
+ <table data-ln-search="" data-ln-search-items="tbody tr" data-ln-persist id="demo-table">
61
+ ...
62
+ </table>
63
+ ```
64
+
65
+ Three rules govern this shape:
66
+
67
+ 1. `data-ln-search` goes on the `<table>` itself, not a wrapper `<div>` —
68
+ `data-ln-table` is absent.
69
+ 2. `data-ln-search-items="tbody tr"` is required. Without it, `_apply()`
70
+ falls back to `dom.children` — the `<thead>` and `<tbody>`, not rows. It
71
+ must be `tbody tr`, not bare `tr` — otherwise the header row's own `<tr>`
72
+ gets `data-ln-search-hide` when nothing matches.
73
+ 3. The toolbar is a `<header class="ln-table__toolbar">`, a sibling of the
74
+ `<table>` — never a `<tr><th colspan="N">` row inside `<thead>`. Only that
75
+ wrapper gets the compact toolbar layout from
76
+ `theme/components/_ln-table.scss`.
77
+
78
+ See `demo/admin/src/pages/table.html` for the live reference.
79
+
80
+ **This is a different case from `ln-table`.** The moment the table also
81
+ needs column filters (`data-ln-table-filter-col`) or data-driven rows
82
+ (`data-ln-table-source`), `data-ln-table` goes on the wrapper and `ln-table`
83
+ owns search instead — see [`ln-table`](../ln-table/README.md#search-integration-ln-search).
84
+ `ln-table` listens for `ln-search:change` on itself and calls
85
+ `preventDefault()` unconditionally; since `dispatchCancelable` bubbles, the
86
+ generic `ln-search` DOM-filter path (including `data-ln-search-items`) never
87
+ runs while `data-ln-table` is present. The two modes are not meant to be
88
+ combined on the same target — pick one per table.
89
+
90
+ ---
91
+
92
+ ## 🧭 Philosophy & Architecture
93
+
94
+ 1. **Two-Host Separation (Attribute Bridge):**
95
+ - **Control (`data-ln-search-for="targetId"`):** Forwards user input and writes the raw value to `target.setAttribute('data-ln-search', rawValue)`.
96
+ - **State Host (`data-ln-search="term"`):** Uses `MutationObserver` (`_syncAttribute`) to detect attribute changes, syncs all matching controls, and dispatches `ln-search:change`.
97
+ 2. **Tokenized AND Matching:** By default, splits search queries on whitespace and tests tokens with substring checks (`indexOf`). An item matches only if every token is present in its text.
98
+ 3. **Exemptions & Subtree Exclusion (`data-ln-search-exclude`):**
99
+ - **On item root:** The item is exempt from filtering — always visible, never given `data-ln-search-hide`.
100
+ - **On descendant:** The subtree text is excluded from search matching.
101
+ 4. **Cancelable Change Event:** Consumers (`ln-table`, `ln-list`, `ln-data-store`) call `e.preventDefault()` on `ln-search:change` to handle their own records and skip default DOM show/hide.
102
+ 5. **Boot Seeding:** Pre-filled or deep-linked `data-ln-search` attributes seed safely using `queueBoot`.
103
+
104
+ ---
105
+
106
+ ## 🛠️ Declarative API Contract
107
+
108
+ ### HTML Attributes
109
+
110
+ | Attribute | Element | Description |
111
+ | :--- | :--- | :--- |
112
+ | `data-ln-search-for="targetId"` | `<input>` / wrapper | Pointer to the target element ID. |
113
+ | `data-ln-search="term"` | Target element | Search term state. Single source of truth. |
114
+ | `data-ln-search-items="selector"` | Target element | Deep CSS selector (e.g. `tbody tr`) to query items instead of direct children. |
115
+ | `data-ln-search-fields="a,b"` | Target element | Comma-separated list of field keys forwarded in event detail. |
116
+ | `data-ln-search-exclude` | Item root or descendant | Excludes item from filtering (on root) or excludes subtree text (on descendant). |
117
+ | `data-ln-search-clear` | `<button>` | Identifies a clear button (in search control or target empty state). Clears input and resets target state. |
118
+ | `data-ln-search-clear-for="targetId"` | `<button>` | Remote clear button targeting a specific element ID anywhere on the page. |
119
+ | `data-ln-search-hide="true"` | Items in target | State attribute automatically set on non-matching elements (`display: none !important`). |
120
+ | `data-ln-hash` | Target / Control | Opt-in. Synchronizes search query to URL hash fragment (e.g. `#users-search:john`). Value is custom namespace; if empty defaults to `[targetId]-search`. |
121
+ | `data-ln-persist` | Target element | **Recommended.** Persists the search term to `localStorage` and restores it before boot; omit it only when the search must start empty on every visit. Goes on `[data-ln-search]`, never on the input. The target needs an `id`, or give the attribute an explicit value (`data-ln-persist="key"`). No effect while `data-ln-hash` is active on the target — hash wins, never double-written. Stored key format: `ln:{id}:data-ln-search` (global by default); add `data-ln-persist-scope="page"` to opt into page-scoping, which stores `ln:{id}:{pathname}:data-ln-search`. |
122
+ | `data-ln-persist-scope` | Target element | Opt-in, independent attribute. `"page"` scopes the persisted key to the current URL pathname. |
123
+
124
+ ### JavaScript API
125
+
126
+ | Instance | Property / Method | Description |
127
+ | :--- | :--- | :--- |
128
+ | `el.lnSearchControl` (Control) | `targetId`, `input`, `destroy()` | Control instance managing input. |
129
+ | `el.lnSearch` (State Host) | `term`, `nsKey`, `hashEnabled`, `_apply()`, `destroy()` | State instance owning term, URL hash sync, and DOM filter logic. |
130
+
131
+
132
+ ---
133
+
134
+ ## ⚡ DOM Events
135
+
136
+ ### `ln-search:change`
137
+ Dispatched on the **target** element whenever the search term updates.
138
+ - **Cancelable**: Yes (`e.preventDefault()` disables default DOM show/hide).
139
+ - **Payload (`detail`)**:
140
+ ```js
141
+ {
142
+ term: string, // Trimmed and lowercased string
143
+ tokens: string[], // Array of whitespace-separated tokens
144
+ targetId: string, // ID of the target
145
+ fields: string[]|null // Forwarded field names
146
+ }
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 🌐 Backend Search Contract (Whitespace & Wildcard Handling)
152
+
153
+ To keep remote search consistent with client-side DOM and table filtering, backend APIs (e.g. `api.php`, SQL endpoints) must mirror the tokenized search contract:
154
+
155
+ 1. **Whitespace Tokenization (`AND` Logic):** Split the query string on whitespace (`\s+`). Every token must be present in the record.
156
+ 2. **Wildcard `*` Conversion:** Convert asterisks (`*`) into SQL `%` or Regex `.*`.
157
+ 3. **Multi-Field Matching (`OR` per token):** Each token can match anywhere across the allowed searchable columns/fields.
158
+
159
+ ```php
160
+ // Reference PHP / SQLite PDO implementation
161
+ $search = isset($_GET['search']) ? trim($_GET['search']) : (isset($_GET['q']) ? trim($_GET['q']) : '');
162
+ if ($search !== '') {
163
+ $tokens = preg_split('/\s+/', $search, -1, PREG_SPLIT_NO_EMPTY);
164
+ $searchFields = ['title', 'department', 'owner', 'tags'];
165
+
166
+ foreach ($tokens as $token) {
167
+ $tokenPattern = '%' . str_replace('*', '%', $token) . '%';
168
+ $tokenPattern = preg_replace('/%+/', '%', $tokenPattern);
169
+
170
+ $fieldClauses = [];
171
+ foreach ($searchFields as $field) {
172
+ $fieldClauses[] = "$field LIKE ?";
173
+ $params[] = $tokenPattern;
174
+ }
175
+ $where[] = '(' . implode(' OR ', $fieldClauses) . ')';
176
+ }
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## ⚠️ Common Pitfalls
183
+
184
+ - **Using `data-ln-search="tableId"` on inputs:** `data-ln-search` is the target state attribute. Inputs must use `data-ln-search-for="tableId"`.
185
+ - **Programmatic Value Mutations:** Assigning `input.value = "text"` programmatically does not trigger native `input` events. Either dispatch a native `input` event on the control or set the attribute directly on the target:
186
+ ```js
187
+ document.getElementById('my-table').setAttribute('data-ln-search', 'query');
188
+ ```
@@ -1 +1 @@
1
- (function(){"use strict";function j(){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 I(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 r=null;for(let o=1;o<t.length;o++)if(t[o]&&t[o].nodeType===1){r=t[o];break}if(!I(r))return}e.apply(console,t)}}function Z(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function ee(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r);const o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r});return e.dispatchEvent(o),o}function k(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){k(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const W={};function te(e,t){W[e]=t}function ne(e){return W[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=te,window.lnCore.getDataMapper=ne);function re(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,k(function(){new MutationObserver(function(){Z(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const V={};function oe(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];V[n]=t}function ie(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return V[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=oe,window.lnCore.getLocaleFallback=ie,window.lnCore.ensureLocaleObserver=re);const O={};function le(e,t){O[e]||(O[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=O[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function K(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let l=0;l<n.length;l++){const d=n[l],a=d.getAttribute("data-ln-field");t[a]!=null&&(d.textContent=t[a])}const r=e.querySelectorAll("[data-ln-attr]");for(let l=0;l<r.length;l++){const d=r[l],a=d.getAttribute("data-ln-attr").split(",");for(let f=0;f<a.length;f++){const p=a[f].trim().split(":");if(p.length!==2)continue;const h=p[0].trim(),b=p[1].trim();t[b]!=null&&d.setAttribute(h,t[b])}}const o=e.querySelectorAll("[data-ln-show]");for(let l=0;l<o.length;l++){const d=o[l],a=d.getAttribute("data-ln-show");a in t&&d.classList.toggle("hidden",!t[a])}const i=e.querySelectorAll("[data-ln-class]");for(let l=0;l<i.length;l++){const d=i[l],a=d.getAttribute("data-ln-class").split(",");for(let f=0;f<a.length;f++){const p=a[f].trim().split(":");if(p.length!==2)continue;const h=p[0].trim(),b=p[1].trim();b in t&&d.classList.toggle(h,!!t[b])}}return e}function se(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 r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].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)K(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 U(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(l,d){return t[d]!==void 0?t[d]:""}))}const r=function(i,l){return t[l]!==void 0?t[l]:""},o=Array.from(e.querySelectorAll("*"));e.nodeType===1&&o.push(e);for(let i=0;i<o.length;i++){const l=o[i],d=l.attributes;for(let a=0;a<d.length;a++){const f=d[a];f.value.indexOf("{{")!==-1&&l.setAttribute(f.name,f.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function ce(e,t,n,r,o,i){const l={};for(let a=0;a<e.children.length;a++){const f=e.children[a],p=f.getAttribute("data-ln-render-key");p&&(l[p]=f)}const d=document.createDocumentFragment();for(let a=0;a<t.length;a++){const f=t[a],p=String(r(f));let h=l[p];if(h)o(h,f,a);else{const b=le(n,i);if(!b||(U(b,f),h=b.firstElementChild,!h))continue;h.setAttribute("data-ln-render-key",p),o(h,f,a)}d.appendChild(h)}e.textContent="",e.appendChild(d)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=U,window.lnCore.fill=K,window.lnCore.lnFill=se,window.lnCore.renderList=ce);function L(e,t,n){const r=e.getAttribute(t);return r===null?n:r}function ae(e,t,n){const r=parseInt(e.getAttribute(t),10);return isNaN(r)?n:r}function ue(e,t,n=!1){const r=e.getAttribute(t);if(r===null)return!!n;const o=r.trim().toLowerCase();return!(o==="false"||o==="0")}function de(e,t){return(e.getAttribute(t)||"").split(",").map(n=>n.trim()).filter(Boolean)}const R=new Map;function fe(e,t){const n=(e?e.join("|"):"")+"::"+t;if(R.has(n))return R.get(n);const r=new Set(e||[]),o=function(i,l){const d=i.getAttribute(l);return d!==null&&r.has(d)?d:t};return R.set(n,o),o}function he(e,t,n){const r=parseFloat(e.getAttribute(t));return isNaN(r)?n:r}function we(e,t,n){const r=e.getAttribute(t);if(!r)return n;try{return JSON.parse(r)}catch{return n}}function Q(e,t,n,r,o){if(!e||t===null)return!0;const i=r||"ln-component",l=e.type;if(l==="trigger"||l==="marker"||l==="string"||l==="list"||!l||t===""&&e.fallback!==void 0)return!0;const d=a=>{o?console.warn(a,o):console.warn(a)};if(l==="boolean"){const a=t.trim().toLowerCase();return a!==""&&a!=="true"&&a!=="false"&&a!=="1"&&a!=="0"?(d(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(l==="enum"){const a=e.values||[];return a.includes(t)?!0:(d(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${a.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(l==="integer"){if(!/^-?\d+$/.test(t))return d(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const a=parseInt(t,10);return e.min!==void 0&&a<e.min?(d(`[${i}] Value ${a} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&a>e.max?(d(`[${i}] Value ${a} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(l==="float")return isNaN(Number(t))?(d(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(l==="json")try{return JSON.parse(t),!0}catch(a){return d(`[${i}] Invalid JSON for attribute "${n}": ${a.message}. Fallback: ${e.fallback}.`),!1}return!0}function pe(e,t,n){for(const r in n){const[o,i,l]=n[r];Object.defineProperty(e,r,{get:function(){return o(t,i,l)},enumerable:!0,configurable:!0})}return e}function ge(e){const t={};for(const n in e){const r=e[n];if(!r||!r.prop)continue;let o=r.read;if(!o&&r.type)switch(r.type){case"enum":o=fe(r.values,r.fallback);break;case"integer":o=ae;break;case"float":o=he;break;case"boolean":o=ue;break;case"list":o=de;break;case"json":o=we;break;case"string":default:o=L;break}o||(o=L),t[r.prop]=[o,n,r.fallback]}return t}function be(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function me(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 D(e,t,n,r){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",l=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&l.push(e);for(const d of l)if(!d[n]){window.lnCore._persistSink&&d.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(d,t);try{d[n]=new r(d)}catch(a){console.error("["+n+"] init failed",d,a)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function Ce(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function q(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function P(){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 ye(e){const t=P(),n=e.observed||[];for(let r=0;r<n.length;r++){const o=n[r];t.byAttr.has(o)||t.byAttr.set(o,[]),t.byAttr.get(o).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const o=t.byDeclaredAttr.get(r);o.some(i=>i.componentTag===e.componentTag)||o.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=me(e);if(r===null)t.reactiveWildcard.push(e);else for(const o of r)t.byReactive.has(o)||t.byReactive.set(o,[]),t.byReactive.get(o).push(e)}e.persist&&t.persist.push(e)}function J(e,t,n,r){for(let o=0;o<e.length;o++){const i=e[o];if(!t[i.attribute])continue;const l=i.effects&&i.effects[n];l?l(t,n,r):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,r)}}function Ae(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=P(),o=r.byAttr.get(n);if(j()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&I(t)){const i=r.byDeclaredAttr.get(n),l=t.getAttribute(n);for(let d=0;d<i.length;d++)Q(i[d].spec,l,n,i[d].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||o)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=r.byReactive.get(n);i&&J(i,t,n,e.oldValue),r.reactiveWildcard.length&&J(r.reactiveWildcard,t,n,e.oldValue)}if(o)for(let i=0;i<o.length;i++){const l=o[i];if(l.handler){l.handler(t,n,e.oldValue);continue}l.onAttributeChange&&t[l.attribute]?l.onAttributeChange(t,n):(D(t,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(t))}}function _e(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,k(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{Ae(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function z(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function Se(e){const t=z();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const o=r.query,i=e.target.nodeType===1?e.target.matches(o)?e.target:e.target.closest(o):e.target.parentElement?e.target.parentElement.closest(o):null;i&&r.onSubtreeChange(i,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o];D(r,i.selector,i.attribute,i.ComponentFn),i.onInit&&i.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o],l=i.query,d=Array.from(r.querySelectorAll(l));r.matches&&r.matches(l)&&d.push(r);for(let a=0;a<d.length;a++){const f=d[a];if(!document.contains(f)){const p=f[i.attribute];if(p&&typeof p.destroy=="function")try{p.destroy()}catch(h){console.error("["+i.attribute+"] destroy failed",f,h)}delete f[i.attribute]}}}}}}function ve(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,k(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{Se(r)}catch(o){console.error("[ln-core] lifecycle handler failed",r.target,o)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function G(e,t,n,r,o={}){const i=o.extraAttributes||[],l=o.onAttributeChange||null,d=o.onSubtreeChange||null,a=o.onInit||null,f=o.onAttrChange||null,p=o.effects||null,h=o.attributes||null,b=h?be(h):p,x=h?new Set(Object.keys(h)):null,F=o.persist||null;function _(u){const c=u||document.body;if(D(c,e,t,n),h&&j())for(const s in h){const w=h[s],m=Array.from(c.querySelectorAll("["+s+"]"));c.matches&&c.matches("["+s+"]")&&m.push(c);for(let A=0;A<m.length;A++){const g=m[A];I(g)&&Q(w,g.getAttribute(s),s,r,g)}}a&&a(c)}const S=[];if(e.indexOf("[")!==-1){const u=/\[([\w-]+)/g;let c;for(;(c=u.exec(e))!==null;)S.push(c[1])}else S.push(e);ye({selector:e,attribute:t,componentTag:r,attributes:h,ComponentFn:n,onInit:a,observed:S.concat(i),onAttributeChange:l,onAttrChange:f,effects:b,declared:x,persist:F}),_e();const M=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";z().push({selector:e,attribute:t,ComponentFn:n,onInit:a,onSubtreeChange:d,query:M}),ve(),window[t]=_;function E(){Ce()>0?q(function(){_(document.body)}):_(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):E(),_}function X(e){return(e||"").replace(/^#/,"")}function $(e){const t=e===void 0?location.hash:e,n={},r=X(t);if(!r)return n;const o=r.split("&");for(let i=0;i<o.length;i++){const l=o[i];if(!l)continue;const d=l.indexOf(":"),a=d>-1?l.slice(0,d):l,f=d>-1?l.slice(d+1):"";if(a)try{n[a]=decodeURIComponent(f)}catch{n[a]=f}}return n}function B(e){if(!e)return null;const t=$();return e in t?t[e]:null}function Y(e,t){if(!e)return;const n=$();t==null?delete n[e]:n[e]=String(t);const o=Object.keys(n).map(function(i){const l=n[i];return l===""?i:i+":"+encodeURIComponent(l)}).join("&");X(location.hash)!==o&&(location.hash=o)}function Ee(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function N(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 r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function Te(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function ke(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 xe(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function Ie(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),o=r?r.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:o}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=$,window.lnCore.hashGet=B,window.lnCore.hashSet=Y,window.lnCore.hashLinkClick=Ee,window.lnCore.resolveHashNamespace=N,window.lnCore.hashSortEncode=Te,window.lnCore.hashSortDecode=ke,window.lnCore.hashFilterEncode=xe,window.lnCore.hashFilterDecode=Ie);function H(e){return String(e||"").trim().toLowerCase()}function Oe(e){const t=H(e);return t?t.split(/\s+/).filter(Boolean):[]}function Le(e){if(e==null)return null;const t=String(e).split(",").map(n=>n.trim()).filter(Boolean);return t.length?t:null}function Re(e,t){if(!t||t.length===0)return!0;if(!e)return!1;const n=String(e).toLowerCase();for(let r=0;r<t.length;r++)if(n.indexOf(t[r])===-1)return!1;return!0}function De(e){return!e||e.length===0?"":e.join(" ").replace(/\s+/g," ").trim().toLowerCase()}(function(){const e="data-ln-search",t="lnSearch",n="data-ln-search-for",r="lnSearchControl",o="data-ln-search-items",i="data-ln-search-fields",l="data-ln-search-exclude",d="data-ln-search-hide",a="data-ln-hash";if(window[t]!==void 0)return;const f={"data-ln-search":{type:"string",effect:E,description:"Active search query term on target element or container"},"data-ln-hash":{type:"string",effect:E,description:"URL hash routing key for search state persistence"},"data-ln-search-for":{prop:"targetId",type:"string",read:L,fallback:null,description:"Target table or list element ID that this input controls"},"data-ln-search-fields":{type:"list",description:"Comma-separated field names to include in client search"},"data-ln-search-items":{type:"string",description:"CSS selector matching searchable child items"},"data-ln-search-exclude":{type:"string",description:"CSS selector matching child items to exclude from search"},"data-ln-search-hide":{type:"enum",values:["collapse","none"],fallback:"collapse",description:"CSS hiding strategy for non-matching rows"},"data-ln-search-clear-for":{type:"trigger",description:"Click trigger to clear search input for target"}},p=ge(f);function h(u){const c=N(u,"search");if(c)return c;if(u.id){const s=document.querySelector("["+n+'="'+u.id+'"]');if(s){const w=N(s,"search");if(w)return w}}return null}function b(u){return u.matches("input, textarea")?u:u.querySelector("input, textarea")}function x(u,c){const s=u.childNodes;for(let w=0;w<s.length;w++){const m=s[w];if(m.nodeType===3){c.push(m.nodeValue);continue}m.nodeType===1&&(m.hasAttribute(l)||x(m,c))}}function F(u){if(u._lnSearchText!==void 0)return u._lnSearchText;const c=[];x(u,c);const s=De(c);return u._lnSearchText=s,s}function _(u,c){if(!u.id)return;const s=document.querySelectorAll("["+n+'="'+u.id+'"]');for(const w of s){const m=b(w);m&&m.value!==c&&(m.value=c)}}function S(u){this.dom=u,this.term=u.getAttribute(e)||"",this._destroyed=!1;const c=this;return this.nsKey=h(u),this.hashEnabled=!!this.nsKey,this._onHashChange=function(){if(c._destroyed||!c.hashEnabled)return;const s=B(c.nsKey),w=c.dom.getAttribute(e)||"";s!==null&&s!==w?c.dom.setAttribute(e,s):s===null&&w!==""&&c.dom.setAttribute(e,"")},this.hashEnabled&&window.addEventListener("hashchange",this._onHashChange),q(function(){if(!c._destroyed){if(c.hashEnabled){const s=B(c.nsKey);if(s!==null&&s!==c.term){c.term=s,c.dom.setAttribute(e,s),_(c.dom,s),c._apply();return}}H(c.term)&&(_(c.dom,c.term),c._apply())}}),this}S.prototype._apply=function(){const u=this.dom,c=H(this.term),s=Oe(c);this.hashEnabled&&Y(this.nsKey,this.term?this.term:null);const w=Le(u.getAttribute(i));if(ee(u,"ln-search:change",{term:c,tokens:s,targetId:u.id,fields:w}).defaultPrevented)return;const A=u.getAttribute(o),g=A?u.querySelectorAll(A):u.children;for(let C=0;C<g.length;C++){const y=g[C];if(y.removeAttribute(d),y.hasAttribute(l)||s.length===0)continue;const T=F(y);Re(T,s)||y.setAttribute(d,"true")}},S.prototype.destroy=function(){this.dom[t]&&(this._destroyed=!0,this.hashEnabled&&this._onHashChange&&window.removeEventListener("hashchange",this._onHashChange),delete this.dom[t])};function v(u){if(this.dom=u,pe(this,u,p),this.input=b(u),this._attachHandler(),this.input&&this.input.value.trim()){const c=this;q(function(){const s=document.getElementById(c.targetId);s&&((s.getAttribute(e)||"").trim()||c._write(c.input.value))})}return this}v.prototype._write=function(u){const c=document.getElementById(this.targetId);c&&c.getAttribute(e)!==u&&c.setAttribute(e,u)},v.prototype._attachHandler=function(){if(!this.input)return;const u=this;this._onInput=function(){u._write(u.input.value)},this.input.addEventListener("input",this._onInput)},v.prototype.destroy=function(){this.dom[r]&&(this.input&&this._onInput&&this.input.removeEventListener("input",this._onInput),delete this.dom[r])};function M(u){const c=u.getAttribute("data-ln-search-clear-for");if(c){const g=document.getElementById(c),C=document.querySelector("["+n+'="'+c+'"]'),y=C?b(C):null;return{target:g,input:y}}const s=u.closest("["+e+"]");if(s){const g=s.id?document.querySelector("["+n+'="'+s.id+'"]'):null,C=g?b(g):null;return{target:s,input:C}}const w=u.closest("[data-ln-table-source], [data-ln-list-source]");if(w){const g=w.getAttribute("data-ln-table-source")||w.getAttribute("data-ln-list-source"),C=g?document.getElementById(g):null;if(C&&C.hasAttribute(e)){const y=document.querySelector("["+n+'="'+g+'"]'),T=y?b(y):null;return{target:C,input:T}}}const m=u.closest("["+n+"]");if(m){const g=m.getAttribute(n),C=g?document.getElementById(g):null,y=b(m);return{target:C,input:y}}const A=u.parentElement;if(A){const g=A.querySelector("["+n+"]");if(g){const C=g.getAttribute(n),y=C?document.getElementById(C):null,T=b(g);return{target:y,input:T}}}return{target:null,input:null}}document.addEventListener("click",function(u){const c=u.target.closest("[data-ln-search-clear], [data-ln-search-clear-for]");if(!c)return;const s=M(c);!s.target&&!s.input||(u.preventDefault(),s.input&&(s.input.value="",s.input.focus()),s.target&&s.target.setAttribute(e,""))});function E(u,c){const s=u[t];if(!s||s._destroyed)return;if(c===a){s._onHashChange&&window.removeEventListener("hashchange",s._onHashChange),s.nsKey=h(u),s.hashEnabled=!!s.nsKey,s.hashEnabled&&window.addEventListener("hashchange",s._onHashChange);return}const w=u.getAttribute(e)||"";w!==s.term&&(s.term=w,_(u,w),s._apply())}G(e,t,S,"ln-search",{attributes:f,onSubtreeChange:function(u,c){const s=c.target;s&&s._lnSearchText!==void 0&&delete s._lnSearchText,s&&s.parentElement&&s.parentElement._lnSearchText!==void 0&&delete s.parentElement._lnSearchText},persist:{attr:e,hashActive:function(u){return!!h(u)}}}),G(n,r,v,"ln-search-control")})()})();
1
+ (function(){"use strict";function j(){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 I(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 r=null;for(let o=1;o<t.length;o++)if(t[o]&&t[o].nodeType===1){r=t[o];break}if(!I(r))return}e.apply(console,t)}}function Z(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function ee(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r);const o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r});return e.dispatchEvent(o),o}function T(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){T(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const W={};function te(e,t){W[e]=t}function ne(e){return W[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=te,window.lnCore.getDataMapper=ne);function re(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,T(function(){new MutationObserver(function(){Z(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const U={};function oe(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];U[n]=t}function ie(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return U[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=oe,window.lnCore.getLocaleFallback=ie,window.lnCore.ensureLocaleObserver=re);const O={};function le(e,t){O[e]||(O[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=O[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function V(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let l=0;l<n.length;l++){const d=n[l],a=d.getAttribute("data-ln-field");t[a]!=null&&(d.textContent=t[a])}const r=e.querySelectorAll("[data-ln-attr]");for(let l=0;l<r.length;l++){const d=r[l],a=d.getAttribute("data-ln-attr").split(",");for(let f=0;f<a.length;f++){const p=a[f].trim().split(":");if(p.length!==2)continue;const h=p[0].trim(),b=p[1].trim();t[b]!=null&&d.setAttribute(h,t[b])}}const o=e.querySelectorAll("[data-ln-show]");for(let l=0;l<o.length;l++){const d=o[l],a=d.getAttribute("data-ln-show");a in t&&d.classList.toggle("hidden",!t[a])}const i=e.querySelectorAll("[data-ln-class]");for(let l=0;l<i.length;l++){const d=i[l],a=d.getAttribute("data-ln-class").split(",");for(let f=0;f<a.length;f++){const p=a[f].trim().split(":");if(p.length!==2)continue;const h=p[0].trim(),b=p[1].trim();b in t&&d.classList.toggle(h,!!t[b])}}return e}function se(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 r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].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)V(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 K(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(l,d){return t[d]!==void 0?t[d]:""}))}const r=function(i,l){return t[l]!==void 0?t[l]:""},o=Array.from(e.querySelectorAll("*"));e.nodeType===1&&o.push(e);for(let i=0;i<o.length;i++){const l=o[i],d=l.attributes;for(let a=0;a<d.length;a++){const f=d[a];f.value.indexOf("{{")!==-1&&l.setAttribute(f.name,f.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function ce(e,t,n,r,o,i){const l={};for(let a=0;a<e.children.length;a++){const f=e.children[a],p=f.getAttribute("data-ln-render-key");p&&(l[p]=f)}const d=document.createDocumentFragment();for(let a=0;a<t.length;a++){const f=t[a],p=String(r(f));let h=l[p];if(h)o(h,f,a);else{const b=le(n,i);if(!b||(K(b,f),h=b.firstElementChild,!h))continue;h.setAttribute("data-ln-render-key",p),o(h,f,a)}d.appendChild(h)}e.textContent="",e.appendChild(d)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=K,window.lnCore.fill=V,window.lnCore.lnFill=se,window.lnCore.renderList=ce);function L(e,t,n){const r=e.getAttribute(t);return r===null?n:r}function ae(e,t,n){const r=parseInt(e.getAttribute(t),10);return isNaN(r)?n:r}function ue(e,t,n=!1){const r=e.getAttribute(t);if(r===null)return!!n;const o=r.trim().toLowerCase();return!(o==="false"||o==="0")}function de(e,t){return(e.getAttribute(t)||"").split(",").map(n=>n.trim()).filter(Boolean)}const D=new Map;function fe(e,t){const n=(e?e.join("|"):"")+"::"+t;if(D.has(n))return D.get(n);const r=new Set(e||[]),o=function(i,l){const d=i.getAttribute(l);return d!==null&&r.has(d)?d:t};return D.set(n,o),o}function he(e,t,n){const r=parseFloat(e.getAttribute(t));return isNaN(r)?n:r}function we(e,t,n){const r=e.getAttribute(t);if(!r)return n;try{return JSON.parse(r)}catch{return n}}function Q(e,t,n,r,o){if(!e||t===null)return!0;const i=r||"ln-component",l=e.type;if(l==="trigger"||l==="marker"||l==="string"||l==="list"||!l||t===""&&e.fallback!==void 0)return!0;const d=a=>{o?console.warn(a,o):console.warn(a)};if(l==="boolean"){const a=t.trim().toLowerCase();return a!==""&&a!=="true"&&a!=="false"&&a!=="1"&&a!=="0"?(d(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(l==="enum"){const a=e.values||[];return a.includes(t)?!0:(d(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${a.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(l==="integer"){if(!/^-?\d+$/.test(t))return d(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const a=parseInt(t,10);return e.min!==void 0&&a<e.min?(d(`[${i}] Value ${a} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&a>e.max?(d(`[${i}] Value ${a} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(l==="float")return isNaN(Number(t))?(d(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(l==="json")try{return JSON.parse(t),!0}catch(a){return d(`[${i}] Invalid JSON for attribute "${n}": ${a.message}. Fallback: ${e.fallback}.`),!1}return!0}function pe(e,t,n){for(const r in n){const[o,i,l]=n[r];Object.defineProperty(e,r,{get:function(){return o(t,i,l)},enumerable:!0,configurable:!0})}return e}function ge(e){const t={};for(const n in e){const r=e[n];if(!r||!r.prop)continue;let o=r.read;if(!o&&r.type)switch(r.type){case"enum":o=fe(r.values,r.fallback);break;case"integer":o=ae;break;case"float":o=he;break;case"boolean":o=ue;break;case"list":o=de;break;case"json":o=we;break;case"string":default:o=L;break}o||(o=L),t[r.prop]=[o,n,r.fallback]}return t}function be(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function me(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 R(e,t,n,r){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",l=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&l.push(e);for(const d of l)if(!d[n]){window.lnCore._persistSink&&d.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(d,t);try{d[n]=new r(d)}catch(a){console.error("["+n+"] init failed",d,a)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function ye(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function q(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function P(){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 Ce(e){const t=P(),n=e.observed||[];for(let r=0;r<n.length;r++){const o=n[r];t.byAttr.has(o)||t.byAttr.set(o,[]),t.byAttr.get(o).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const o=t.byDeclaredAttr.get(r);o.some(i=>i.componentTag===e.componentTag)||o.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=me(e);if(r===null)t.reactiveWildcard.push(e);else for(const o of r)t.byReactive.has(o)||t.byReactive.set(o,[]),t.byReactive.get(o).push(e)}e.persist&&t.persist.push(e)}function J(e,t,n,r){for(let o=0;o<e.length;o++){const i=e[o];if(!t[i.attribute])continue;const l=i.effects&&i.effects[n];l?l(t,n,r):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,r)}}function Ae(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=P(),o=r.byAttr.get(n);if(j()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&I(t)){const i=r.byDeclaredAttr.get(n),l=t.getAttribute(n);for(let d=0;d<i.length;d++)Q(i[d].spec,l,n,i[d].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||o)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=r.byReactive.get(n);i&&J(i,t,n,e.oldValue),r.reactiveWildcard.length&&J(r.reactiveWildcard,t,n,e.oldValue)}if(o)for(let i=0;i<o.length;i++){const l=o[i];if(l.handler){l.handler(t,n,e.oldValue);continue}l.onAttributeChange&&t[l.attribute]?l.onAttributeChange(t,n):(R(t,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(t))}}function _e(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,T(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{Ae(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function z(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function Se(e){const t=z();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const o=r.query,i=e.target.nodeType===1?e.target.matches(o)?e.target:e.target.closest(o):e.target.parentElement?e.target.parentElement.closest(o):null;i&&r.onSubtreeChange(i,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o];R(r,i.selector,i.attribute,i.ComponentFn),i.onInit&&i.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o],l=i.query,d=Array.from(r.querySelectorAll(l));r.matches&&r.matches(l)&&d.push(r);for(let a=0;a<d.length;a++){const f=d[a];if(!document.contains(f)){const p=f[i.attribute];if(p&&typeof p.destroy=="function")try{p.destroy()}catch(h){console.error("["+i.attribute+"] destroy failed",f,h)}delete f[i.attribute]}}}}}}function xe(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,T(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{Se(r)}catch(o){console.error("[ln-core] lifecycle handler failed",r.target,o)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function G(e,t,n,r,o={}){const i=o.extraAttributes||[],l=o.onAttributeChange||null,d=o.onSubtreeChange||null,a=o.onInit||null,f=o.onAttrChange||null,p=o.effects||null,h=o.attributes||null,b=h?be(h):p,k=h?new Set(Object.keys(h)):null,F=o.persist||null;function _(u){const c=u||document.body;if(R(c,e,t,n),h&&j())for(const s in h){const w=h[s],m=Array.from(c.querySelectorAll("["+s+"]"));c.matches&&c.matches("["+s+"]")&&m.push(c);for(let A=0;A<m.length;A++){const g=m[A];I(g)&&Q(w,g.getAttribute(s),s,r,g)}}a&&a(c)}const S=[];if(e.indexOf("[")!==-1){const u=/\[([\w-]+)/g;let c;for(;(c=u.exec(e))!==null;)S.push(c[1])}else S.push(e);Ce({selector:e,attribute:t,componentTag:r,attributes:h,ComponentFn:n,onInit:a,observed:S.concat(i),onAttributeChange:l,onAttrChange:f,effects:b,declared:k,persist:F}),_e();const M=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";z().push({selector:e,attribute:t,ComponentFn:n,onInit:a,onSubtreeChange:d,query:M}),xe(),window[t]=_;function v(){ye()>0?q(function(){_(document.body)}):_(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",v):v(),_}function X(e){return(e||"").replace(/^#/,"")}function $(e){const t=e===void 0?location.hash:e,n={},r=X(t);if(!r)return n;const o=r.split("&");for(let i=0;i<o.length;i++){const l=o[i];if(!l)continue;const d=l.indexOf(":"),a=d>-1?l.slice(0,d):l,f=d>-1?l.slice(d+1):"";if(a)try{n[a]=decodeURIComponent(f)}catch{n[a]=f}}return n}function B(e){if(!e)return null;const t=$();return e in t?t[e]:null}function Y(e,t){if(!e)return;const n=$();t==null?delete n[e]:n[e]=String(t);const o=Object.keys(n).map(function(i){const l=n[i];return l===""?i:i+":"+encodeURIComponent(l)}).join("&");X(location.hash)!==o&&(location.hash=o)}function ve(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function N(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 r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function Ee(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function Te(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 ke(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function Ie(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),o=r?r.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:o}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=$,window.lnCore.hashGet=B,window.lnCore.hashSet=Y,window.lnCore.hashLinkClick=ve,window.lnCore.resolveHashNamespace=N,window.lnCore.hashSortEncode=Ee,window.lnCore.hashSortDecode=Te,window.lnCore.hashFilterEncode=ke,window.lnCore.hashFilterDecode=Ie);function Oe(){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=Oe);function H(e){return String(e||"").trim().toLowerCase()}function Le(e){const t=H(e);return t?t.split(/\s+/).filter(Boolean):[]}function De(e){if(e==null)return null;const t=String(e).split(",").map(n=>n.trim()).filter(Boolean);return t.length?t:null}function Re(e,t){if(!t||t.length===0)return!0;if(!e)return!1;const n=String(e).toLowerCase();for(let r=0;r<t.length;r++)if(n.indexOf(t[r])===-1)return!1;return!0}function qe(e){return!e||e.length===0?"":e.join(" ").replace(/\s+/g," ").trim().toLowerCase()}(function(){const e="data-ln-search",t="lnSearch",n="data-ln-search-for",r="lnSearchControl",o="data-ln-search-items",i="data-ln-search-fields",l="data-ln-search-exclude",d="data-ln-search-hide",a="data-ln-hash";if(window[t]!==void 0)return;const f={"data-ln-search":{type:"string",effect:v,description:"Active search query term on target element or container"},"data-ln-hash":{type:"string",effect:v,description:"URL hash routing key for search state persistence"},"data-ln-search-for":{prop:"targetId",type:"string",read:L,fallback:null,description:"Target table or list element ID that this input controls"},"data-ln-search-fields":{type:"list",description:"Comma-separated field names to include in client search"},"data-ln-search-items":{type:"string",description:"CSS selector matching searchable child items"},"data-ln-search-exclude":{type:"string",description:"CSS selector matching child items to exclude from search"},"data-ln-search-hide":{type:"enum",values:["collapse","none"],fallback:"collapse",description:"CSS hiding strategy for non-matching rows"},"data-ln-search-clear-for":{type:"trigger",description:"Click trigger to clear search input for target"}},p=ge(f);function h(u){const c=N(u,"search");if(c)return c;if(u.id){const s=document.querySelector("["+n+'="'+u.id+'"]');if(s){const w=N(s,"search");if(w)return w}}return null}function b(u){return u.matches("input, textarea")?u:u.querySelector("input, textarea")}function k(u,c){const s=u.childNodes;for(let w=0;w<s.length;w++){const m=s[w];if(m.nodeType===3){c.push(m.nodeValue);continue}m.nodeType===1&&(m.hasAttribute(l)||k(m,c))}}function F(u){if(u._lnSearchText!==void 0)return u._lnSearchText;const c=[];k(u,c);const s=qe(c);return u._lnSearchText=s,s}function _(u,c){if(!u.id)return;const s=document.querySelectorAll("["+n+'="'+u.id+'"]');for(const w of s){const m=b(w);m&&m.value!==c&&(m.value=c)}}function S(u){this.dom=u,this.term=u.getAttribute(e)||"",this._destroyed=!1;const c=this;return this.nsKey=h(u),this.hashEnabled=!!this.nsKey,this._onHashChange=function(){if(c._destroyed||!c.hashEnabled)return;const s=B(c.nsKey),w=c.dom.getAttribute(e)||"";s!==null&&s!==w?c.dom.setAttribute(e,s):s===null&&w!==""&&c.dom.setAttribute(e,"")},this.hashEnabled&&window.addEventListener("hashchange",this._onHashChange),q(function(){if(!c._destroyed){if(c.hashEnabled){const s=B(c.nsKey);if(s!==null&&s!==c.term){c.term=s,c.dom.setAttribute(e,s),_(c.dom,s),c._apply();return}}H(c.term)&&(_(c.dom,c.term),c._apply())}}),this}S.prototype._apply=function(){const u=this.dom,c=H(this.term),s=Le(c);this.hashEnabled&&Y(this.nsKey,this.term?this.term:null);const w=De(u.getAttribute(i));if(ee(u,"ln-search:change",{term:c,tokens:s,targetId:u.id,fields:w}).defaultPrevented)return;const A=u.getAttribute(o),g=A?u.querySelectorAll(A):u.children;for(let y=0;y<g.length;y++){const C=g[y];if(C.removeAttribute(d),C.hasAttribute(l)||s.length===0)continue;const E=F(C);Re(E,s)||C.setAttribute(d,"true")}},S.prototype.destroy=function(){this.dom[t]&&(this._destroyed=!0,this.hashEnabled&&this._onHashChange&&window.removeEventListener("hashchange",this._onHashChange),delete this.dom[t])};function x(u){if(this.dom=u,pe(this,u,p),this.input=b(u),this._attachHandler(),this.input&&this.input.value.trim()){const c=this;q(function(){const s=document.getElementById(c.targetId);s&&((s.getAttribute(e)||"").trim()||c._write(c.input.value))})}return this}x.prototype._write=function(u){const c=document.getElementById(this.targetId);c&&c.getAttribute(e)!==u&&c.setAttribute(e,u)},x.prototype._attachHandler=function(){if(!this.input)return;const u=this;this._onInput=function(){u._write(u.input.value)},this.input.addEventListener("input",this._onInput)},x.prototype.destroy=function(){this.dom[r]&&(this.input&&this._onInput&&this.input.removeEventListener("input",this._onInput),delete this.dom[r])};function M(u){const c=u.getAttribute("data-ln-search-clear-for");if(c){const g=document.getElementById(c),y=document.querySelector("["+n+'="'+c+'"]'),C=y?b(y):null;return{target:g,input:C}}const s=u.closest("["+e+"]");if(s){const g=s.id?document.querySelector("["+n+'="'+s.id+'"]'):null,y=g?b(g):null;return{target:s,input:y}}const w=u.closest("[data-ln-table-source], [data-ln-list-source]");if(w){const g=w.getAttribute("data-ln-table-source")||w.getAttribute("data-ln-list-source"),y=g?document.getElementById(g):null;if(y&&y.hasAttribute(e)){const C=document.querySelector("["+n+'="'+g+'"]'),E=C?b(C):null;return{target:y,input:E}}}const m=u.closest("["+n+"]");if(m){const g=m.getAttribute(n),y=g?document.getElementById(g):null,C=b(m);return{target:y,input:C}}const A=u.parentElement;if(A){const g=A.querySelector("["+n+"]");if(g){const y=g.getAttribute(n),C=y?document.getElementById(y):null,E=b(g);return{target:C,input:E}}}return{target:null,input:null}}document.addEventListener("click",function(u){const c=u.target.closest("[data-ln-search-clear], [data-ln-search-clear-for]");if(!c)return;const s=M(c);!s.target&&!s.input||(u.preventDefault(),s.input&&(s.input.value="",s.input.focus()),s.target&&s.target.setAttribute(e,""))});function v(u,c){const s=u[t];if(!s||s._destroyed)return;if(c===a){s._onHashChange&&window.removeEventListener("hashchange",s._onHashChange),s.nsKey=h(u),s.hashEnabled=!!s.nsKey,s.hashEnabled&&window.addEventListener("hashchange",s._onHashChange);return}const w=u.getAttribute(e)||"";w!==s.term&&(s.term=w,_(u,w),s._apply())}G(e,t,S,"ln-search",{attributes:f,onSubtreeChange:function(u,c){const s=c.target;s&&s._lnSearchText!==void 0&&delete s._lnSearchText,s&&s.parentElement&&s.parentElement._lnSearchText!==void 0&&delete s.parentElement._lnSearchText},persist:{attr:e,hashActive:function(u){return!!h(u)}}}),G(n,r,x,"ln-search-control")})()})();