@livenetworks/ashlar 1.5.1 → 1.6.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 +189 -177
- package/js/COMPONENTS.md +55 -49
- package/js/index.js +22 -5
- package/js/ln-accordion/README.md +186 -137
- package/js/ln-accordion/ln-accordion.js +1 -1
- package/js/ln-ajax/README.md +73 -4
- package/js/ln-ajax/ln-ajax.js +1 -1
- package/js/ln-ajax/src/ln-ajax.js +13 -13
- package/js/ln-api-connector/README.md +261 -150
- package/js/ln-api-connector/ln-api-connector.js +1 -1
- package/js/ln-api-connector/src/ln-api-connector.js +368 -265
- package/js/ln-api-queue/README.md +219 -0
- package/js/ln-api-queue/ln-api-queue.js +1 -0
- package/js/ln-api-queue/src/ln-api-queue.js +320 -0
- package/js/ln-api-queue/src/queue-storage.js +455 -0
- package/js/ln-autoresize/README.md +47 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -1
- package/js/ln-autosave/README.md +33 -1
- package/js/ln-autosave/ln-autosave.js +1 -1
- package/js/ln-autosave/src/ln-autosave.js +4 -4
- package/js/ln-circular-progress/README.md +27 -13
- package/js/ln-circular-progress/ln-circular-progress.js +1 -1
- package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
- package/js/ln-confirm/README.md +162 -86
- package/js/ln-confirm/_ln-confirm.scss +11 -0
- package/js/ln-confirm/ln-confirm.js +1 -1
- package/js/ln-confirm/src/ln-confirm.js +77 -41
- package/js/ln-core/README.md +896 -0
- package/js/ln-core/hash.js +92 -0
- package/js/ln-core/helpers.js +337 -8
- package/js/ln-core/index.js +4 -2
- package/js/ln-core/persist.js +29 -3
- package/js/ln-core/positioning.js +1 -35
- package/js/ln-core/window-cache.js +213 -0
- package/js/ln-couchdb-connector/README.md +177 -156
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
- package/js/ln-data-coordinator/README.md +536 -165
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
- package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
- package/js/ln-data-store/README.md +241 -94
- package/js/ln-data-store/ln-data-store.js +1 -1
- package/js/ln-data-store/src/ln-data-store.js +694 -699
- package/js/ln-date/README.md +39 -1
- package/js/ln-date/ln-date-dev.scss +14 -0
- package/js/ln-date/ln-date.js +1 -1
- package/js/ln-date/src/ln-date.js +236 -178
- package/js/ln-debug/README.md +58 -0
- package/js/ln-debug/ln-debug.js +1 -0
- package/js/ln-debug/src/ln-debug.js +19 -0
- package/js/ln-dropdown/README.md +31 -8
- package/js/ln-dropdown/ln-dropdown.js +1 -1
- package/js/ln-dropdown/src/ln-dropdown.js +30 -13
- package/js/ln-editor/README.md +274 -0
- package/js/ln-editor/ln-editor.js +1 -0
- package/js/ln-editor/ln-editor.scss +43 -0
- package/js/ln-editor/src/ln-editor.js +619 -0
- package/js/ln-external-links/README.md +44 -5
- package/js/ln-external-links/ln-external-links.js +1 -1
- package/js/ln-fill/README.md +199 -0
- package/js/ln-fill/ln-fill.js +1 -0
- package/js/ln-fill/src/ln-fill.js +87 -0
- package/js/ln-filter/README.md +92 -2
- package/js/ln-filter/ln-filter-dev.scss +8 -0
- package/js/ln-filter/ln-filter.js +1 -1
- package/js/ln-filter/src/ln-filter.js +22 -7
- package/js/ln-form/README.md +212 -49
- package/js/ln-form/ln-form.js +1 -1
- package/js/ln-form/src/ln-form.js +99 -199
- package/js/ln-http/README.md +125 -89
- package/js/ln-http/ln-http.js +1 -1
- package/js/ln-http/src/ln-http.js +32 -13
- package/js/ln-icon/README.md +130 -0
- package/js/ln-icon/ln-icon.js +1 -0
- package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
- package/js/ln-link/README.md +47 -9
- package/js/ln-link/ln-link.js +1 -1
- package/js/ln-link/src/ln-link.js +22 -6
- package/js/ln-list/README.md +129 -0
- package/js/ln-list/ln-list.js +1 -0
- package/js/ln-list/ln-list.scss +28 -0
- package/js/ln-list/src/ln-list.js +1216 -0
- package/js/ln-modal/README.md +185 -154
- package/js/ln-modal/ln-modal-dev.scss +13 -0
- package/js/ln-modal/ln-modal.js +1 -1
- package/js/ln-modal/ln-modal.scss +28 -4
- package/js/ln-modal/src/ln-modal.js +137 -201
- package/js/ln-modal-coordinator/README.md +99 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
- package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
- package/js/ln-nav/README.md +46 -3
- package/js/ln-nav/ln-nav.js +1 -1
- package/js/ln-nav/src/ln-nav.js +92 -127
- package/js/ln-number/README.md +28 -1
- package/js/ln-number/ln-number-dev.scss +13 -0
- package/js/ln-number/ln-number.js +1 -1
- package/js/ln-number/src/ln-number.js +443 -350
- package/js/ln-options/README.md +66 -0
- package/js/ln-options/ln-options.js +1 -0
- package/js/ln-options/src/ln-options.js +74 -0
- package/js/ln-popover/README.md +40 -5
- package/js/ln-popover/ln-popover.js +1 -1
- package/js/ln-popover/src/ln-popover.js +38 -11
- package/js/ln-progress/README.md +413 -442
- package/js/ln-progress/ln-progress.js +1 -1
- package/js/ln-progress/src/ln-progress.js +98 -150
- package/js/ln-router/README.md +432 -0
- package/js/ln-router/ln-router.js +1 -0
- package/js/ln-router/src/ln-router.js +539 -0
- package/js/ln-search/README.md +158 -83
- package/js/ln-search/ln-search-dev.scss +8 -0
- package/js/ln-search/ln-search.js +1 -1
- package/js/ln-search/src/ln-search.js +12 -9
- package/js/ln-slug/README.md +110 -0
- package/js/ln-slug/ln-slug.js +1 -0
- package/js/ln-slug/src/ln-slug.js +74 -0
- package/js/ln-sort/README.md +170 -0
- package/js/ln-sort/ln-sort-dev.scss +8 -0
- package/js/ln-sort/ln-sort.js +1 -0
- package/js/ln-sort/src/ln-sort.js +178 -0
- package/js/ln-sortable/README.md +133 -95
- package/js/ln-sortable/ln-sortable.js +1 -1
- package/js/ln-stat/README.md +55 -0
- package/js/ln-stat/ln-stat.js +1 -0
- package/js/ln-stat/src/ln-stat.js +55 -0
- package/js/ln-table/README.md +285 -101
- package/js/ln-table/ln-table-dev.scss +17 -0
- package/js/ln-table/ln-table.js +1 -1
- package/js/ln-table/ln-table.scss +1 -1
- package/js/ln-table/src/ln-table.js +1493 -473
- package/js/ln-table-coordinator/README.md +73 -0
- package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
- package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
- package/js/ln-tabs/README.md +221 -137
- package/js/ln-tabs/ln-tabs-dev.scss +18 -0
- package/js/ln-tabs/ln-tabs.js +1 -1
- package/js/ln-tabs/src/ln-tabs.js +41 -24
- package/js/ln-time/README.md +14 -0
- package/js/ln-time/ln-time.js +1 -1
- package/js/ln-time/src/ln-time.js +223 -192
- package/js/ln-toast/README.md +178 -122
- package/js/ln-toast/ln-toast.js +1 -15
- package/js/ln-toast/src/ln-toast.js +232 -210
- package/js/ln-toast/template.html +16 -12
- package/js/ln-toggle/README.md +61 -2
- package/js/ln-toggle/ln-toggle-dev.scss +13 -0
- package/js/ln-toggle/ln-toggle.js +1 -1
- package/js/ln-toggle/src/ln-toggle.js +56 -43
- package/js/ln-tooltip/README.md +95 -58
- package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -1
- package/js/ln-tooltip/src/ln-tooltip.js +45 -6
- package/js/ln-translations/README.md +118 -96
- package/js/ln-translations/ln-translations.js +1 -1
- package/js/ln-translations/src/ln-translations.js +10 -5
- package/js/ln-upload/README.md +85 -10
- package/js/ln-upload/ln-upload.js +1 -1
- package/js/ln-upload/src/ln-upload.js +26 -10
- package/js/ln-validate/README.md +72 -0
- package/js/ln-validate/ln-validate-dev.scss +15 -0
- package/js/ln-validate/ln-validate.js +1 -1
- package/js/ln-validate/src/ln-validate.js +64 -0
- package/package.json +61 -56
- package/scss/base/_global.scss +2 -2
- package/scss/base/_typography.scss +1 -2
- package/scss/components/_date.scss +1 -0
- package/scss/components/_dropdown.scss +0 -5
- package/scss/components/_editor.scss +5 -0
- package/scss/components/_filter.scss +26 -25
- package/scss/components/_form.scss +7 -0
- package/scss/components/_ln-table.scss +13 -3
- package/scss/components/_sort.scss +10 -0
- package/scss/components/_table.scss +36 -23
- package/scss/components/_toast.scss +68 -41
- package/scss/components/_toggle.scss +16 -0
- package/scss/config/_density.scss +205 -117
- package/scss/config/_icons.scss +10 -9
- package/scss/config/_tokens.scss +69 -47
- package/scss/config/mixins/_accordion.scss +2 -7
- package/scss/config/mixins/_alert.scss +4 -4
- package/scss/config/mixins/_breadcrumbs.scss +2 -2
- package/scss/config/mixins/_btn.scss +1 -1
- package/scss/config/mixins/_card.scss +89 -14
- package/scss/config/mixins/_collapsible.scss +3 -0
- package/scss/config/mixins/_colors.scss +1 -1
- package/scss/config/mixins/_diagnostics.scss +11 -0
- package/scss/config/mixins/_display.scss +1 -0
- package/scss/config/mixins/_dropdown.scss +3 -5
- package/scss/config/mixins/_editor.scss +124 -0
- package/scss/config/mixins/_empty-state.scss +7 -0
- package/scss/config/mixins/_filter.scss +3 -5
- package/scss/config/mixins/_footer.scss +1 -2
- package/scss/config/mixins/_form.scss +207 -21
- package/scss/config/mixins/_index.scss +2 -0
- package/scss/config/mixins/_ln-table.scss +41 -2
- package/scss/config/mixins/_modal.scss +149 -66
- package/scss/config/mixins/_nav.scss +3 -4
- package/scss/config/mixins/_popover.scss +39 -0
- package/scss/config/mixins/_sidebar.scss +1 -1
- package/scss/config/mixins/_sort.scss +42 -0
- package/scss/config/mixins/_table.scss +85 -60
- package/scss/config/mixins/_toast.scss +179 -152
- package/scss/config/mixins/_tooltip.scss +17 -7
- package/scss/config/mixins/_typography.scss +4 -2
- package/scss/ln-ashlar-dev.scss +27 -9
- package/scss/ln-ashlar.scss +7 -1
- package/js/ln-data-table/README.md +0 -110
- package/js/ln-data-table/ln-data-table.js +0 -1
- package/js/ln-data-table/ln-data-table.scss +0 -10
- package/js/ln-data-table/src/ln-data-table.js +0 -1087
- package/js/ln-icons/README.md +0 -88
- package/js/ln-icons/ln-icons.js +0 -1
- package/js/ln-table/ln-table-sort.js +0 -1
- package/js/ln-table/src/ln-table-sort.js +0 -156
package/js/ln-toast/README.md
CHANGED
|
@@ -1,122 +1,178 @@
|
|
|
1
|
-
# ln-toast
|
|
2
|
-
|
|
3
|
-
> Service-style non-blocking status notifications, managed reactively via window events.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Philosophy & The Toast Mindset
|
|
8
|
-
|
|
9
|
-
In `ln-ashlar`, the core design principle is **decoupling**. Toasts are a pure application-level service.
|
|
10
|
-
|
|
11
|
-
1. **Zero-Markup Dispatch (JavaScript)**: Components and pages never import toast files or call imperative layout methods directly. To show a notification, any script or controller simply dispatches an `ln-toast:enqueue` event to the global `window` object.
|
|
12
|
-
2. **Central Coordination (The Service)**: A single viewport container (`[data-ln-toast]`) listens for these window-level events, builds card elements dynamically from templates, coordinates automatic 6-second exit timers, pauses timers on mouse hover, and destroys elements after animations.
|
|
13
|
-
3. **Decoupled Styling (CSS)**: Visual alert chrome, slide-in animations, and side-accent status colors are handled in Vanilla CSS. The library ships mixins like `@include toast-container` and `@include toast-card` for styling.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 2. Minimal Blueprint
|
|
18
|
-
|
|
19
|
-
To enable toasts, simply place a single container `<ul>` in your HTML layout (typically right before the closing `</body>` tag).
|
|
20
|
-
|
|
21
|
-
```html
|
|
22
|
-
<!-- The Central Toast Container -->
|
|
23
|
-
<ul data-ln-toast data-ln-toast-timeout="6000" data-ln-toast-max="5"></ul>
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Key Anatomy Rules
|
|
27
|
-
- **The Container (`data-ln-toast`)**: Creates the toast listener service.
|
|
28
|
-
- **Auto-Dismiss Timeout (`data-ln-toast-timeout="6000"`)**: Default dismissal duration in milliseconds. Use `0` for persistent notifications.
|
|
29
|
-
- **Max Stack size (`data-ln-toast-max="5"`)**: Evicts the oldest toast when the count exceeds the threshold.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
1
|
+
# ln-toast
|
|
2
|
+
|
|
3
|
+
> Service-style non-blocking status notifications, managed reactively via window events.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Philosophy & The Toast Mindset
|
|
8
|
+
|
|
9
|
+
In `ln-ashlar`, the core design principle is **decoupling**. Toasts are a pure application-level service.
|
|
10
|
+
|
|
11
|
+
1. **Zero-Markup Dispatch (JavaScript)**: Components and pages never import toast files or call imperative layout methods directly. To show a notification, any script or controller simply dispatches an `ln-toast:enqueue` event to the global `window` object.
|
|
12
|
+
2. **Central Coordination (The Service)**: A single viewport container (`[data-ln-toast]`) listens for these window-level events, builds card elements dynamically from templates, coordinates automatic 6-second exit timers, pauses timers on mouse hover, and destroys elements after animations.
|
|
13
|
+
3. **Decoupled Styling (CSS)**: Visual alert chrome, slide-in animations, and side-accent status colors are handled in Vanilla CSS. The library ships mixins like `@include toast-container` and `@include toast-card` for styling.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Minimal Blueprint
|
|
18
|
+
|
|
19
|
+
To enable toasts, simply place a single container `<ul>` in your HTML layout (typically right before the closing `</body>` tag).
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<!-- The Central Toast Container -->
|
|
23
|
+
<ul data-ln-toast data-ln-toast-timeout="6000" data-ln-toast-max="5" aria-live="polite" aria-atomic="false"></ul>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Key Anatomy Rules
|
|
27
|
+
- **The Container (`data-ln-toast`)**: Creates the toast listener service.
|
|
28
|
+
- **Auto-Dismiss Timeout (`data-ln-toast-timeout="6000"`)**: Default dismissal duration in milliseconds. Use `0` for persistent notifications.
|
|
29
|
+
- **Max Stack size (`data-ln-toast-max="5"`)**: Evicts the oldest toast when the count exceeds the threshold.
|
|
30
|
+
- **ARIA (`aria-live`/`aria-atomic`)**: authored once on the container — the live region announces every appended/hydrated toast. Individual toast items never carry `role` or `aria-live`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 3. The Window API Contract
|
|
35
|
+
|
|
36
|
+
Toasts are triggered exclusively by dispatching CustomEvents on the global `window` object. **The window event is the sole contract.**
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// Dispatch a success notification
|
|
40
|
+
window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
|
|
41
|
+
detail: {
|
|
42
|
+
type: 'success',
|
|
43
|
+
title: 'Saved',
|
|
44
|
+
message: 'Changes have been saved successfully.'
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Enqueue Event Options (`ln-toast:enqueue`)
|
|
50
|
+
Pass options inside the event's `detail` object:
|
|
51
|
+
|
|
52
|
+
| Field | Type | Description |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `type` | `success\|error\|warn\|info` | Toast category. Becomes the `<li>` class verbatim (`success`/`error`/`warn`/`info`); unrecognized or absent values render with no icon and neutral tone (documented degradation, not an error). |
|
|
55
|
+
| `title` | string | Optional card title. |
|
|
56
|
+
| `message` | string \| string[] | Card body content. Pass an array of strings to render a bulleted validation error list. |
|
|
57
|
+
| `timeout` | number | Dismissal timeout in ms. Use `0` to make it persistent. |
|
|
58
|
+
|
|
59
|
+
### Clear Event (`ln-toast:clear`)
|
|
60
|
+
Dismiss all active toasts in the viewport:
|
|
61
|
+
```js
|
|
62
|
+
window.dispatchEvent(new CustomEvent('ln-toast:clear'));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 4. Toast Types & Tone
|
|
68
|
+
|
|
69
|
+
`type` drives CSS tone and icon selection only — nothing else. Recognized
|
|
70
|
+
values: `success`, `error`, `warn`, `info`. Each becomes the class on the
|
|
71
|
+
toast `<li>` (the card itself — no nested wrapper): the SSR path authors
|
|
72
|
+
`class="success"`, the dynamic path fills it via `data-ln-attr="class:type"`.
|
|
73
|
+
The SCSS maps the type class → color family (`.warn` → the `warning` family)
|
|
74
|
+
and shows the matching icon from the authored 4-icon list. An unrecognized or
|
|
75
|
+
omitted `type` renders with no icon and neutral tone — no JS whitelist.
|
|
76
|
+
|
|
77
|
+
There are no default titles. If `title` is omitted, the title element stays
|
|
78
|
+
empty and collapses (`:empty`) — the same rule applies to `message`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 5. Integration Patterns
|
|
83
|
+
|
|
84
|
+
### A. SSR-Rendered Flash Messages (Hydration)
|
|
85
|
+
For server-side frameworks (like Laravel, Rails, or ASP.NET), the server authors the full toast markup — icon, title, body, and close button are real HTML written by the backend template. `ln-toast` only binds behavior on load: it wires the close button and starts the auto-dismiss timer. There is no template clone and no DOM replacement — the authored `<li>` is never touched, only queried:
|
|
86
|
+
```html
|
|
87
|
+
<ul data-ln-toast data-ln-toast-timeout="6000" data-ln-toast-max="5" aria-live="polite" aria-atomic="false">
|
|
88
|
+
<li data-ln-toast-item class="success">
|
|
89
|
+
<div class="icon">
|
|
90
|
+
<svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-circle-check"></use></svg>
|
|
91
|
+
</div>
|
|
92
|
+
<section class="content">
|
|
93
|
+
<header>
|
|
94
|
+
<strong class="title">Saved</strong>
|
|
95
|
+
<button type="button" data-ln-toast-close aria-label="Close"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg></button>
|
|
96
|
+
</header>
|
|
97
|
+
<main class="body"><p>Changes have been saved successfully.</p></main>
|
|
98
|
+
</section>
|
|
99
|
+
</li>
|
|
100
|
+
</ul>
|
|
101
|
+
```
|
|
102
|
+
An optional per-item `data-ln-toast-timeout` overrides the container's default timeout (`0` = persistent — e.g. a blocking error the user must dismiss manually).
|
|
103
|
+
|
|
104
|
+
### B. Bulleted Validation Error Maps
|
|
105
|
+
To display a list of form validation errors inside a single toast, pass an array of strings to `message`:
|
|
106
|
+
```js
|
|
107
|
+
window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
|
|
108
|
+
detail: {
|
|
109
|
+
type: 'error',
|
|
110
|
+
title: 'Validation Failed',
|
|
111
|
+
message: ['Email is required.', 'Password is too short.']
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### C. The Dynamic Template Is Authored Markup
|
|
117
|
+
The `ln-toast:enqueue` path renders from a `<template data-ln-template="ln-toast-item">`
|
|
118
|
+
that the page provides. `ln-toast` ships no hidden runtime default — the
|
|
119
|
+
library never generates or injects this markup. Nest the `<template>` inside
|
|
120
|
+
your `[data-ln-toast]` container for scoped lookup (`cloneTemplateScoped`
|
|
121
|
+
resolves container-local templates first — the recommended placement), or
|
|
122
|
+
place it anywhere else in the document body for a global fallback. Copy
|
|
123
|
+
[`js/ln-toast/template.html`](template.html) as your starting point — it is a
|
|
124
|
+
developer example to adapt, not a runtime artifact. Binding contract: the
|
|
125
|
+
root `<li>` carries `data-ln-toast-item` (eviction, clear, and hydration
|
|
126
|
+
address items by it; the JS stamps it defensively if omitted) plus
|
|
127
|
+
`data-ln-attr="class:type"` and NO static class (fill
|
|
128
|
+
sets the class via setAttribute, which would clobber an authored one);
|
|
129
|
+
`data-ln-field="title"` / `data-ln-field="message"` for text; a
|
|
130
|
+
`data-ln-toast-when="{type}"` icon list if you want per-type icons. A missing
|
|
131
|
+
template fails loudly — `console.warn('[ln-toast] Template "ln-toast-item"
|
|
132
|
+
not found')` at clone time, no silent fallback.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 6. Integration & Source Files
|
|
137
|
+
|
|
138
|
+
- **Unified Bundle**: Loaded automatically with the main bundle:
|
|
139
|
+
```html
|
|
140
|
+
<script src="dist/ln-ashlar.iife.js" defer></script>
|
|
141
|
+
```
|
|
142
|
+
- **Standalone IIFE**: For lightweight pages, load the standalone, self-registering IIFE version:
|
|
143
|
+
```html
|
|
144
|
+
<script src="js/ln-toast/ln-toast.js" defer></script>
|
|
145
|
+
```
|
|
146
|
+
- **Active Source (ESM)**: Development source is located at [js/ln-toast/src/ln-toast.js](file:///c:/laragon/www/ln-ashlar/js/ln-toast/src/ln-toast.js).
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🔧 Internals
|
|
151
|
+
|
|
152
|
+
Source: `js/ln-toast/ln-toast.js`. One `_Component` instance per `[data-ln-toast]` container; no reactive proxy, direct DOM manipulation.
|
|
153
|
+
|
|
154
|
+
### Build order matters
|
|
155
|
+
|
|
156
|
+
`_buildItem` calls `fill(fragment, data)` on the raw `DocumentFragment` *before* extracting `firstElementChild` — `fill()`'s `querySelectorAll` excludes its own root, so operating on the fragment (whose child is the `<li>`) is what lets `data-ln-attr="class:type"` land on the `<li>` itself. `classList.add('ln-enter')` must run *after* that `fill()` call, because `setAttribute('class', …)` clobbers the whole class attribute and would wipe a pre-added enter flag.
|
|
157
|
+
|
|
158
|
+
### Two build paths
|
|
159
|
+
|
|
160
|
+
`_buildItem` (dynamic, `ln-toast:enqueue`) clones the authored `<template data-ln-template="ln-toast-item">` via `cloneTemplateScoped` (container-local template wins over a document-global one) and runs `fill()`. `_hydrateLI` (SSR path, runs once per existing `[data-ln-toast-item]` at construction) never touches markup — it only queries inside the authored `<li>` to wire the close button and start the timeout. Excess authored items beyond `data-ln-toast-max` are removed (no exit animation) before hydration runs.
|
|
161
|
+
|
|
162
|
+
### Dismiss
|
|
163
|
+
|
|
164
|
+
`clearTimeout` → `classList.remove('ln-enter')` (defensive, in case dismissed before the enter animation's `requestAnimationFrame` fired) → `classList.add('ln-out')` → 200ms `setTimeout` → `removeChild`. `.ln-enter` only ever appears on JS-built items; SSR items render in their final state on first paint.
|
|
165
|
+
|
|
166
|
+
### Why no ARIA on individual items
|
|
167
|
+
|
|
168
|
+
Live-region semantics (`aria-live`, `aria-atomic`) live once on the container, authored by the consumer. Neither build path ever writes `role` or `aria-live` on an item — toast is a non-blocking surface by design, and a nested independent live region inside an already-live container risks double-announcement.
|
|
169
|
+
|
|
170
|
+
### Icon selection is pure CSS
|
|
171
|
+
|
|
172
|
+
The template authors all four icon variants as a `<ul>/<li data-ln-toast-when="...">` list, hidden by default; CSS shows the one matching the card's type class. JS holds no type→icon lookup table and never writes an icon `href`.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Related
|
|
177
|
+
- **[`ln-modal`](../ln-modal/README.md)** — Viewport-blocking focus-gated dialogs.
|
|
178
|
+
- **[`ln-confirm`](../ln-confirm/README.md)** — Lightweight inline action confirmations.
|
package/js/ln-toast/ln-toast.js
CHANGED
|
@@ -1,15 +1 @@
|
|
|
1
|
-
(function(){"use strict";const
|
|
2
|
-
<div class="ln-toast__card" data-ln-attr="role:role, aria-live:ariaLive">\r
|
|
3
|
-
<div class="ln-toast__side">\r
|
|
4
|
-
<svg class="ln-icon" aria-hidden="true"><use href=""></use></svg>\r
|
|
5
|
-
</div>\r
|
|
6
|
-
<div class="ln-toast__content">\r
|
|
7
|
-
<div class="ln-toast__head">\r
|
|
8
|
-
<strong class="ln-toast__title" data-ln-field="title"></strong>\r
|
|
9
|
-
</div>\r
|
|
10
|
-
<button type="button" class="ln-toast__close" aria-label="Close"><svg class="ln-icon" aria-hidden="true"><use href="#ln-x"></use></svg></button>\r
|
|
11
|
-
<div class="ln-toast__body" data-ln-show="hasBody"></div>\r
|
|
12
|
-
</div>\r
|
|
13
|
-
</div>\r
|
|
14
|
-
</li>\r
|
|
15
|
-
`;(function(){const o="data-ln-toast",r="lnToast",u="ln-toast-item",_={success:"circle-check",error:"circle-x",warn:"alert-triangle",info:"info-circle"},g={success:"success",error:"error",warn:"warning",info:"info"},h={success:"Success",error:"Error",warn:"Warning",info:"Information"};if(window.__lnToastLoaded)return;window.__lnToastLoaded=!0;function i(){if(document.querySelector('[data-ln-template="ln-toast-item"]')||!document.body)return;const t=document.createElement("template");t.setAttribute("data-ln-template","ln-toast-item"),t.innerHTML=I,document.body.appendChild(t)}function a(t){if(!t||t.nodeType!==1)return;const e=Array.from(t.querySelectorAll("["+o+"]"));t.hasAttribute&&t.hasAttribute(o)&&e.push(t);for(const n of e)n[r]||new c(n)}function c(t){this.dom=t,t[r]=this,this.timeoutDefault=parseInt(t.getAttribute("data-ln-toast-timeout")||"6000",10),this.max=parseInt(t.getAttribute("data-ln-toast-max")||"5",10);for(const e of Array.from(t.querySelectorAll("[data-ln-toast-item]")))B(e,t);return this}c.prototype.destroy=function(){if(this.dom[r]){for(const t of Array.from(this.dom.children))d(t);delete this.dom[r]}};function f(t,e){const n=((t.type||"info")+"").toLowerCase(),l=x(e,u,"ln-toast");if(!l)return console.warn('[ln-toast] Template "'+u+'" not found'),null;const s=l.firstElementChild;if(!s)return null;const p=!!(t.message||t.data&&t.data.errors);M(s,{title:t.title||h[n]||h.info,role:n==="error"?"alert":"status",ariaLive:n==="error"?"assertive":"polite",hasBody:p});const v=s.querySelector(".ln-toast__card");v&&v.classList.add(g[n]||"info");const T=s.querySelector(".ln-toast__side");if(T){const S=T.querySelector("use");S&&S.setAttribute("href","#ln-"+(_[n]||_.info))}const L=s.querySelector(".ln-toast__body");L&&p&&m(L,t);const E=s.querySelector(".ln-toast__close");return E&&E.addEventListener("click",function(){d(s)}),s}function m(t,e){if(e.message)if(Array.isArray(e.message)){const n=document.createElement("ul");for(const l of e.message){const s=document.createElement("li");s.textContent=l,n.appendChild(s)}t.appendChild(n)}else{const n=document.createElement("p");n.textContent=e.message,t.appendChild(n)}if(e.data&&e.data.errors){const n=document.createElement("ul");for(const l of Object.values(e.data.errors).flat()){const s=document.createElement("li");s.textContent=l,n.appendChild(s)}t.appendChild(n)}}function y(t,e){for(;t.dom.children.length>=t.max;)t.dom.removeChild(t.dom.firstElementChild);t.dom.appendChild(e),requestAnimationFrame(()=>e.classList.add("ln-toast__item--in"))}function d(t){!t||!t.parentNode||(clearTimeout(t._timer),t.classList.remove("ln-toast__item--in"),t.classList.add("ln-toast__item--out"),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},200))}function C(t){let e=t&&t.container;return typeof e=="string"&&(e=document.querySelector(e)),e instanceof HTMLElement||(e=document.querySelector("["+o+"]")||document.getElementById("ln-toast-container")),e||null}function B(t,e){const n=((t.getAttribute("data-type")||"info")+"").toLowerCase(),l=t.getAttribute("data-title"),s=(t.innerText||t.textContent||"").trim(),p=f({type:n,title:l,message:s||void 0},e);p&&(t.parentNode&&t.parentNode.replaceChild(p,t),requestAnimationFrame(()=>p.classList.add("ln-toast__item--in")))}function O(t){const e=t.detail||{},n=C(e);if(!n){console.warn("[ln-toast] No toast container found");return}const l=n[r]||new c(n),s=f(e,n);if(!s)return;const p=Number.isFinite(e.timeout)?e.timeout:l.timeoutDefault;y(l,s),p>0&&(s._timer=setTimeout(()=>d(s),p))}function F(t){const e=t&&t.detail||{};if(e.container){const n=C(e);if(n)for(const l of Array.from(n.children))d(l)}else{const n=document.querySelectorAll("["+o+"]");for(const l of Array.from(n))for(const s of Array.from(l.children))d(s)}}b(function(){i(),window.addEventListener("ln-toast:enqueue",O),window.addEventListener("ln-toast:clear",F),new MutationObserver(function(e){for(const n of e){if(n.type==="attributes"){a(n.target);continue}for(const l of n.addedNodes)a(l)}}).observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[o]}),a(document.body)},"ln-toast")})()})();
|
|
1
|
+
(function(){"use strict";if(typeof window<"u"){const t=console.warn;console.warn=function(...n){typeof n[0]=="string"&&(n[0].startsWith("[ln-")||n[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||t.apply(console,n)}}const b={};function L(t,n){b[t]||(b[t]=document.querySelector('[data-ln-template="'+t+'"]'));const i=b[t];return i?i.content.cloneNode(!0):(console.warn("["+(n||"ln-core")+'] Template "'+t+'" not found'),null)}function C(t,n){if(!t||!n)return t;const i=t.querySelectorAll("[data-ln-field]");for(let r=0;r<i.length;r++){const a=i[r],s=a.getAttribute("data-ln-field");n[s]!=null&&(a.textContent=n[s])}const f=t.querySelectorAll("[data-ln-attr]");for(let r=0;r<f.length;r++){const a=f[r],s=a.getAttribute("data-ln-attr").split(",");for(let u=0;u<s.length;u++){const m=s[u].trim().split(":");if(m.length!==2)continue;const y=m[0].trim(),w=m[1].trim();n[w]!=null&&a.setAttribute(y,n[w])}}const p=t.querySelectorAll("[data-ln-show]");for(let r=0;r<p.length;r++){const a=p[r],s=a.getAttribute("data-ln-show");s in n&&a.classList.toggle("hidden",!n[s])}const c=t.querySelectorAll("[data-ln-class]");for(let r=0;r<c.length;r++){const a=c[r],s=a.getAttribute("data-ln-class").split(",");for(let u=0;u<s.length;u++){const m=s[u].trim().split(":");if(m.length!==2)continue;const y=m[0].trim(),w=m[1].trim();w in n&&a.classList.toggle(y,!!n[w])}}return t}function k(t,n){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:n??null,bubbles:!0}));const i=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let f=0;f<i.length;f++)i[f].dispatchEvent(new CustomEvent("ln-fill",{detail:n??null,bubbles:!0}));return t}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(t){if(!(!t.target.matches||!t.target.matches("[data-ln-fillable]")))if(t.detail)C(t.target,t.detail);else{const n=t.target.querySelectorAll("[data-ln-field]");for(let i=0;i<n.length;i++)n[i].textContent=""}})));function _(t,n){if(!t||!n)return t;const i=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;i.nextNode();){const c=i.currentNode;c.textContent.indexOf("{{")!==-1&&(c.textContent=c.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(r,a){return n[a]!==void 0?n[a]:""}))}const f=function(c,r){return n[r]!==void 0?n[r]:""},p=Array.from(t.querySelectorAll("*"));t.nodeType===1&&p.push(t);for(let c=0;c<p.length;c++){const r=p[c],a=r.attributes;for(let s=0;s<a.length;s++){const u=a[s];u.value.indexOf("{{")!==-1&&r.setAttribute(u.name,u.value.replace(/\{\{\s*(\w+)\s*\}\}/g,f))}}return t}function N(t,n,i,f,p,c){const r={};for(let s=0;s<t.children.length;s++){const u=t.children[s],m=u.getAttribute("data-ln-key");m&&(r[m]=u)}const a=document.createDocumentFragment();for(let s=0;s<n.length;s++){const u=n[s],m=String(f(u));let y=r[m];if(y)p(y,u,s);else{const w=L(i,c);if(!w||(_(w,u),y=w.firstElementChild,!y))continue;y.setAttribute("data-ln-key",m),p(y,u,s)}a.appendChild(y)}t.textContent="",t.appendChild(a)}function v(t,n){if(!document.body){document.addEventListener("DOMContentLoaded",function(){v(t,n)}),console.warn("["+n+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function D(t,n,i){if(t){const f=t.querySelector('[data-ln-template="'+n+'"]');if(f)return f.content.cloneNode(!0)}return L(n,i)}const S={};function M(t,n){S[t]=n}function F(t){return S[t]||{ingress:n=>n,egress:n=>n}}const E={};function T(t,n){if(!t||typeof n!="object")return;const i=t.toLowerCase().split("-")[0];E[i]=n}function O(t){if(!t)return null;const n=t.toLowerCase().split("-")[0];return E[n]||null}T("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=M,window.lnCore.getDataMapper=F,window.lnCore.registerLocaleFallback=T,window.lnCore.getLocaleFallback=O,window.lnCore.fillTemplate=_,window.lnCore.fill=C,window.lnCore.lnFill=k,window.lnCore.renderList=N);function q(t){return(t||"").replace(/^#/,"")}function A(t){const n=t===void 0?location.hash:t,i={},f=q(n);if(!f)return i;const p=f.split("&");for(let c=0;c<p.length;c++){const r=p[c];if(!r)continue;const a=r.indexOf(":"),s=a>-1?r.slice(0,a):r,u=a>-1?r.slice(a+1):"";if(s)try{i[s]=decodeURIComponent(u)}catch{i[s]=u}}return i}function I(t){if(!t)return null;const n=A();return t in n?n[t]:null}function B(t,n){if(!t)return;const i=A();n==null?delete i[t]:i[t]=String(n);const p=Object.keys(i).map(function(c){const r=i[c];return r===""?c:c+":"+encodeURIComponent(r)}).join("&");q(location.hash)!==p&&(location.hash=p)}function j(t){return t.button===1||t.ctrlKey||t.metaKey||t.shiftKey?!1:(t.preventDefault(),!0)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=A,window.lnCore.hashGet=I,window.lnCore.hashSet=B,window.lnCore.hashLinkClick=j),(function(){const t="data-ln-toast",n="lnToast",i="ln-toast-item";if(window.__lnToastLoaded)return;window.__lnToastLoaded=!0;function f(e){if(!(!e||!(e instanceof HTMLElement))&&(e.hasAttribute("popover")||e.setAttribute("popover","manual"),typeof e.showPopover=="function")){if(e.matches(":popover-open"))try{e.hidePopover()}catch{}try{e.showPopover()}catch{}}}function p(e){if(!e||!(e instanceof HTMLElement))return;if(e.querySelectorAll("[data-ln-toast-item]").length===0&&typeof e.hidePopover=="function"&&e.matches(":popover-open"))try{e.hidePopover()}catch{}}function c(e){if(!e||e.nodeType!==1)return;const o=Array.from(e.querySelectorAll("["+t+"]"));e.hasAttribute&&e.hasAttribute(t)&&o.push(e);for(const l of o)l[n]||new r(l)}function r(e){this.dom=e,e[n]=this,this.timeoutDefault=parseInt(e.getAttribute("data-ln-toast-timeout")||"6000",10),this.max=parseInt(e.getAttribute("data-ln-toast-max")||"5",10);const o=Array.from(e.querySelectorAll("[data-ln-toast-item]"));for(;o.length>this.max;)e.removeChild(o.shift());for(const l of o)w(l,this);return o.length>0&&f(e),this}r.prototype.destroy=function(){if(this.dom[n]){for(const e of Array.from(this.dom.querySelectorAll("[data-ln-toast-item]")))m(e);p(this.dom),delete this.dom[n]}};function a(e,o){const l=((e.type||"")+"").trim().toLowerCase(),h=D(o,i,"ln-toast");if(!h)return console.warn('[ln-toast] Template "'+i+'" not found'),null;C(h,{type:l,title:e.title,message:typeof e.message=="string"?e.message:void 0});const d=h.firstElementChild;if(!d)return null;d.hasAttribute("data-ln-toast-item")||d.setAttribute("data-ln-toast-item",""),d.classList.add("ln-enter");const g=d.querySelector(".body");g&&s(g,e);const x=d.querySelector("[data-ln-toast-close]");return x&&x.addEventListener("click",function(){m(d)}),d}function s(e,o){if(Array.isArray(o.message)){const l=document.createElement("ul");for(const h of o.message){const d=document.createElement("li");d.textContent=h,l.appendChild(d)}e.appendChild(l)}if(o.data&&o.data.errors){const l=document.createElement("ul");for(const h of Object.values(o.data.errors).flat()){const d=document.createElement("li");d.textContent=h,l.appendChild(d)}e.appendChild(l)}}function u(e,o){const l=Array.from(e.dom.querySelectorAll("[data-ln-toast-item]"));for(;l.length>=e.max&&l.length>0;)e.dom.removeChild(l.shift());e.dom.appendChild(o),f(e.dom),requestAnimationFrame(()=>o.classList.remove("ln-enter"))}function m(e){if(!e||!e.parentNode)return;const o=e.parentNode;clearTimeout(e._timer),e.classList.remove("ln-enter"),e.classList.add("ln-out"),setTimeout(()=>{e.parentNode&&(e.parentNode.removeChild(e),p(o))},200)}function y(e){let o=e&&e.container;return typeof o=="string"&&(o=document.querySelector(o)),o instanceof HTMLElement||(o=document.querySelector("["+t+"]")||document.getElementById("ln-toast-container")),o||null}function w(e,o){if(e._lnToastHydrated)return;e._lnToastHydrated=!0;const l=e.querySelector("[data-ln-toast-close]");l&&l.addEventListener("click",function(){m(e)});const h=e.getAttribute("data-ln-toast-timeout"),d=h!==null?parseInt(h,10):NaN,g=Number.isFinite(d)?d:o.timeoutDefault;g>0&&(e._timer=setTimeout(function(){m(e)},g))}function P(e){const o=e.detail||{},l=y(o);if(!l){console.warn("[ln-toast] No toast container found");return}const h=l[n]||new r(l),d=a(o,l);if(!d)return;const g=Number.isFinite(o.timeout)?o.timeout:h.timeoutDefault;u(h,d),g>0&&(d._timer=setTimeout(()=>m(d),g))}function W(e){const o=e&&e.detail||{};if(o.container){const l=y(o);if(l)for(const h of Array.from(l.querySelectorAll("[data-ln-toast-item]")))m(h)}else{const l=document.querySelectorAll("["+t+"]");for(const h of Array.from(l))for(const d of Array.from(h.querySelectorAll("[data-ln-toast-item]")))m(d)}}v(function(){window.addEventListener("ln-toast:enqueue",P),window.addEventListener("ln-toast:clear",W),window.addEventListener("ln-modal:open",function(){const o=document.querySelectorAll("["+t+"]");for(const l of Array.from(o))l.querySelectorAll("[data-ln-toast-item]").length>0&&f(l)}),new MutationObserver(function(o){for(const l of o){if(l.type==="attributes"){c(l.target);continue}for(const h of l.addedNodes)c(h)}}).observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[t]}),c(document.body)},"ln-toast")})()})();
|