@livenetworks/ashlar 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +189 -177
  2. package/js/COMPONENTS.md +55 -49
  3. package/js/index.js +22 -5
  4. package/js/ln-accordion/README.md +186 -137
  5. package/js/ln-accordion/ln-accordion.js +1 -1
  6. package/js/ln-ajax/README.md +73 -4
  7. package/js/ln-ajax/ln-ajax.js +1 -1
  8. package/js/ln-ajax/src/ln-ajax.js +13 -13
  9. package/js/ln-api-connector/README.md +261 -150
  10. package/js/ln-api-connector/ln-api-connector.js +1 -1
  11. package/js/ln-api-connector/src/ln-api-connector.js +368 -265
  12. package/js/ln-api-queue/README.md +219 -0
  13. package/js/ln-api-queue/ln-api-queue.js +1 -0
  14. package/js/ln-api-queue/src/ln-api-queue.js +320 -0
  15. package/js/ln-api-queue/src/queue-storage.js +455 -0
  16. package/js/ln-autoresize/README.md +47 -0
  17. package/js/ln-autoresize/ln-autoresize.js +1 -1
  18. package/js/ln-autosave/README.md +33 -1
  19. package/js/ln-autosave/ln-autosave.js +1 -1
  20. package/js/ln-autosave/src/ln-autosave.js +4 -4
  21. package/js/ln-circular-progress/README.md +27 -13
  22. package/js/ln-circular-progress/ln-circular-progress.js +1 -1
  23. package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
  24. package/js/ln-confirm/README.md +165 -86
  25. package/js/ln-confirm/_ln-confirm.scss +11 -0
  26. package/js/ln-confirm/ln-confirm.js +1 -1
  27. package/js/ln-confirm/src/ln-confirm.js +82 -42
  28. package/js/ln-core/README.md +896 -0
  29. package/js/ln-core/hash.js +92 -0
  30. package/js/ln-core/helpers.js +337 -8
  31. package/js/ln-core/index.js +4 -2
  32. package/js/ln-core/persist.js +29 -3
  33. package/js/ln-core/positioning.js +1 -35
  34. package/js/ln-core/window-cache.js +213 -0
  35. package/js/ln-couchdb-connector/README.md +177 -156
  36. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  37. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
  38. package/js/ln-data-coordinator/README.md +536 -165
  39. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
  40. package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
  41. package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
  42. package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
  43. package/js/ln-data-store/README.md +241 -94
  44. package/js/ln-data-store/ln-data-store.js +1 -1
  45. package/js/ln-data-store/src/ln-data-store.js +694 -699
  46. package/js/ln-date/README.md +39 -1
  47. package/js/ln-date/ln-date-dev.scss +14 -0
  48. package/js/ln-date/ln-date.js +1 -1
  49. package/js/ln-date/src/ln-date.js +236 -178
  50. package/js/ln-debug/README.md +58 -0
  51. package/js/ln-debug/ln-debug.js +1 -0
  52. package/js/ln-debug/src/ln-debug.js +19 -0
  53. package/js/ln-dropdown/README.md +31 -8
  54. package/js/ln-dropdown/ln-dropdown.js +1 -1
  55. package/js/ln-dropdown/src/ln-dropdown.js +30 -13
  56. package/js/ln-editor/README.md +274 -0
  57. package/js/ln-editor/ln-editor.js +1 -0
  58. package/js/ln-editor/ln-editor.scss +43 -0
  59. package/js/ln-editor/src/ln-editor.js +619 -0
  60. package/js/ln-external-links/README.md +44 -5
  61. package/js/ln-external-links/ln-external-links.js +1 -1
  62. package/js/ln-fill/README.md +199 -0
  63. package/js/ln-fill/ln-fill.js +1 -0
  64. package/js/ln-fill/src/ln-fill.js +87 -0
  65. package/js/ln-filter/README.md +92 -2
  66. package/js/ln-filter/ln-filter-dev.scss +8 -0
  67. package/js/ln-filter/ln-filter.js +1 -1
  68. package/js/ln-filter/src/ln-filter.js +22 -7
  69. package/js/ln-form/README.md +212 -49
  70. package/js/ln-form/ln-form.js +1 -1
  71. package/js/ln-form/src/ln-form.js +99 -199
  72. package/js/ln-http/README.md +125 -89
  73. package/js/ln-http/ln-http.js +1 -1
  74. package/js/ln-http/src/ln-http.js +32 -13
  75. package/js/ln-icon/README.md +130 -0
  76. package/js/ln-icon/ln-icon.js +1 -0
  77. package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
  78. package/js/ln-link/README.md +47 -9
  79. package/js/ln-link/ln-link.js +1 -1
  80. package/js/ln-link/src/ln-link.js +22 -6
  81. package/js/ln-list/README.md +129 -0
  82. package/js/ln-list/ln-list.js +1 -0
  83. package/js/ln-list/ln-list.scss +28 -0
  84. package/js/ln-list/src/ln-list.js +1216 -0
  85. package/js/ln-modal/README.md +185 -154
  86. package/js/ln-modal/ln-modal-dev.scss +13 -0
  87. package/js/ln-modal/ln-modal.js +1 -1
  88. package/js/ln-modal/ln-modal.scss +28 -4
  89. package/js/ln-modal/src/ln-modal.js +137 -201
  90. package/js/ln-modal-coordinator/README.md +99 -0
  91. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
  92. package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
  93. package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
  94. package/js/ln-nav/README.md +46 -3
  95. package/js/ln-nav/ln-nav.js +1 -1
  96. package/js/ln-nav/src/ln-nav.js +92 -127
  97. package/js/ln-number/README.md +28 -1
  98. package/js/ln-number/ln-number-dev.scss +13 -0
  99. package/js/ln-number/ln-number.js +1 -1
  100. package/js/ln-number/src/ln-number.js +443 -350
  101. package/js/ln-options/README.md +66 -0
  102. package/js/ln-options/ln-options.js +1 -0
  103. package/js/ln-options/src/ln-options.js +74 -0
  104. package/js/ln-popover/README.md +40 -5
  105. package/js/ln-popover/ln-popover.js +1 -1
  106. package/js/ln-popover/src/ln-popover.js +38 -11
  107. package/js/ln-progress/README.md +413 -442
  108. package/js/ln-progress/ln-progress.js +1 -1
  109. package/js/ln-progress/src/ln-progress.js +98 -150
  110. package/js/ln-router/README.md +432 -0
  111. package/js/ln-router/ln-router.js +1 -0
  112. package/js/ln-router/src/ln-router.js +539 -0
  113. package/js/ln-search/README.md +158 -83
  114. package/js/ln-search/ln-search-dev.scss +8 -0
  115. package/js/ln-search/ln-search.js +1 -1
  116. package/js/ln-search/src/ln-search.js +12 -9
  117. package/js/ln-slug/README.md +110 -0
  118. package/js/ln-slug/ln-slug.js +1 -0
  119. package/js/ln-slug/src/ln-slug.js +74 -0
  120. package/js/ln-sort/README.md +170 -0
  121. package/js/ln-sort/ln-sort-dev.scss +8 -0
  122. package/js/ln-sort/ln-sort.js +1 -0
  123. package/js/ln-sort/src/ln-sort.js +178 -0
  124. package/js/ln-sortable/README.md +133 -95
  125. package/js/ln-sortable/ln-sortable.js +1 -1
  126. package/js/ln-stat/README.md +55 -0
  127. package/js/ln-stat/ln-stat.js +1 -0
  128. package/js/ln-stat/src/ln-stat.js +55 -0
  129. package/js/ln-table/README.md +285 -101
  130. package/js/ln-table/ln-table-dev.scss +17 -0
  131. package/js/ln-table/ln-table.js +1 -1
  132. package/js/ln-table/ln-table.scss +1 -1
  133. package/js/ln-table/src/ln-table.js +1493 -473
  134. package/js/ln-table-coordinator/README.md +73 -0
  135. package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
  136. package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
  137. package/js/ln-tabs/README.md +221 -137
  138. package/js/ln-tabs/ln-tabs-dev.scss +18 -0
  139. package/js/ln-tabs/ln-tabs.js +1 -1
  140. package/js/ln-tabs/src/ln-tabs.js +41 -24
  141. package/js/ln-time/README.md +14 -0
  142. package/js/ln-time/ln-time.js +1 -1
  143. package/js/ln-time/src/ln-time.js +223 -192
  144. package/js/ln-toast/README.md +178 -122
  145. package/js/ln-toast/ln-toast.js +1 -15
  146. package/js/ln-toast/src/ln-toast.js +232 -210
  147. package/js/ln-toast/template.html +16 -12
  148. package/js/ln-toggle/README.md +61 -2
  149. package/js/ln-toggle/ln-toggle-dev.scss +13 -0
  150. package/js/ln-toggle/ln-toggle.js +1 -1
  151. package/js/ln-toggle/src/ln-toggle.js +56 -43
  152. package/js/ln-tooltip/README.md +95 -58
  153. package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
  154. package/js/ln-tooltip/ln-tooltip.js +1 -1
  155. package/js/ln-tooltip/src/ln-tooltip.js +45 -6
  156. package/js/ln-translations/README.md +118 -96
  157. package/js/ln-translations/ln-translations.js +1 -1
  158. package/js/ln-translations/src/ln-translations.js +10 -5
  159. package/js/ln-upload/README.md +85 -10
  160. package/js/ln-upload/ln-upload.js +1 -1
  161. package/js/ln-upload/src/ln-upload.js +26 -10
  162. package/js/ln-validate/README.md +72 -0
  163. package/js/ln-validate/ln-validate-dev.scss +15 -0
  164. package/js/ln-validate/ln-validate.js +1 -1
  165. package/js/ln-validate/src/ln-validate.js +64 -0
  166. package/package.json +61 -56
  167. package/scss/base/_global.scss +2 -2
  168. package/scss/base/_typography.scss +1 -2
  169. package/scss/components/_date.scss +1 -0
  170. package/scss/components/_dropdown.scss +0 -5
  171. package/scss/components/_editor.scss +5 -0
  172. package/scss/components/_filter.scss +26 -25
  173. package/scss/components/_form.scss +7 -0
  174. package/scss/components/_ln-table.scss +13 -3
  175. package/scss/components/_sort.scss +10 -0
  176. package/scss/components/_table.scss +36 -23
  177. package/scss/components/_toast.scss +68 -41
  178. package/scss/components/_toggle.scss +16 -0
  179. package/scss/config/_density.scss +205 -117
  180. package/scss/config/_icons.scss +10 -9
  181. package/scss/config/_tokens.scss +69 -47
  182. package/scss/config/mixins/_accordion.scss +2 -7
  183. package/scss/config/mixins/_alert.scss +4 -4
  184. package/scss/config/mixins/_breadcrumbs.scss +2 -2
  185. package/scss/config/mixins/_btn.scss +1 -1
  186. package/scss/config/mixins/_card.scss +89 -14
  187. package/scss/config/mixins/_collapsible.scss +3 -0
  188. package/scss/config/mixins/_colors.scss +1 -1
  189. package/scss/config/mixins/_diagnostics.scss +11 -0
  190. package/scss/config/mixins/_display.scss +1 -0
  191. package/scss/config/mixins/_dropdown.scss +3 -5
  192. package/scss/config/mixins/_editor.scss +124 -0
  193. package/scss/config/mixins/_empty-state.scss +7 -0
  194. package/scss/config/mixins/_filter.scss +3 -5
  195. package/scss/config/mixins/_footer.scss +1 -2
  196. package/scss/config/mixins/_form.scss +207 -21
  197. package/scss/config/mixins/_index.scss +2 -0
  198. package/scss/config/mixins/_ln-table.scss +41 -2
  199. package/scss/config/mixins/_modal.scss +149 -66
  200. package/scss/config/mixins/_nav.scss +3 -4
  201. package/scss/config/mixins/_popover.scss +39 -0
  202. package/scss/config/mixins/_sidebar.scss +1 -1
  203. package/scss/config/mixins/_sort.scss +42 -0
  204. package/scss/config/mixins/_table.scss +85 -60
  205. package/scss/config/mixins/_toast.scss +179 -152
  206. package/scss/config/mixins/_tooltip.scss +17 -7
  207. package/scss/config/mixins/_typography.scss +4 -2
  208. package/scss/ln-ashlar-dev.scss +27 -9
  209. package/scss/ln-ashlar.scss +7 -1
  210. package/js/ln-data-table/README.md +0 -110
  211. package/js/ln-data-table/ln-data-table.js +0 -1
  212. package/js/ln-data-table/ln-data-table.scss +0 -10
  213. package/js/ln-data-table/src/ln-data-table.js +0 -1087
  214. package/js/ln-icons/README.md +0 -88
  215. package/js/ln-icons/ln-icons.js +0 -1
  216. package/js/ln-table/ln-table-sort.js +0 -1
  217. package/js/ln-table/src/ln-table-sort.js +0 -156
