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