@livenetworks/ashlar 2.1.12 → 2.2.0
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/README.md +25 -2
- package/components/COMPONENTS.md +29 -25
- package/components/ln-api-connector/ln-api-connector.schema.json +105 -105
- package/components/ln-chart/ln-chart-dev.scss +0 -4
- package/components/ln-core/README.md +16 -60
- package/components/ln-core/index.js +1 -1
- package/components/ln-core/ln-core.schema.json +120 -120
- package/components/ln-core/query.js +113 -113
- package/components/ln-core/reactive.js +0 -59
- package/components/ln-data-coordinator/README.md +2 -0
- package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -196
- package/components/ln-data-coordinator/src/ln-data-coordinator.js +457 -432
- package/components/ln-data-store/ln-data-store.schema.json +92 -92
- package/components/ln-date/ln-date.js +1 -1
- package/components/ln-date/src/ln-date.js +1 -1
- package/components/ln-debug/ln-debug.js +1 -1
- package/components/ln-debug/ln-debug.schema.json +28 -28
- package/components/ln-debug/src/generated-attributes.js +304 -302
- package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
- package/components/ln-external-links/README.md +19 -19
- package/components/ln-external-links/ln-external-links.js +1 -1
- package/components/ln-external-links/ln-external-links.schema.json +9 -0
- package/components/ln-external-links/ln-external-links.scss +12 -0
- package/components/ln-external-links/src/ln-external-links.js +3 -2
- package/components/ln-filter/README.md +2 -1
- package/components/ln-filter/ln-filter.js +1 -1
- package/components/ln-filter/ln-filter.schema.json +157 -169
- package/components/ln-filter/src/ln-filter.js +4 -4
- package/components/ln-form/ln-form.schema.json +57 -57
- package/components/ln-key/ln-key.js +1 -1
- package/components/ln-key/ln-key.schema.json +8 -0
- package/components/ln-key/src/ln-key.js +8 -5
- package/components/ln-obfuscator/ln-obfuscator.js +1 -1
- package/components/ln-obfuscator/ln-obfuscator.schema.json +6 -0
- package/components/ln-obfuscator/src/ln-obfuscator.js +2 -2
- package/components/ln-options/ln-options.schema.json +47 -47
- package/components/ln-popover/ln-popover.schema.json +82 -82
- package/components/ln-progress/ln-progress.schema.json +0 -13
- package/components/ln-search/ln-search.schema.json +111 -111
- package/components/ln-sort/README.md +1 -0
- package/components/ln-sort/ln-sort.js +1 -1
- package/components/ln-sort/ln-sort.schema.json +100 -112
- package/components/ln-sort/src/sort-model.js +1 -6
- package/components/ln-sortable/ln-sortable.schema.json +38 -38
- package/components/ln-sortable/ln-sortable.scss +25 -25
- package/components/ln-table/README.md +8 -1
- package/components/ln-table/ln-table.js +1 -1
- package/components/ln-table/ln-table.schema.json +195 -195
- package/components/ln-table/src/ln-table.js +43 -7
- package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
- package/components/ln-table-coordinator/ln-table-coordinator.schema.json +18 -0
- package/components/ln-table-coordinator/src/ln-table-coordinator.js +104 -2
- package/components/ln-toast/README.md +1 -1
- package/components/ln-toast/ln-toast.js +1 -1
- package/components/ln-toast/ln-toast.schema.json +6 -0
- package/components/ln-toast/src/ln-toast.js +1 -1
- package/components/ln-validate/README.md +6 -6
- package/components/ln-validate/ln-validate-dev.scss +0 -5
- package/components/ln-validate/ln-validate.js +1 -1
- package/components/ln-validate/ln-validate.schema.json +0 -6
- package/components/ln-validate/src/ln-validate.js +48 -29
- package/ln-ashlar.html-data.json +1824 -1814
- package/package.json +15 -5
- package/theme/README.md +74 -0
- package/theme/base/_global.scss +2 -2
- package/theme/base/_reset.scss +11 -0
- package/theme/components/_app-shell.scss +0 -8
- package/theme/components/_form.scss +6 -6
- package/theme/components/_ln-table.scss +6 -3
- package/theme/components/_scrollbar.scss +5 -4
- package/theme/components/_sortable.scss +19 -19
- package/theme/components/_stat-card.scss +1 -0
- package/theme/components/_table.scss +11 -35
- package/theme/config/_density.scss +110 -65
- package/theme/config/_palette.scss +71 -49
- package/theme/config/_theme.scss +27 -31
- package/theme/config/_tokens.scss +28 -26
- package/theme/config/mixins/_app-shell.scss +17 -21
- package/theme/config/mixins/_avatar.scss +2 -2
- package/theme/config/mixins/_btn.scss +4 -2
- package/theme/config/mixins/_card.scss +88 -71
- package/theme/config/mixins/_colors.scss +2 -1
- package/theme/config/mixins/_display.scss +12 -0
- package/theme/config/mixins/_footer.scss +3 -1
- package/theme/config/mixins/_form.scss +74 -55
- package/theme/config/mixins/_layout.scss +9 -0
- package/theme/config/mixins/_ln-table.scss +22 -15
- package/theme/config/mixins/_modal.scss +3 -0
- package/theme/config/mixins/_nav.scss +31 -8
- package/theme/config/mixins/_sidebar.scss +2 -1
- package/theme/config/mixins/_spacing.scss +4 -4
- package/theme/config/mixins/_stat-card.scss +33 -2
- package/theme/config/mixins/_status-badge.scss +1 -0
- package/theme/config/mixins/_table.scss +87 -69
- package/theme/config/mixins/_tabs.scss +31 -0
- package/theme/config/mixins/_toast.scss +0 -1
- package/theme/designs/relaxed/_card.scss +12 -0
- package/theme/designs/relaxed/_layout.scss +24 -0
- package/theme/ln-ashlar-core.scss +1 -0
- package/theme/ln-ashlar-dev.scss +0 -1
- package/theme/ln-ashlar-relaxed.scss +67 -0
- package/theme/tailwind/components.css +2198 -0
- package/theme/tailwind/entry.css +3 -0
- package/theme/tailwind/theme.css +23 -0
- package/theme/utilities/_utilities.scss +1 -11
- package/web-types.json +2351 -2337
- package/components/ln-progress/ln-progress-dev.scss +0 -8
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Combine autonomous components directly in semantic HTML — **zero initializatio
|
|
|
107
107
|
|
|
108
108
|
### 1. 🏛️ Dual-Core Architecture (SSR + Client-Side SPA)
|
|
109
109
|
- **Backend-First Progressive HTML Enhancement (SSR):** Your backend (Laravel, Go, Django, Rails) delivers indexable, semantic HTML. Elements auto-activate via attributes (`data-ln-modal`, `data-ln-filter`, `data-ln-table`).
|
|
110
|
-
- **Client-Side Single-Page Applications (SPA / JSON Mode):** Built-in client router (`ln-router`), compound hash-state codec (`hash.js`), 3-tier local storage (`ln-data-store`), and microtask-batched
|
|
110
|
+
- **Client-Side Single-Page Applications (SPA / JSON Mode):** Built-in client router (`ln-router`), compound hash-state codec (`hash.js`), 3-tier local storage (`ln-data-store`), and microtask-batched state rendering (`createBatcher`, `renderList`) when client JSON APIs are required.
|
|
111
111
|
|
|
112
112
|
### 2. 🤖 AI-Native & MCP Surface
|
|
113
113
|
- **Machine-Readable Component Corpus (`docs-mcp/`):** Schema-validated documentation served directly to AI agent workflows via Model Context Protocol (MCP).
|
|
@@ -184,7 +184,7 @@ Deep architectural blueprints detailing the engine driving `ln-ashlar`.
|
|
|
184
184
|
| 📖 **[Architecture Philosophy](docs/architecture/philosophy.md)** | The DOM-First engineering manifesto detailing computing cycles, framework EOL risks, performance, security, **and full 17-category comparison tables**. |
|
|
185
185
|
| 🔄 **[Data Flow Architecture](docs/architecture/data-flow.md)** | Rules governing how data moves. Splits responsibilities into **four isolated concerns**: Data (`ln-data-store` + `ln-data-coordinator`), Render (`ln-table`), Submit (`ln-form`), and Validate (`ln-validate`). Details the **parallel fan-out write pipeline** with Mermaid sequence diagrams. |
|
|
186
186
|
| 🧭 **[3-Tier Local-First Storage Specs](docs/architecture/data-store-architecture.md)** | Technical reference for decoupling storage caches (`ln-data-store`), network gateways (`ln-*-connector`), and data transformations (`ln-mapper`). |
|
|
187
|
-
| ⚡ **[Reactive Architecture Reference](docs/reactive.md)** | Blueprint on how components manage
|
|
187
|
+
| ⚡ **[Reactive Architecture Reference](docs/reactive.md)** | Blueprint on how components manage state using microtask batching (`createBatcher`), DOM bindings (`fill`, `renderList`), and attribute-to-state bridges. |
|
|
188
188
|
| 📐 **[Design System Specifications](docs/architecture/reference.md)** | Complete styling specs covering CSS Custom Properties, layout grids, buttons, responsive breakpoints, typography hierarchies, dark mode theming, and icons. |
|
|
189
189
|
| 🛡️ **[Security & Threat Mitigation](docs/architecture/security.md)** | Deep architectural security analysis covering Web Crypto API encryption-at-rest, strict CSP compliance (no dynamic eval), sensitive DOM attribute protections, and the same-origin AJAX fragment trust boundary. |
|
|
190
190
|
| 🧠 **[Architect Overview Guide](docs/architecture/overview.md)** | Master developer guide introducing the DOM-First doctrine, component inventories, override architectures, and new project integration pipelines. |
|
|
@@ -270,6 +270,29 @@ For detailed manual instructions, properties, attributes, and events of individu
|
|
|
270
270
|
|
|
271
271
|
---
|
|
272
272
|
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 🎨 Dual CSS Engine Architecture
|
|
276
|
+
|
|
277
|
+
`ln-ashlar` offers dual-engine visual styling with 100% design parity across all components and layouts:
|
|
278
|
+
|
|
279
|
+
1. **Native Ashlar SCSS:** Class-free semantic HTML styling and design tokens. Optimized for minimal CSSOM invalidation by eliminating mixin selector explosions and `:not(:has())` bottlenecks.
|
|
280
|
+
2. **Tailwind CSS v4 Engine:** Utility-first stylesheet compiled directly with Tailwind v4 `@theme` tokens (`npm run build:tailwind`).
|
|
281
|
+
|
|
282
|
+
See [Theme Architecture & SCSS Guidelines](theme/README.md) for details.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 🧪 Automated Testing & Performance Suite
|
|
287
|
+
|
|
288
|
+
`ln-ashlar` includes an automated headless verification and benchmarking harness comparing both CSS engines:
|
|
289
|
+
|
|
290
|
+
- **Visual & Design Suite (`npm run test:visual`):** Validates responsive layout, horizontal overflow, and reactive UI across all 67 demo admin pages.
|
|
291
|
+
- **JS Performance Suite (`npm run test:perf`):** Benchmarks execution latency, DOM node churn, and JS heap memory across 46 interactive JS components.
|
|
292
|
+
- **Historical Comparison (`npm run test:perf:compare`):** Tracks performance trends across git commits and alerts on performance regressions (`>20%`).
|
|
293
|
+
|
|
294
|
+
See [Headless Testing Documentation](tests/headless/README.md) for full usage and options.
|
|
295
|
+
|
|
273
296
|
## 🖥️ Interactive Demo Site
|
|
274
297
|
|
|
275
298
|
`ln-ashlar` ships with a complete local dashboard environment inside `demo/admin/`. The demo is itself a project consuming `ln-ashlar`, showing real-world layout structures, component setups, and customized semantic SCSS:
|
package/components/COMPONENTS.md
CHANGED
|
@@ -69,7 +69,7 @@ Every component is an IIFE (Immediately Invoked Function Expression). Shared hel
|
|
|
69
69
|
|
|
70
70
|
```javascript
|
|
71
71
|
import { dispatch } from '../ln-core';
|
|
72
|
-
import {
|
|
72
|
+
import { createBatcher } from '../ln-core';
|
|
73
73
|
|
|
74
74
|
(function () {
|
|
75
75
|
const DOM_SELECTOR = 'data-ln-{name}';
|
|
@@ -96,8 +96,6 @@ import { deepReactive, createBatcher } from '../ln-core';
|
|
|
96
96
|
| `fillTemplate(clone, data)` | Replace `{{ key }}` text-node placeholders in cloned template with `data[key]` values |
|
|
97
97
|
| `buildDict(root, selector)` | Read hidden i18n elements once at init, return plain object, remove from DOM |
|
|
98
98
|
| `renderList(container, items, tpl, keyFn, fillFn, tag)` | Keyed list rendering with DOM reuse |
|
|
99
|
-
| `reactiveState(initial, onChange)` | Shallow Proxy — onChange(prop, value, old) per set |
|
|
100
|
-
| `deepReactive(obj, onChange)` | Deep Proxy — onChange() on any nested change |
|
|
101
99
|
| `createBatcher(renderFn, afterRender)` | Coalesce multiple sync state changes into one render |
|
|
102
100
|
|
|
103
101
|
Import only what the component needs. Vite tree-shakes unused exports.
|
|
@@ -215,31 +213,38 @@ function _domObserver() {
|
|
|
215
213
|
|
|
216
214
|
## Reactive Rendering Pattern
|
|
217
215
|
|
|
218
|
-
When a component has **multiple internal state properties** that
|
|
216
|
+
When a component has **multiple internal state properties** or frequent updates that drive DOM updates, use `createBatcher` + `fill()` from ln-core. This eliminates manual querySelector chains and coalesces multiple synchronous state changes into a single DOM update.
|
|
219
217
|
|
|
220
|
-
### The
|
|
218
|
+
### The core primitives
|
|
221
219
|
|
|
222
220
|
| Helper | Role |
|
|
223
221
|
|--------|------|
|
|
224
|
-
| `reactiveState(initial, onChange)` | Proxy — calls `onChange(prop, value, old)` on every top-level property set |
|
|
225
222
|
| `createBatcher(renderFn, afterRender)` | Coalesces sync state changes into one `renderFn` call via `queueMicrotask` |
|
|
226
223
|
| `fill(root, data)` | Writes state to DOM via `data-ln-field`, `data-ln-attr`, `data-ln-show`, `data-ln-class` |
|
|
227
224
|
|
|
228
225
|
### Pattern
|
|
229
226
|
|
|
230
227
|
```javascript
|
|
231
|
-
import {
|
|
228
|
+
import { createBatcher, fill, dispatch } from '../ln-core';
|
|
232
229
|
|
|
233
230
|
function _component(dom) {
|
|
234
231
|
this.dom = dom;
|
|
235
232
|
const self = this;
|
|
236
233
|
|
|
237
|
-
|
|
238
|
-
function () { self._render(); }, // runs after current sync block
|
|
239
|
-
function () { self._afterRender(); } // runs after render — dispatch events here
|
|
240
|
-
);
|
|
234
|
+
this.state = { key: null, value: null };
|
|
241
235
|
|
|
242
|
-
this.
|
|
236
|
+
this.queueRender = createBatcher(
|
|
237
|
+
function () {
|
|
238
|
+
if (!self._destroyed) {
|
|
239
|
+
self._render();
|
|
240
|
+
}
|
|
241
|
+
}, // runs after current sync block
|
|
242
|
+
function () {
|
|
243
|
+
if (!self._destroyed) {
|
|
244
|
+
self._afterRender();
|
|
245
|
+
}
|
|
246
|
+
} // runs after render — dispatch events here
|
|
247
|
+
);
|
|
243
248
|
}
|
|
244
249
|
|
|
245
250
|
_component.prototype._render = function () {
|
|
@@ -250,31 +255,30 @@ _component.prototype._render = function () {
|
|
|
250
255
|
_component.prototype._afterRender = function () {
|
|
251
256
|
dispatch(this.dom, 'ln-component:changed', { key: this.state.key });
|
|
252
257
|
};
|
|
258
|
+
|
|
259
|
+
_component.prototype.update = function (key, value) {
|
|
260
|
+
this.state.key = key;
|
|
261
|
+
this.state.value = value;
|
|
262
|
+
this.queueRender(); // Multiple sync updates trigger exactly ONE render pass
|
|
263
|
+
};
|
|
253
264
|
```
|
|
254
265
|
|
|
255
266
|
When multiple properties change in the same sync block:
|
|
256
267
|
|
|
257
268
|
```javascript
|
|
258
|
-
this.state.key = 'status';
|
|
259
|
-
this.state.value = 'active';
|
|
269
|
+
this.state.key = 'status';
|
|
270
|
+
this.state.value = 'active';
|
|
271
|
+
this.queueRender();
|
|
260
272
|
// → microtask fires → _render() once → _afterRender() once
|
|
261
273
|
```
|
|
262
274
|
|
|
263
|
-
### `reactiveState` vs `deepReactive`
|
|
264
|
-
|
|
265
|
-
| | `reactiveState` | `deepReactive` |
|
|
266
|
-
|---|---|---|
|
|
267
|
-
| **Depth** | Shallow — top-level sets only | Deep — nested object/array mutations |
|
|
268
|
-
| **onChange args** | `(prop, value, old)` | `()` — no args |
|
|
269
|
-
| **Use when** | Flat state with primitive values | State contains nested objects or arrays |
|
|
270
|
-
|
|
271
275
|
### `fill()` — limitations
|
|
272
276
|
|
|
273
277
|
`fill()` sets `textContent` for `data-ln-field` — it does **not** set `.value` on `<input>`, `<textarea>`, or `<select>`. For form elements, set `.value` manually after `fill()`.
|
|
274
278
|
|
|
275
279
|
### When to use
|
|
276
280
|
|
|
277
|
-
Use
|
|
281
|
+
Use `createBatcher` when a component has **frequent or multiple state updates** (e.g. search input, filter checkboxes, sorting), and multiple updates can occur in the same synchronous cycle.
|
|
278
282
|
|
|
279
283
|
**Don't use it for:**
|
|
280
284
|
- Single boolean state — `this.isOpen = true; dom.classList.toggle(...)` is simpler
|
|
@@ -283,7 +287,7 @@ Use reactive rendering when a component has **2+ state properties** that togethe
|
|
|
283
287
|
|
|
284
288
|
### Used in
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
Used in `ln-search`, `ln-filter`, `ln-list`, and `ln-data-coordinator` for microtask render coalescing.
|
|
287
291
|
|
|
288
292
|
---
|
|
289
293
|
|
|
@@ -985,7 +989,7 @@ The component calls `_ensureDefaultItemTemplate()` at the top of `_initUpload()`
|
|
|
985
989
|
|
|
986
990
|
| Component | Pattern | Data Attr | Description |
|
|
987
991
|
|-----------|---------|-----------|------|
|
|
988
|
-
| ln-core | Shared module | — | cloneTemplate, cloneTemplateScoped, dispatch, dispatchCancelable, fill, fillTemplate, renderList, buildDict, guardBody, findElements,
|
|
992
|
+
| ln-core | Shared module | — | cloneTemplate, cloneTemplateScoped, dispatch, dispatchCancelable, fill, fillTemplate, renderList, buildDict, guardBody, findElements, createBatcher, createWindowCache |
|
|
989
993
|
| ln-toggle | Instance | `data-ln-toggle` | Generic toggle (sidebar, collapse) |
|
|
990
994
|
| ln-accordion | Instance | `data-ln-accordion` | Wrapper — only one toggle open at a time |
|
|
991
995
|
| ln-tabs | Instance | `data-ln-tabs` | Tab navigation — URL hash (anchor triggers) or localStorage persist (button triggers) |
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
|
|
3
|
-
"component": "ln-api-connector",
|
|
4
|
-
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
-
"attributes": {
|
|
6
|
-
"data-ln-api-base-url": {
|
|
7
|
-
"direction": null,
|
|
8
|
-
"sources": [
|
|
9
|
-
"src/ln-api-connector.js"
|
|
10
|
-
],
|
|
11
|
-
"type": "string",
|
|
12
|
-
"fallback": "",
|
|
13
|
-
"description": "Base URL endpoint for API requests"
|
|
14
|
-
},
|
|
15
|
-
"data-ln-api-connector": {
|
|
16
|
-
"direction": null,
|
|
17
|
-
"sources": [
|
|
18
|
-
"src/ln-api-connector.js"
|
|
19
|
-
],
|
|
20
|
-
"type": "marker",
|
|
21
|
-
"description": "Mounts API connector bridging REST backend and ln-ashlar data coordinators"
|
|
22
|
-
},
|
|
23
|
-
"data-ln-api-connector-query-debounce": {
|
|
24
|
-
"direction": "author",
|
|
25
|
-
"sources": [
|
|
26
|
-
"src/ln-api-connector.js"
|
|
27
|
-
],
|
|
28
|
-
"type": "integer",
|
|
29
|
-
"fallback": 200,
|
|
30
|
-
"min": 0,
|
|
31
|
-
"description": "Debounce delay in milliseconds before dispatching query requests"
|
|
32
|
-
},
|
|
33
|
-
"data-ln-api-endpoint": {
|
|
34
|
-
"direction": "author",
|
|
35
|
-
"sources": [
|
|
36
|
-
"src/ln-api-connector.js"
|
|
37
|
-
],
|
|
38
|
-
"type": "string",
|
|
39
|
-
"fallback": "",
|
|
40
|
-
"description": "Alias for data-ln-api-path"
|
|
41
|
-
},
|
|
42
|
-
"data-ln-api-headers": {
|
|
43
|
-
"direction": null,
|
|
44
|
-
"sources": [
|
|
45
|
-
"src/ln-api-connector.js"
|
|
46
|
-
],
|
|
47
|
-
"type": "json",
|
|
48
|
-
"description": "Custom HTTP headers in JSON format or semicolon-separated pairs"
|
|
49
|
-
},
|
|
50
|
-
"data-ln-api-param-limit": {
|
|
51
|
-
"direction": "author",
|
|
52
|
-
"sources": [
|
|
53
|
-
"src/ln-api-connector.js"
|
|
54
|
-
],
|
|
55
|
-
"type": "string",
|
|
56
|
-
"fallback": "limit",
|
|
57
|
-
"description": "Query parameter name for pagination page size"
|
|
58
|
-
},
|
|
59
|
-
"data-ln-api-param-offset": {
|
|
60
|
-
"direction": "author",
|
|
61
|
-
"sources": [
|
|
62
|
-
"src/ln-api-connector.js"
|
|
63
|
-
],
|
|
64
|
-
"type": "string",
|
|
65
|
-
"fallback": "offset",
|
|
66
|
-
"description": "Query parameter name for pagination offset"
|
|
67
|
-
},
|
|
68
|
-
"data-ln-api-param-search": {
|
|
69
|
-
"direction": "author",
|
|
70
|
-
"sources": [
|
|
71
|
-
"src/ln-api-connector.js"
|
|
72
|
-
],
|
|
73
|
-
"type": "string",
|
|
74
|
-
"fallback": "search",
|
|
75
|
-
"description": "Query parameter name for text search filter"
|
|
76
|
-
},
|
|
77
|
-
"data-ln-api-param-sort-dir": {
|
|
78
|
-
"direction": "author",
|
|
79
|
-
"sources": [
|
|
80
|
-
"src/ln-api-connector.js"
|
|
81
|
-
],
|
|
82
|
-
"type": "string",
|
|
83
|
-
"fallback": "sort_dir",
|
|
84
|
-
"description": "Query parameter name for sort direction"
|
|
85
|
-
},
|
|
86
|
-
"data-ln-api-param-sort-field": {
|
|
87
|
-
"direction": "author",
|
|
88
|
-
"sources": [
|
|
89
|
-
"src/ln-api-connector.js"
|
|
90
|
-
],
|
|
91
|
-
"type": "string",
|
|
92
|
-
"fallback": "sort_by",
|
|
93
|
-
"description": "Query parameter name for sort field"
|
|
94
|
-
},
|
|
95
|
-
"data-ln-api-path": {
|
|
96
|
-
"direction": "author",
|
|
97
|
-
"sources": [
|
|
98
|
-
"src/ln-api-connector.js"
|
|
99
|
-
],
|
|
100
|
-
"type": "string",
|
|
101
|
-
"fallback": "",
|
|
102
|
-
"description": "Resource path appended to base URL"
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
|
|
3
|
+
"component": "ln-api-connector",
|
|
4
|
+
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
+
"attributes": {
|
|
6
|
+
"data-ln-api-base-url": {
|
|
7
|
+
"direction": null,
|
|
8
|
+
"sources": [
|
|
9
|
+
"src/ln-api-connector.js"
|
|
10
|
+
],
|
|
11
|
+
"type": "string",
|
|
12
|
+
"fallback": "",
|
|
13
|
+
"description": "Base URL endpoint for API requests"
|
|
14
|
+
},
|
|
15
|
+
"data-ln-api-connector": {
|
|
16
|
+
"direction": null,
|
|
17
|
+
"sources": [
|
|
18
|
+
"src/ln-api-connector.js"
|
|
19
|
+
],
|
|
20
|
+
"type": "marker",
|
|
21
|
+
"description": "Mounts API connector bridging REST backend and ln-ashlar data coordinators"
|
|
22
|
+
},
|
|
23
|
+
"data-ln-api-connector-query-debounce": {
|
|
24
|
+
"direction": "author",
|
|
25
|
+
"sources": [
|
|
26
|
+
"src/ln-api-connector.js"
|
|
27
|
+
],
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"fallback": 200,
|
|
30
|
+
"min": 0,
|
|
31
|
+
"description": "Debounce delay in milliseconds before dispatching query requests"
|
|
32
|
+
},
|
|
33
|
+
"data-ln-api-endpoint": {
|
|
34
|
+
"direction": "author",
|
|
35
|
+
"sources": [
|
|
36
|
+
"src/ln-api-connector.js"
|
|
37
|
+
],
|
|
38
|
+
"type": "string",
|
|
39
|
+
"fallback": "",
|
|
40
|
+
"description": "Alias for data-ln-api-path"
|
|
41
|
+
},
|
|
42
|
+
"data-ln-api-headers": {
|
|
43
|
+
"direction": null,
|
|
44
|
+
"sources": [
|
|
45
|
+
"src/ln-api-connector.js"
|
|
46
|
+
],
|
|
47
|
+
"type": "json",
|
|
48
|
+
"description": "Custom HTTP headers in JSON format or semicolon-separated pairs"
|
|
49
|
+
},
|
|
50
|
+
"data-ln-api-param-limit": {
|
|
51
|
+
"direction": "author",
|
|
52
|
+
"sources": [
|
|
53
|
+
"src/ln-api-connector.js"
|
|
54
|
+
],
|
|
55
|
+
"type": "string",
|
|
56
|
+
"fallback": "limit",
|
|
57
|
+
"description": "Query parameter name for pagination page size"
|
|
58
|
+
},
|
|
59
|
+
"data-ln-api-param-offset": {
|
|
60
|
+
"direction": "author",
|
|
61
|
+
"sources": [
|
|
62
|
+
"src/ln-api-connector.js"
|
|
63
|
+
],
|
|
64
|
+
"type": "string",
|
|
65
|
+
"fallback": "offset",
|
|
66
|
+
"description": "Query parameter name for pagination offset"
|
|
67
|
+
},
|
|
68
|
+
"data-ln-api-param-search": {
|
|
69
|
+
"direction": "author",
|
|
70
|
+
"sources": [
|
|
71
|
+
"src/ln-api-connector.js"
|
|
72
|
+
],
|
|
73
|
+
"type": "string",
|
|
74
|
+
"fallback": "search",
|
|
75
|
+
"description": "Query parameter name for text search filter"
|
|
76
|
+
},
|
|
77
|
+
"data-ln-api-param-sort-dir": {
|
|
78
|
+
"direction": "author",
|
|
79
|
+
"sources": [
|
|
80
|
+
"src/ln-api-connector.js"
|
|
81
|
+
],
|
|
82
|
+
"type": "string",
|
|
83
|
+
"fallback": "sort_dir",
|
|
84
|
+
"description": "Query parameter name for sort direction"
|
|
85
|
+
},
|
|
86
|
+
"data-ln-api-param-sort-field": {
|
|
87
|
+
"direction": "author",
|
|
88
|
+
"sources": [
|
|
89
|
+
"src/ln-api-connector.js"
|
|
90
|
+
],
|
|
91
|
+
"type": "string",
|
|
92
|
+
"fallback": "sort_by",
|
|
93
|
+
"description": "Query parameter name for sort field"
|
|
94
|
+
},
|
|
95
|
+
"data-ln-api-path": {
|
|
96
|
+
"direction": "author",
|
|
97
|
+
"sources": [
|
|
98
|
+
"src/ln-api-connector.js"
|
|
99
|
+
],
|
|
100
|
+
"type": "string",
|
|
101
|
+
"fallback": "",
|
|
102
|
+
"description": "Resource path appended to base URL"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
@use '../../theme/config/mixins' as *;
|
|
2
2
|
|
|
3
3
|
[data-ln-debug] {
|
|
4
|
-
[data-ln-chart]:not(.chart) {
|
|
5
|
-
@include dev-dom-error("[data-ln-chart] is missing the required .chart visual class.");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
4
|
[data-ln-chart]:not([data-ln-chart-x]),
|
|
9
5
|
[data-ln-chart]:not([data-ln-chart-y]) {
|
|
10
6
|
@include dev-dom-error("[data-ln-chart] requires data-ln-chart-x and data-ln-chart-y.");
|
|
@@ -13,7 +13,7 @@ ln-core exposes helpers in these categories:
|
|
|
13
13
|
- **DOM binding** — declarative data-attribute-driven rendering (`fill`, `renderList`, `fillTemplate`)
|
|
14
14
|
- **Forms & Inputs** — serialize/populate by field `name`, intercept input value getters/setters (`interceptValueProperty`)
|
|
15
15
|
- **Target & Event Predicates** — `shouldIgnoreClick`, `isTargetDisabled`, `isUsableTarget`, `isEditableTarget`
|
|
16
|
-
- **Reactivity** —
|
|
16
|
+
- **Reactivity & Batching** — microtask-coalesced render batcher (`createBatcher`)
|
|
17
17
|
- **Windowing** — sliding-window sparse cache for server-side virtualization (`createWindowCache`)
|
|
18
18
|
- **Layout & Positioning** — viewport-aware positioning (`computePlacement`), measurement (`measureHidden`)
|
|
19
19
|
- **Hash codec** — namespaced URL fragment state (`hashParse`, `hashGet`, `hashSet`, `hashSortEncode`, `hashSortDecode`, `hashFilterEncode`, `hashFilterDecode`, `resolveHashNamespace`)
|
|
@@ -542,37 +542,6 @@ fetch('/tpl.html').then(html => {
|
|
|
542
542
|
|
|
543
543
|
## reactive.js
|
|
544
544
|
|
|
545
|
-
### reactiveState(initial, onChange)
|
|
546
|
-
|
|
547
|
-
Shallow `Proxy` — fires `onChange(prop, value, old)` on every property set. No-op if value hasn't changed.
|
|
548
|
-
|
|
549
|
-
```js
|
|
550
|
-
const state = reactiveState({ searchTerm: '', sortCol: -1 }, function (prop, value, old) {
|
|
551
|
-
batchRender();
|
|
552
|
-
});
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
- Returns a Proxy wrapping a shallow copy of `initial`
|
|
556
|
-
- Only tracks direct property assignments (not nested objects)
|
|
557
|
-
- Used by `ln-filter` for flat state
|
|
558
|
-
|
|
559
|
-
### deepReactive(obj, onChange)
|
|
560
|
-
|
|
561
|
-
Deep `Proxy` — recursively wraps nested objects and arrays. Fires `onChange()` (no args) on any nested mutation.
|
|
562
|
-
|
|
563
|
-
```js
|
|
564
|
-
const state = deepReactive({ toasts: [], files: [] }, function () {
|
|
565
|
-
batchRender();
|
|
566
|
-
});
|
|
567
|
-
state.toasts.push({ title: 'Hello' }); // triggers onChange
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
- Wraps existing nested objects on creation
|
|
571
|
-
- Auto-wraps newly assigned objects/arrays
|
|
572
|
-
- `deleteProperty` also triggers `onChange`
|
|
573
|
-
- Internal `Symbol('deepReactive')` flag prevents double-wrapping
|
|
574
|
-
- Used by `ln-toast`, `ln-upload` for complex state
|
|
575
|
-
|
|
576
545
|
### createBatcher(renderFn, afterRender)
|
|
577
546
|
|
|
578
547
|
Coalesce multiple synchronous state changes into a single `renderFn` call via `queueMicrotask`.
|
|
@@ -586,39 +555,24 @@ const batchRender = createBatcher(
|
|
|
586
555
|
|
|
587
556
|
- Multiple calls to `schedule()` within the same microtask result in one `renderFn` call
|
|
588
557
|
- Optional `afterRender` callback fires after each render
|
|
589
|
-
- Pattern: `
|
|
558
|
+
- Pattern: used in `ln-search`, `ln-filter`, `ln-list`, `ln-data-coordinator` to debounce/coalesce state and render operations
|
|
590
559
|
|
|
591
560
|
---
|
|
592
561
|
|
|
593
562
|
## Reactive Rendering Pattern
|
|
594
563
|
|
|
595
|
-
The
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
2. **Microtask queue.** The onChange is wired to a `createBatcher`
|
|
603
|
-
schedule function. Any number of synchronous assignments in the
|
|
604
|
-
same sync block schedule a single microtask.
|
|
605
|
-
3. **One render.** When the microtask fires, the batcher calls
|
|
606
|
-
`renderFn` exactly once. `renderFn` typically calls `fill(this.dom,
|
|
607
|
-
this.state)` (or `renderList` for keyed collections) to project
|
|
608
|
-
state into the DOM, then the optional `afterRender` callback fires
|
|
609
|
-
(commonly used to dispatch a `ln-{component}:rendered` event).
|
|
610
|
-
|
|
611
|
-
Two assignments on the same tick produce one DOM update. External
|
|
612
|
-
callers that need to set several properties at once do not need a
|
|
613
|
-
`batch()` helper — the batcher handles it.
|
|
564
|
+
The `createBatcher` primitive and DOM rendering helpers (`fill`, `renderList`) are designed to be used together as the canonical ln-ashlar microtask render loop:
|
|
565
|
+
|
|
566
|
+
1. **State mutation.** Component updates internal data properties (e.g. `this.state.key = 'val'`).
|
|
567
|
+
2. **Microtask queue.** The component triggers the `schedule()` function returned by `createBatcher`. Any number of synchronous assignments in the same sync block schedule a single microtask.
|
|
568
|
+
3. **One render.** When the microtask fires, the batcher calls `renderFn` exactly once. `renderFn` typically calls `fill(this.dom, this.state)` (or `renderList` for keyed collections) to project state into the DOM, then the optional `afterRender` callback fires (commonly used to dispatch a `ln-{component}:rendered` or changed event).
|
|
569
|
+
|
|
570
|
+
Two state assignments on the same tick produce one DOM update:
|
|
614
571
|
|
|
615
572
|
The pattern is: **state mutation → microtask queue → one render**.
|
|
616
|
-
Never wire
|
|
617
|
-
batcher between them — you will get one render per assignment, and
|
|
618
|
-
the DOM will thrash.
|
|
573
|
+
Never wire sync state updates directly to immediate DOM writes when multiple attributes or filter parameters can change simultaneously — batching prevents layout thrashing.
|
|
619
574
|
|
|
620
|
-
For
|
|
621
|
-
to use this pattern at all, anti-patterns), see
|
|
575
|
+
For further details, see
|
|
622
576
|
[Reactive Rendering Pattern in components/COMPONENTS.md](../../components/COMPONENTS.md#reactive-rendering-pattern).
|
|
623
577
|
|
|
624
578
|
---
|
|
@@ -981,7 +935,7 @@ Returns `true` if the element is an `<input>`, `<textarea>`, `<select>`, or `[co
|
|
|
981
935
|
## Typical Usage Pattern
|
|
982
936
|
|
|
983
937
|
```js
|
|
984
|
-
import { dispatch, dispatchCancelable, guardBody, findElements,
|
|
938
|
+
import { dispatch, dispatchCancelable, guardBody, findElements, createBatcher, fill, renderList } from '../ln-core';
|
|
985
939
|
|
|
986
940
|
(function () {
|
|
987
941
|
const DOM_SELECTOR = 'data-ln-example';
|
|
@@ -997,8 +951,10 @@ import { dispatch, dispatchCancelable, guardBody, findElements, deepReactive, cr
|
|
|
997
951
|
const self = this;
|
|
998
952
|
|
|
999
953
|
// Reactive state + batched rendering
|
|
1000
|
-
|
|
1001
|
-
this.
|
|
954
|
+
this.state = { items: [] };
|
|
955
|
+
this.batchRender = createBatcher(function () {
|
|
956
|
+
if (!self._destroyed) self._render();
|
|
957
|
+
});
|
|
1002
958
|
|
|
1003
959
|
return this;
|
|
1004
960
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { cloneTemplate, cloneTemplateScoped, dispatch, dispatchCancelable, requestData, fill, lnFill, fillTemplate, renderList, buildDict, guardBody, findElements, isVisible, shouldIgnoreClick, isTargetDisabled, isUsableTarget, isEditableTarget, serializeForm, populateForm, resolveFormMethod, getLocale, readValue, detectValueType, compareValues, registerComponent, shouldInterceptLink, buildUrl, getHeaders, parseHeaders, registerDataMapper, getDataMapper, interceptValueProperty, registerLocaleFallback, getLocaleFallback, holdInit, releaseInit, pendingCount, queueBoot, ensureLocaleObserver, observeAttributes, observeLifecycle, withoutLifecycle, setDebugSink, setPersistSink, isDevMode, hasActiveDebug } from './helpers.js';
|
|
2
|
-
export {
|
|
2
|
+
export { createBatcher } from './reactive.js';
|
|
3
3
|
export { createWindowCache } from './window-cache.js';
|
|
4
4
|
export { hashParse, hashGet, hashSet, hashLinkClick, resolveHashNamespace, hashSortEncode, hashSortDecode, hashFilterEncode, hashFilterDecode } from './hash.js';
|
|
5
5
|
export { queryParse, queryGet, querySet, resolveQueryParam, querySortEncode, querySortDecode, queryFilterEncode, queryFilterDecode } from './query.js';
|