@@ -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
- ## 3. The Window API Contract
34
-
35
- Toasts are triggered exclusively by dispatching CustomEvents on the global `window` object. **The window event is the sole contract.**
36
-
37
- ```js
38
- // Dispatch a success notification
39
- window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
40
- detail: {
41
- type: 'success',
42
- title: 'Saved',
43
- message: 'Changes have been saved successfully.'
44
- }
45
- }));
46
- ```
47
-
48
- ### Enqueue Event Options (`ln-toast:enqueue`)
49
- Pass options inside the event's `detail` object:
50
-
51
- | Field | Type | Description |
52
- |---|---|---|
53
- | `type` | `success\|error\|warn\|info` | Toast category. Drives accents, aria-live roles, and default titles. |
54
- | `title` | string | Optional card title. |
55
- | `message` | string \| string[] | Card body content. Pass an array of strings to render a bulleted validation error list. |
56
- | `timeout` | number | Dismissal timeout in ms. Use `0` to make it persistent. |
57
-
58
- ### Clear Event (`ln-toast:clear`)
59
- Dismiss all active toasts in the viewport:
60
- ```js
61
- window.dispatchEvent(new CustomEvent('ln-toast:clear'));
62
- ```
63
-
64
- ---
65
-
66
- ## 4. Toast Types & Default Accessibility
67
-
68
- ARIA roles and accessibility live-regions are injected automatically by the component depending on the category:
69
-
70
- | Category | Default Title | `aria-live` | `role` |
71
- |---|---|---|---|
72
- | `success` | Success | `polite` | `status` |
73
- | `error` | Error | `assertive` | `alert` |
74
- | `warn` | Warning | `polite` | `status` |
75
- | `info` | Information | `polite` | `status` |
76
-
77
- ---
78
-
79
- ## 5. Integration Patterns
80
-
81
- ### A. SSR-Rendered Flash Messages (Hydration)
82
- For server-side frameworks (like Laravel, Rails, or ASP.NET), you can place initial toast cards inside the container. The component will hydrate and auto-dismiss them on load:
83
- ```html
84
- <ul data-ln-toast>
85
- <li data-ln-toast-item data-type="success" data-title="Saved">
86
- Changes have been saved successfully.
87
- </li>
88
- </ul>
89
- ```
90
-
91
- ### B. Bulleted Validation Error Maps
92
- To display a list of form validation errors inside a single toast, pass an array of strings to `message`:
93
- ```js
94
- window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
95
- detail: {
96
- type: 'error',
97
- title: 'Validation Failed',
98
- message: ['Email is required.', 'Password is too short.']
99
- }
100
- }));
101
- ```
102
-
103
- ---
104
-
105
- ## 6. Integration & Source Files
106
-
107
- - **Unified Bundle**: Loaded automatically with the main bundle:
108
- ```html
109
- <script src="dist/ln-ashlar.iife.js" defer></script>
110
- ```
111
- - **Standalone IIFE**: For lightweight pages, load the standalone, self-registering IIFE version:
112
- ```html
113
- <script src="js/ln-toast/ln-toast.js" defer></script>
114
- ```
115
- - **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).
116
-
117
- ---
118
-
119
- ## Related
120
- - **[`ln-modal`](../ln-modal/README.md)** — Viewport-blocking focus-gated dialogs.
121
- - **[`ln-confirm`](../ln-confirm/README.md)** — Lightweight inline action confirmations.
122
- - **Architecture deep-dive** — [`docs/js/toast.md`](../../docs/js/toast.md).
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.
@@ -1,15 +1 @@
1
- (function(){"use strict";const A={};function q(o,r){A[o]||(A[o]=document.querySelector('[data-ln-template="'+o+'"]'));const u=A[o];return u?u.content.cloneNode(!0):(console.warn("["+r+'] Template "'+o+'" not found'),null)}function M(o,r){if(!o||!r)return o;const u=o.querySelectorAll("[data-ln-field]");for(let i=0;i<u.length;i++){const a=u[i],c=a.getAttribute("data-ln-field");r[c]!=null&&(a.textContent=r[c])}const _=o.querySelectorAll("[data-ln-attr]");for(let i=0;i<_.length;i++){const a=_[i],c=a.getAttribute("data-ln-attr").split(",");for(let f=0;f<c.length;f++){const m=c[f].trim().split(":");if(m.length!==2)continue;const y=m[0].trim(),d=m[1].trim();r[d]!=null&&a.setAttribute(y,r[d])}}const g=o.querySelectorAll("[data-ln-show]");for(let i=0;i<g.length;i++){const a=g[i],c=a.getAttribute("data-ln-show");c in r&&a.classList.toggle("hidden",!r[c])}const h=o.querySelectorAll("[data-ln-class]");for(let i=0;i<h.length;i++){const a=h[i],c=a.getAttribute("data-ln-class").split(",");for(let f=0;f<c.length;f++){const m=c[f].trim().split(":");if(m.length!==2)continue;const y=m[0].trim(),d=m[1].trim();d in r&&a.classList.toggle(y,!!r[d])}}return o}function b(o,r){if(!document.body){document.addEventListener("DOMContentLoaded",function(){b(o,r)}),console.warn("["+r+'] Script loaded before <body> — add "defer" to your <script> tag');return}o()}function x(o,r,u){if(o){const _=o.querySelector('[data-ln-template="'+r+'"]');if(_)return _.content.cloneNode(!0)}return q(r,u)}const w={};function N(o,r){w[o]=r}function D(o){return w[o]||{ingress:r=>r,egress:r=>r}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=N,window.lnCore.getDataMapper=D);const I=`<li class="ln-toast__item">\r
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")})()})();