@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
@@ -22,9 +22,7 @@ The host element must be empty at init — the constructor `appendChild`s the SV
22
22
  |---|---|---|
23
23
  | `data-ln-circular-progress="N"` | host element | Current value. Creates the instance. Number; negative or above-max values are allowed — the rendered percentage clamps to 0–100, but the attribute is left as-written. Empty / non-numeric strings render as 0%. |
24
24
  | `data-ln-circular-progress-max="N"` | host element | Maximum value. Default `100`. Used as denominator in `(value / max) × 100`. `max="0"` forces 0% (avoids divide-by-zero). |
25
- | `data-ln-circular-progress-label="text"` | host element | Custom centre label, replaces the auto-computed percentage. Read inside `_render`, but **NOT in the MutationObserver filter** — mutating this attribute alone does not trigger a re-render. Update the label alongside a value or max write. |
26
-
27
- `data-ln-circular-progress-initialized=""` is set by the constructor and removed by `destroy()`. It is an internal marker — do not read it from project code; use `el.lnCircularProgress` instead.
25
+ | `data-ln-circular-progress-label="text"` | host element | Custom centre label, replaces the auto-computed percentage. Fully observed and reactive — mutating this attribute alone triggers an immediate re-render and updates the centre text and `aria-valuetext`. |
28
26
 
29
27
  **Size variants** — apply a class to the host: `.sm` (2.5 rem), default (4 rem), `.lg` (6 rem), `.xl` (8 rem). Each rebinds the label font-size.
30
28
 
@@ -53,21 +51,15 @@ The event fires on the initial render at construction, as well as on every subse
53
51
 
54
52
  ## Accessibility
55
53
 
56
- The component sets `aria-hidden="true"` on the constructed `<svg>` — screen readers skip the SVG element. The component does **not** write `role`, `aria-valuenow`, `aria-valuemin`, or `aria-valuemax` on the host. Consumer owns the accessibility tree:
54
+ The component sets `aria-hidden="true"` on the constructed `<svg>` — screen readers skip the SVG element. The component automatically maps and synchronizes ARIA progressbar attributes on the host element (`role="progressbar"`, `aria-valuemin="0"`, `aria-valuemax`, `aria-valuenow` clamped, and `aria-valuetext`). For proper context, screen reader users should be provided with a descriptive label on the host:
57
55
 
58
56
  ```html
59
57
  <div data-ln-circular-progress="75"
60
- role="progressbar"
61
- aria-valuenow="75"
62
- aria-valuemin="0"
63
- aria-valuemax="100"
64
58
  aria-label="Upload progress"
65
59
  class="success">
66
60
  </div>
67
61
  ```
68
62
 
69
- Keep `aria-valuenow` in sync when writing `data-ln-circular-progress` — the component does not do this automatically.
70
-
71
63
  ## API
72
64
 
73
65
  `el.lnCircularProgress` on a constructed host element:
@@ -79,7 +71,7 @@ Keep `aria-valuenow` in sync when writing `data-ln-circular-progress` — the co
79
71
  | `trackCircle` | `SVGCircleElement` | Background circle (stroke = `--color-border`) |
80
72
  | `progressCircle` | `SVGCircleElement` | Fill circle — `stroke-dashoffset` carries the progress |
81
73
  | `labelEl` | `HTMLElement` | The `<strong>` element holding the percentage or custom label text |
82
- | `destroy()` | method | Disconnects the attribute observer, removes the SVG and label from DOM, removes `data-ln-circular-progress-initialized`, deletes `el.lnCircularProgress`. Leaves the value attribute, colour class, and size class in place — setting the value attribute again re-instantiates. |
74
+ | `destroy()` | method | Disconnects the attribute observer, removes the SVG and label from DOM, deletes `el.lnCircularProgress`. Leaves the value attribute, colour class, and size class in place — setting the value attribute again re-instantiates. |
83
75
 
84
76
  `window.lnCircularProgress(root)` re-runs the init scan over `root`. The shared `registerComponent` observer already covers AJAX inserts; call this manually only for Shadow DOM roots or foreign documents the observer cannot reach.
85
77
 
@@ -127,7 +119,6 @@ transition animates the arc from the previous offset to the new one.
127
119
  ## What it does NOT do
128
120
 
129
121
  - No indeterminate / spinner mode. An empty value renders 0% (empty track). For an indeterminate spinner use `@mixin loader`.
130
- - No automatic ARIA. Consumer adds `role="progressbar"` and keeps `aria-valuenow` in sync.
131
122
  - No debounce on rapid attribute writes — each write triggers a synchronous render and a `:change` event.
132
123
  - No `setValue()` / `setMax()` / imperative API. `el.setAttribute('data-ln-circular-progress', n)` is the canonical update path.
133
124
 
@@ -151,11 +142,34 @@ If you only need circular progress indicators without the rest of the Ashlar lib
151
142
  * **Active Development Source**: [js/ln-circular-progress/src/ln-circular-progress.js](file:///c:/laragon/www/ln-ashlar/js/ln-circular-progress/src/ln-circular-progress.js) — The source of truth containing the ES module implementation.
152
143
  * **Compiled Standalone Release**: [js/ln-circular-progress/ln-circular-progress.js](file:///c:/laragon/www/ln-ashlar/js/ln-circular-progress/ln-circular-progress.js) — The compiled IIFE distribution file.
153
144
 
145
+ ## 🔧 Internals
146
+
147
+ Source: `js/ln-circular-progress/ln-circular-progress.js`. Passive renderer — owns no data; `_render` re-reads all three attributes fresh on every call, no cached value.
148
+
149
+ ### SVG geometry
150
+
151
+ Fixed module-level constants: `VIEW_SIZE = 36`, `RADIUS = 16`, `CIRCUMFERENCE = 2π·16 ≈ 100.531`. Both circles share `cx=18 cy=18 r=16`, `stroke-width="3"` (1.5 in/out of the radius). The fill circle carries `stroke-dasharray` equal to the full circumference and a `stroke-dashoffset` that shrinks linearly as progress grows (`offset = CIRCUMFERENCE - (pct/100) × CIRCUMFERENCE`), plus `transform="rotate(-90 18 18)"` to start the arc at 12 o'clock instead of 3.
152
+
153
+ ### Render choices
154
+
155
+ - `parseFloat('') || 0` / `|| 100` — invalid or empty attributes silently fall back to defaults; no warning (consistent with the passive-renderer pattern).
156
+ - Clamping happens at the percentage level, not the attribute level: `detail.value` stays the raw unclamped number, `detail.percentage` is the 0–100 clamp used for the arc — this is how a consumer distinguishes overshoot from the visible fill.
157
+ - ARIA (`role`, `aria-valuemin/max/now`, `aria-valuetext`) is rewritten on every render on the host element itself, not the SVG (which is `aria-hidden`).
158
+
159
+ ### What the per-instance observer watches
160
+
161
+ `_listenValues` filters to the three state attributes only — deliberately excluding `class`. Colour/size variants are pure CSS; nothing about the SVG geometry depends on the class, so no JS reaction is needed.
162
+
163
+ ### Destroy
164
+
165
+ Disconnects the attribute observer, removes the SVG and label nodes, removes the ARIA attributes it added, deletes `el.lnCircularProgress`. Leaves the value attribute and any classes in place — a later write to the value attribute re-triggers `registerComponent`'s shared observer and rebuilds the instance from scratch (`_buildSvg` is not idempotent; it always appends new nodes).
166
+
167
+ ---
168
+
154
169
  ## See also
155
170
 
156
171
  - `@mixin circular-progress` (`scss/config/mixins/_circular-progress.scss`) — SVG layout, fill/track stroke tokens, motion-safe transition, size variant mixins.
157
172
  - `scss/components/_circular-progress.scss` — default selector application and the `.success` / `.warning` / `.error` colour variant rules.
158
173
  - [`ln-progress`](../ln-progress/README.md) — linear sibling. Same contract shape (`data-ln-progress` + `-max`); supports parent-track shared max.
159
- - [`docs/js/circular-progress.md`](../../docs/js/circular-progress.md) — architecture reference: SVG geometry constants, render flow, MutationObserver filter, performance notes.
160
174
  - `@mixin loader` (`scss/config/mixins/_loader.scss`) — indeterminate spinner for operations with no known fraction.
161
175
  - [`docs/architecture/data-flow.md`](../../docs/architecture/data-flow.md) — passive renderer pattern used by this component.
@@ -1 +1 @@
1
- (function(){"use strict";function _(t,e,c){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:c||{}}))}function y(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){y(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function m(t,e,c,a){if(t.nodeType!==1)return;const d=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",h=Array.from(t.querySelectorAll(d));t.matches&&t.matches(d)&&h.push(t);for(const n of h)n[c]||(n[c]=new a(n))}function A(t,e,c,a,f={}){const d=f.extraAttributes||[],h=f.onAttributeChange||null,n=f.onInit||null;function g(p){const u=p||document.body;m(u,t,e,c),n&&n(u)}return y(function(){const p=new MutationObserver(function(o){for(let i=0;i<o.length;i++){const r=o[i];if(r.type==="childList"){for(let s=0;s<r.addedNodes.length;s++){const l=r.addedNodes[s];l.nodeType===1&&(m(l,t,e,c),n&&n(l))}for(let s=0;s<r.removedNodes.length;s++){const l=r.removedNodes[s];if(l.nodeType===1){const x=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",b=Array.from(l.querySelectorAll(x));l.matches&&l.matches(x)&&b.push(l);for(let v=0;v<b.length;v++){const E=b[v];if(!document.contains(E)){const C=E[e];C&&typeof C.destroy=="function"&&C.destroy()}}}}}else r.type==="attributes"&&(h&&r.target[e]?h(r.target,r.attributeName):(m(r.target,t,e,c),n&&n(r.target)))}});let u=[];if(t.indexOf("[")!==-1){const o=/\[([\w-]+)/g;let i;for(;(i=o.exec(t))!==null;)u.push(i[1])}else u.push(t);p.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:u.concat(d)})},a),window[e]=g,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){g(document.body)}):g(document.body),g}const w={};function O(t,e){w[t]=e}function M(t){return w[t]||{ingress:e=>e,egress:e=>e}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=O,window.lnCore.getDataMapper=M),(function(){const t="data-ln-circular-progress",e="lnCircularProgress";if(window[e]!==void 0)return;const c="http://www.w3.org/2000/svg",a=36,f=16,d=2*Math.PI*f;function h(o){return this.dom=o,this.svg=null,this.trackCircle=null,this.progressCircle=null,this.labelEl=null,this._attrObserver=null,g.call(this),u.call(this),p.call(this),o.setAttribute("data-ln-circular-progress-initialized",""),this}h.prototype.destroy=function(){this.dom[e]&&(this._attrObserver&&this._attrObserver.disconnect(),this.svg&&this.svg.remove(),this.labelEl&&this.labelEl.remove(),this.dom.removeAttribute("data-ln-circular-progress-initialized"),delete this.dom[e])};function n(o,i){const r=document.createElementNS(c,o);for(const s in i)r.setAttribute(s,i[s]);return r}function g(){this.svg=n("svg",{viewBox:"0 0 "+a+" "+a,"aria-hidden":"true"}),this.trackCircle=n("circle",{cx:a/2,cy:a/2,r:f,fill:"none","stroke-width":"3"}),this.trackCircle.classList.add("ln-circular-progress__track"),this.progressCircle=n("circle",{cx:a/2,cy:a/2,r:f,fill:"none","stroke-width":"3","stroke-linecap":"round","stroke-dasharray":d,"stroke-dashoffset":d,transform:"rotate(-90 "+a/2+" "+a/2+")"}),this.progressCircle.classList.add("ln-circular-progress__fill"),this.svg.appendChild(this.trackCircle),this.svg.appendChild(this.progressCircle),this.labelEl=document.createElement("strong"),this.labelEl.classList.add("ln-circular-progress__label"),this.dom.appendChild(this.svg),this.dom.appendChild(this.labelEl)}function p(){const o=this,i=new MutationObserver(function(r){for(const s of r)(s.attributeName==="data-ln-circular-progress"||s.attributeName==="data-ln-circular-progress-max")&&u.call(o)});i.observe(this.dom,{attributes:!0,attributeFilter:["data-ln-circular-progress","data-ln-circular-progress-max"]}),this._attrObserver=i}function u(){const o=parseFloat(this.dom.getAttribute("data-ln-circular-progress"))||0,i=parseFloat(this.dom.getAttribute("data-ln-circular-progress-max"))||100;let r=i>0?o/i*100:0;r<0&&(r=0),r>100&&(r=100);const s=d-r/100*d;this.progressCircle.setAttribute("stroke-dashoffset",s);const l=this.dom.getAttribute("data-ln-circular-progress-label");this.labelEl.textContent=l!==null?l:Math.round(r)+"%",_(this.dom,"ln-circular-progress:change",{target:this.dom,value:o,max:i,percentage:r})}A(t,e,h,"ln-circular-progress")})()})();
1
+ (function(){"use strict";if(typeof window<"u"){const t=console.warn;console.warn=function(...e){typeof e[0]=="string"&&(e[0].startsWith("[ln-")||e[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||t.apply(console,e)}}const b={};function N(t,e){b[t]||(b[t]=document.querySelector('[data-ln-template="'+t+'"]'));const r=b[t];return r?r.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function D(t,e,r){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:r||{}}))}function E(t,e){if(!t||!e)return t;const r=t.querySelectorAll("[data-ln-field]");for(let n=0;n<r.length;n++){const i=r[n],l=i.getAttribute("data-ln-field");e[l]!=null&&(i.textContent=e[l])}const s=t.querySelectorAll("[data-ln-attr]");for(let n=0;n<s.length;n++){const i=s[n],l=i.getAttribute("data-ln-attr").split(",");for(let a=0;a<l.length;a++){const f=l[a].trim().split(":");if(f.length!==2)continue;const u=f[0].trim(),c=f[1].trim();e[c]!=null&&i.setAttribute(u,e[c])}}const d=t.querySelectorAll("[data-ln-show]");for(let n=0;n<d.length;n++){const i=d[n],l=i.getAttribute("data-ln-show");l in e&&i.classList.toggle("hidden",!e[l])}const o=t.querySelectorAll("[data-ln-class]");for(let n=0;n<o.length;n++){const i=o[n],l=i.getAttribute("data-ln-class").split(",");for(let a=0;a<l.length;a++){const f=l[a].trim().split(":");if(f.length!==2)continue;const u=f[0].trim(),c=f[1].trim();c in e&&i.classList.toggle(u,!!e[c])}}return t}function q(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));const r=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let s=0;s<r.length;s++)r[s].dispatchEvent(new CustomEvent("ln-fill",{detail:e??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)E(t.target,t.detail);else{const e=t.target.querySelectorAll("[data-ln-field]");for(let r=0;r<e.length;r++)e[r].textContent=""}})));function k(t,e){if(!t||!e)return t;const r=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;r.nextNode();){const o=r.currentNode;o.textContent.indexOf("{{")!==-1&&(o.textContent=o.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(n,i){return e[i]!==void 0?e[i]:""}))}const s=function(o,n){return e[n]!==void 0?e[n]:""},d=Array.from(t.querySelectorAll("*"));t.nodeType===1&&d.push(t);for(let o=0;o<d.length;o++){const n=d[o],i=n.attributes;for(let l=0;l<i.length;l++){const a=i[l];a.value.indexOf("{{")!==-1&&n.setAttribute(a.name,a.value.replace(/\{\{\s*(\w+)\s*\}\}/g,s))}}return t}function F(t,e,r,s,d,o){const n={};for(let l=0;l<t.children.length;l++){const a=t.children[l],f=a.getAttribute("data-ln-key");f&&(n[f]=a)}const i=document.createDocumentFragment();for(let l=0;l<e.length;l++){const a=e[l],f=String(s(a));let u=n[f];if(u)d(u,a,l);else{const c=N(r,o);if(!c||(k(c,a),u=c.firstElementChild,!u))continue;u.setAttribute("data-ln-key",f),d(u,a,l)}i.appendChild(u)}t.textContent="",t.appendChild(i)}function S(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){S(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function w(t,e,r,s){if(t.nodeType!==1)return;const o=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",n=Array.from(t.querySelectorAll(o));t.matches&&t.matches(o)&&n.push(t);for(const i of n)i[r]||(i[r]=new s(i))}function I(t,e,r,s,d={}){const o=d.extraAttributes||[],n=d.onAttributeChange||null,i=d.onInit||null;function l(a){const f=a||document.body;w(f,t,e,r),i&&i(f)}return S(function(){const a=new MutationObserver(function(u){for(let c=0;c<u.length;c++){const h=u[c];if(h.type==="childList"){for(let p=0;p<h.addedNodes.length;p++){const m=h.addedNodes[p];m.nodeType===1&&(w(m,t,e,r),i&&i(m))}for(let p=0;p<h.removedNodes.length;p++){const m=h.removedNodes[p];if(m.nodeType===1){const g=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",v=Array.from(m.querySelectorAll(g));m.matches&&m.matches(g)&&v.push(m);for(let A=0;A<v.length;A++){const T=v[A];if(!document.contains(T)){const x=T[e];x&&typeof x.destroy=="function"&&x.destroy()}}}}}else h.type==="attributes"&&(n&&h.target[e]?n(h.target,h.attributeName):(w(h.target,t,e,r),i&&i(h.target)))}});let f=[];if(t.indexOf("[")!==-1){const u=/\[([\w-]+)/g;let c;for(;(c=u.exec(t))!==null;)f.push(c[1])}else f.push(t);a.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:f.concat(o)})},s),window[e]=l,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){l(document.body)}):l(document.body),l}const _={};function j(t,e){_[t]=e}function W(t){return _[t]||{ingress:e=>e,egress:e=>e}}const L={};function O(t,e){if(!t||typeof e!="object")return;const r=t.toLowerCase().split("-")[0];L[r]=e}function R(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return L[e]||null}O("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=j,window.lnCore.getDataMapper=W,window.lnCore.registerLocaleFallback=O,window.lnCore.getLocaleFallback=R,window.lnCore.fillTemplate=k,window.lnCore.fill=E,window.lnCore.lnFill=q,window.lnCore.renderList=F);function M(t){return(t||"").replace(/^#/,"")}function C(t){const e=t===void 0?location.hash:t,r={},s=M(e);if(!s)return r;const d=s.split("&");for(let o=0;o<d.length;o++){const n=d[o];if(!n)continue;const i=n.indexOf(":"),l=i>-1?n.slice(0,i):n,a=i>-1?n.slice(i+1):"";if(l)try{r[l]=decodeURIComponent(a)}catch{r[l]=a}}return r}function B(t){if(!t)return null;const e=C();return t in e?e[t]:null}function U(t,e){if(!t)return;const r=C();e==null?delete r[t]:r[t]=String(e);const d=Object.keys(r).map(function(o){const n=r[o];return n===""?o:o+":"+encodeURIComponent(n)}).join("&");M(location.hash)!==d&&(location.hash=d)}function K(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=C,window.lnCore.hashGet=B,window.lnCore.hashSet=U,window.lnCore.hashLinkClick=K),(function(){const t="data-ln-circular-progress",e="lnCircularProgress";if(window[e]!==void 0)return;const r="http://www.w3.org/2000/svg",s=36,d=16,o=2*Math.PI*d;function n(u){return this.dom=u,this.svg=null,this.trackCircle=null,this.progressCircle=null,this.labelEl=null,this._attrObserver=null,l.call(this),f.call(this),a.call(this),this}n.prototype.destroy=function(){this.dom[e]&&(this._attrObserver&&this._attrObserver.disconnect(),this.svg&&this.svg.remove(),this.labelEl&&this.labelEl.remove(),this.dom.removeAttribute("role"),this.dom.removeAttribute("aria-valuemin"),this.dom.removeAttribute("aria-valuemax"),this.dom.removeAttribute("aria-valuenow"),this.dom.removeAttribute("aria-valuetext"),delete this.dom[e])};function i(u,c){const h=document.createElementNS(r,u);for(const p in c)h.setAttribute(p,c[p]);return h}function l(){this.svg=i("svg",{viewBox:"0 0 "+s+" "+s,"aria-hidden":"true"}),this.trackCircle=i("circle",{cx:s/2,cy:s/2,r:d,fill:"none","stroke-width":"3"}),this.trackCircle.classList.add("ln-circular-progress__track"),this.progressCircle=i("circle",{cx:s/2,cy:s/2,r:d,fill:"none","stroke-width":"3","stroke-linecap":"round","stroke-dasharray":o,"stroke-dashoffset":o,transform:"rotate(-90 "+s/2+" "+s/2+")"}),this.progressCircle.classList.add("ln-circular-progress__fill"),this.svg.appendChild(this.trackCircle),this.svg.appendChild(this.progressCircle),this.labelEl=document.createElement("strong"),this.labelEl.classList.add("ln-circular-progress__label"),this.dom.appendChild(this.svg),this.dom.appendChild(this.labelEl)}function a(){const u=this,c=new MutationObserver(function(h){for(const p of h)(p.attributeName==="data-ln-circular-progress"||p.attributeName==="data-ln-circular-progress-max"||p.attributeName==="data-ln-circular-progress-label")&&f.call(u)});c.observe(this.dom,{attributes:!0,attributeFilter:["data-ln-circular-progress","data-ln-circular-progress-max","data-ln-circular-progress-label"]}),this._attrObserver=c}function f(){const u=parseFloat(this.dom.getAttribute("data-ln-circular-progress"))||0,c=parseFloat(this.dom.getAttribute("data-ln-circular-progress-max"))||100;let h=c>0?u/c*100:0;h<0&&(h=0),h>100&&(h=100);const p=o-h/100*o;this.progressCircle.setAttribute("stroke-dashoffset",p);const m=this.dom.getAttribute("data-ln-circular-progress-label"),y=m!==null?m:Math.round(h)+"%";this.labelEl.textContent=y,this.dom.setAttribute("role","progressbar"),this.dom.setAttribute("aria-valuemin","0"),this.dom.setAttribute("aria-valuemax",String(c));const g=Math.max(0,Math.min(u,c));this.dom.setAttribute("aria-valuenow",String(g)),this.dom.setAttribute("aria-valuetext",y),D(this.dom,"ln-circular-progress:change",{target:this.dom,value:u,max:c,percentage:h})}I(t,e,n,"ln-circular-progress")})()})();
@@ -21,7 +21,6 @@ import { dispatch, registerComponent } from '../../ln-core';
21
21
  _buildSvg.call(this);
22
22
  _render.call(this);
23
23
  _listenValues.call(this);
24
- dom.setAttribute('data-ln-circular-progress-initialized', '');
25
24
  return this;
26
25
  }
27
26
 
@@ -36,7 +35,11 @@ import { dispatch, registerComponent } from '../../ln-core';
36
35
  if (this.labelEl) {
37
36
  this.labelEl.remove();
38
37
  }
39
- this.dom.removeAttribute('data-ln-circular-progress-initialized');
38
+ this.dom.removeAttribute('role');
39
+ this.dom.removeAttribute('aria-valuemin');
40
+ this.dom.removeAttribute('aria-valuemax');
41
+ this.dom.removeAttribute('aria-valuenow');
42
+ this.dom.removeAttribute('aria-valuetext');
40
43
  delete this.dom[DOM_ATTRIBUTE];
41
44
  };
42
45
 
@@ -91,7 +94,8 @@ import { dispatch, registerComponent } from '../../ln-core';
91
94
  const observer = new MutationObserver(function (mutations) {
92
95
  for (const mutation of mutations) {
93
96
  if (mutation.attributeName === 'data-ln-circular-progress' ||
94
- mutation.attributeName === 'data-ln-circular-progress-max') {
97
+ mutation.attributeName === 'data-ln-circular-progress-max' ||
98
+ mutation.attributeName === 'data-ln-circular-progress-label') {
95
99
  _render.call(self);
96
100
  }
97
101
  }
@@ -99,7 +103,7 @@ import { dispatch, registerComponent } from '../../ln-core';
99
103
 
100
104
  observer.observe(this.dom, {
101
105
  attributes: true,
102
- attributeFilter: ['data-ln-circular-progress', 'data-ln-circular-progress-max']
106
+ attributeFilter: ['data-ln-circular-progress', 'data-ln-circular-progress-max', 'data-ln-circular-progress-label']
103
107
  });
104
108
 
105
109
  this._attrObserver = observer;
@@ -117,7 +121,16 @@ import { dispatch, registerComponent } from '../../ln-core';
117
121
  this.progressCircle.setAttribute('stroke-dashoffset', offset);
118
122
 
119
123
  const label = this.dom.getAttribute('data-ln-circular-progress-label');
120
- this.labelEl.textContent = label !== null ? label : Math.round(percentage) + '%';
124
+ const labelText = label !== null ? label : Math.round(percentage) + '%';
125
+ this.labelEl.textContent = labelText;
126
+
127
+ // Sync ARIA properties
128
+ this.dom.setAttribute('role', 'progressbar');
129
+ this.dom.setAttribute('aria-valuemin', '0');
130
+ this.dom.setAttribute('aria-valuemax', String(max));
131
+ const clampedValue = Math.max(0, Math.min(value, max));
132
+ this.dom.setAttribute('aria-valuenow', String(clampedValue));
133
+ this.dom.setAttribute('aria-valuetext', labelText);
121
134
 
122
135
  dispatch(this.dom, 'ln-circular-progress:change', {
123
136
  target: this.dom,
@@ -1,86 +1,165 @@
1
- # ln-confirm
2
-
3
- A zero-dependency, ultra-lightweight **Interaction Gate Primitive** (~109 lines of JavaScript) that creates a self-contained, two-click confirmation checkpoint on standard buttons. It intercepts accidental clicks, morphs in-place to reveal confirmation prompts, and passes the second click directly to native platform events.
4
-
5
- ---
6
-
7
- ## 🧭 Philosophy & Architecture
8
-
9
- 1. **In-Place Morphing:** Instead of launching heavy, separate dialogs or using frozen, unstyleable `window.confirm()` scripts, confirmation lives **directly on the target button**. It preserves structural styling while using CSS mixins to display temporary labels or icon tooltips.
10
- 2. **Platform Event Release:** `ln-confirm` does not implement a custom `accept` handler. The acceptance is the standard platform `click` event. On the second click, the component steps out of the way, allowing form submissions (`type="submit"`), links (`href`), or custom AJAX click listeners to execute natively.
11
- 3. **Graceful Auto-Revert:** The gate is timed. When armed, a countdown timer is scheduled. If a second click does not arrive within the window, the button cleanly reverts to its idle text or icon state.
12
-
13
- ---
14
-
15
- ## 📦 Minimal Blueprint
16
-
17
- ### Text-Button Mode
18
-
19
- ```html
20
- <button data-ln-confirm="Are you sure you want to delete?">Delete account</button>
21
- ```
22
-
23
- On click, the text replaces with the confirmation prompt in-place, and the button accent shifts to error red.
24
-
25
- ### Icon-Only Mode
26
-
27
- ```html
28
- <button aria-label="Delete item" data-ln-confirm="Confirm delete?">
29
- <svg class="ln-icon"><use href="#ln-trash"></use></svg>
30
- </button>
31
- ```
32
-
33
- On click, the trash icon swaps to `#ln-check`, a tooltip bubble is displayed above, and the prompt is announced for screen-readers.
34
-
35
- ---
36
-
37
- ## 🛠️ Declarative API Contract
38
-
39
- ### HTML Attributes
40
-
41
- | Attribute | Elements | Description |
42
- | :--- | :--- | :--- |
43
- | `data-ln-confirm="Prompt"` | `<button>`, `<a>` | Action gate marker. Empty value defaults to `"Confirm?"`. |
44
- | `data-ln-confirm-timeout="3"` | `<button>`, `<a>` | Auto-revert delay in seconds (default `3`). |
45
- | `data-confirming="true"` | `<button>` (auto) | Managed state. Set during active confirmation; acts as public CSS hook. |
46
- | `data-tooltip-text="Prompt"` | `<button>` (auto) | Managed state. Displays tooltip bubble in icon-only mode. |
47
-
48
- ### JS API
49
-
50
- Access the confirmation instance directly via the `lnConfirm` property on the button element:
51
-
52
- ```javascript
53
- const button = document.getElementById('delete-btn');
54
-
55
- // 1. Check if armed (Boolean getter)
56
- if (button.lnConfirm.confirming) { ... }
57
-
58
- // 2. Disarm immediately, restore visual states, and unbind listeners
59
- button.lnConfirm.destroy();
60
- ```
61
-
62
- ---
63
-
64
- ## ⚡ DOM Events
65
-
66
- ### Emitted
67
-
68
- `ln-confirm` dispatches exactly one event during its lifecycle:
69
-
70
- | Event | Bubbles | Payload | Description |
71
- | :--- | :--- | :--- | :--- |
72
- | `ln-confirm:waiting` | Yes | `{ target }` | Fires on first click, right after the button arms. Useful for telemetry. |
73
-
74
- *Note: There is no `accept` event. Code execution on accept belongs on standard browser `click` or form `submit` handlers.*
75
-
76
- ---
77
-
78
- ## ⚠️ Common Pitfalls
79
-
80
- - **Listening for an `:accept` event:** There is no `ln-confirm:accept`. Destructive logic belongs in the button's native click handler or form submit listener — which are automatically gated.
81
- - **Double-Click Protection:** Once the second click executes the target action, the button returns to idle. If your custom AJAX handler takes time, the user can click again and execute duplicate actions. Disable the button on submit:
82
- ```javascript
83
- form.addEventListener('submit', () => submitButton.disabled = true);
84
- ```
85
- - **GET Request Navigation (`<a>`):** While the script works on links, performing destructive actions via HTTP GET is a security risk (crawlers and pre-fetchers can trigger deletes). Always use `<form method="POST">` with submit buttons instead.
86
- - **Attachment Order:** `ln-confirm` intercepts first clicks via `stopImmediatePropagation()`. If custom click handlers are bound *before* the bundle loads, they will run on the first click anyway. Ensure scripts are defer-loaded.
1
+ # ln-confirm
2
+
3
+ A zero-dependency, ultra-lightweight **Interaction Gate Primitive** (~131 lines of JavaScript) that creates a self-contained, two-click confirmation checkpoint on standard buttons. It intercepts accidental clicks, morphs in-place to reveal confirmation prompts, and passes the second click directly to native platform events.
4
+
5
+ ---
6
+
7
+ ## 🧭 Philosophy & Architecture
8
+
9
+ 1. **In-Place Morphing:** Instead of launching heavy, separate dialogs or using frozen, unstyleable `window.confirm()` scripts, confirmation lives **directly on the target button**. It preserves structural styling while using CSS mixins to display temporary labels or icon tooltips.
10
+ 2. **Platform Event Release:** `ln-confirm` does not implement a custom `accept` handler. The acceptance is the standard platform `click` event. On the second click, the component steps out of the way of the *default action* — form submissions (`type="submit"`), links (`href`), or custom AJAX click listeners execute natively. It does **not** step out of the way of propagation: the click stops at the button on both clicks, so an ancestor click surface never sees it.
11
+ 3. **Graceful Auto-Revert:** The gate is timed. When armed, a countdown timer is scheduled. If a second click does not arrive within the window, the button cleanly reverts to its idle text or icon state.
12
+
13
+ ---
14
+
15
+ ## 📦 Minimal Blueprint
16
+
17
+ ### Text-Button Mode
18
+
19
+ ```html
20
+ <button data-ln-confirm="Are you sure you want to delete?">Delete account</button>
21
+ ```
22
+
23
+ On click, the text replaces with the confirmation prompt in-place, and the button accent shifts to error red.
24
+
25
+ ### Icon-Only Mode
26
+
27
+ ```html
28
+ <button aria-label="Delete item" data-ln-confirm="Confirm delete?">
29
+ <svg class="ln-icon"><use href="#ln-icon-trash"></use></svg>
30
+ </button>
31
+ ```
32
+
33
+ ### Two-Element Mode (Recommended for rich content)
34
+
35
+ ```html
36
+ <button class="btn btn-danger" data-ln-confirm>
37
+ <!-- Idle state -->
38
+ <span data-ln-confirm-idle>
39
+ <svg class="ln-icon"><use href="#ln-icon-trash"></use></svg>
40
+ Delete Selected (<span data-ln-table-selected></span>)
41
+ </span>
42
+ <!-- Active (Confirming) state -->
43
+ <span data-ln-confirm-active hidden>
44
+ Are you sure?
45
+ </span>
46
+ </button>
47
+ ```
48
+
49
+ On click, the idle content hides and the active content unhides, without mutating or destroying the button's internal DOM nodes (such as the icon or selection span).
50
+
51
+ ---
52
+
53
+ ## 🛠️ Declarative API Contract
54
+
55
+
56
+ ### HTML Attributes
57
+
58
+ | Attribute | Elements | Description |
59
+ | :--- | :--- | :--- |
60
+ | `data-ln-confirm="Prompt"` | `<button>`, `<a>` | Action gate marker. Empty value defaults to `"Confirm?"` (in legacy mode). If using Two-Element Mode, this value is left blank. |
61
+ | `data-ln-confirm-timeout="3"` | `<button>`, `<a>` | Auto-revert delay in seconds (default `3`). |
62
+ | `data-confirming="true"` | `<button>` (auto) | Managed state. Set during active confirmation; acts as public CSS hook. |
63
+ | `data-tooltip-text="Prompt"` | `<button>` (auto) | Managed state. Displays tooltip bubble in legacy icon-only mode. |
64
+ | `data-ln-confirm-idle` | Any child | Defines the idle state layout/content (button icon/text) in Two-Element Mode. |
65
+ | `data-ln-confirm-active` | Any child | Defines the confirmation prompt content in Two-Element Mode. |
66
+
67
+ ---
68
+
69
+ ### JS API
70
+
71
+ Access the confirmation instance directly via the `lnConfirm` property on the button element:
72
+
73
+ ```javascript
74
+ const button = document.getElementById('delete-btn');
75
+
76
+ // 1. Check if armed (Boolean getter)
77
+ if (button.lnConfirm.confirming) { ... }
78
+
79
+ // 2. Disarm immediately, restore visual states, and unbind listeners
80
+ button.lnConfirm.destroy();
81
+ ```
82
+
83
+ ---
84
+
85
+ ## ⚡ DOM Events
86
+
87
+ `ln-confirm` dispatches exactly one event during its lifecycle:
88
+
89
+ | Event | Bubbles | Payload | Description |
90
+ | :--- | :--- | :--- | :--- |
91
+ | `ln-confirm:waiting` | Yes | `{ target }` | Fires on first click, right after the button arms. Useful for telemetry. |
92
+
93
+ *Note: There is no `accept` event. Code execution on accept belongs on standard browser `click` or form `submit` handlers.*
94
+
95
+ ---
96
+
97
+ ## ⚠️ Common Pitfalls
98
+
99
+ - **Listening for an `:accept` event:** There is no `ln-confirm:accept`. Destructive logic belongs in the button's native click handler or form submit listener — which are automatically gated.
100
+ - **Double-Click Protection:** Once the second click executes the target action, the button returns to idle. If your custom AJAX handler takes time, the user can click again and execute duplicate actions. Disable the button on submit:
101
+ ```javascript
102
+ form.addEventListener('submit', () => submitButton.disabled = true);
103
+ ```
104
+ - **GET Request Navigation (`<a>`):** While the script works on links, performing destructive actions via HTTP GET is a security risk (crawlers and pre-fetchers can trigger deletes). Always use `<form method="POST">` with submit buttons instead.
105
+ - **Attachment Order:** `ln-confirm` intercepts first clicks via `stopImmediatePropagation()`. If custom click handlers are bound *before* the bundle loads, they will run on the first click anyway. Ensure scripts are defer-loaded.
106
+ - **Bulk Actions & High-Impact Operations:** `ln-confirm` is strictly for **single-element, low-impact actions** (e.g. deleting a single table row). Bulk operations (e.g. "Delete selected users") must use a proper confirmation modal (`ln-modal`) that lists affected resources and offers explicit "Confirm" / "Cancel" buttons.
107
+
108
+ ---
109
+
110
+ ## 🔧 Internals
111
+
112
+ Source: `js/ln-confirm/ln-confirm.js`. Imports nothing from other components, consumes no `ln-*` event, and emits nothing the library listens to. Its entire contract is the platform `click` sequence on its own button.
113
+
114
+ ### Two-click flow
115
+
116
+ The click handler branches on `this.confirming`.
117
+
118
+ **First click** (`confirming === false`) — arm:
119
+ - `preventDefault()` blocks the platform action (submit / navigate).
120
+ - `stopImmediatePropagation()` stops *later* same-element listeners, so a project's own analytics/validation click handler does not fire on the arming click — only on the second, accepting click. Listeners bound *before* ln-confirm still run, which is why the bundle must be defer-loaded.
121
+ - `_enterConfirm()`: set `confirming`, write `data-confirming="true"` (CSS hook), swap the button to its confirm presentation (see Modes), schedule the auto-revert timer, dispatch `ln-confirm:waiting`.
122
+
123
+ **Second click** (`confirming === true`) — accept:
124
+ - No `preventDefault` — the click runs its native default (form submit, link nav, existing handler) unmodified. That is the whole design: insert a checkpoint, then step out.
125
+ - `stopPropagation()` — the click is contained at the button, exactly as on the first click. Without it the accepting click reaches ancestor click surfaces (a clickable card, a row handler) and fires *their* action alongside the confirmed one. Deliberately **not** `stopImmediatePropagation`: same-element listeners are the accept path and must run.
126
+ - `_reset()` runs **synchronously before the handler returns**, so the button is visually reverted in the same frame the action proceeds — no flash of the armed state. A `_submitted` flag guards against a synthetic re-entrant click inside the same handler.
127
+
128
+ Containment covers listener-based ancestors. It does not cover an ancestor `<a href>`: navigation is activation behavior, cancelled only by `preventDefault` — which would also cancel the confirmed action. Don't nest the button inside the anchor; see [ln-link](../ln-link/README.md), which skips clicks originating from `button`.
129
+
130
+ There is deliberately no `ln-confirm:accept` event and no second-click cancel signal — "accept" is just the native click; listen on the form `submit`, the link, or the button `click`.
131
+
132
+ ### Construction-time snapshot
133
+
134
+ `originalText` (`dom.textContent.trim()`) and `confirmText` (`data-ln-confirm`, default `"Confirm?"`) are captured **once at construction**, not per click. Mutating the button's text or the attribute afterwards does not propagate to the live instance — the contract is snapshotted once and run on every click. This is intentional: re-reading per confirm would race AJAX-driven content updates and change the button text mid-confirm.
135
+
136
+ For the same reason the shared observer watches `data-ln-confirm` only, **not** `data-ln-confirm-timeout`. The timeout is read once, at arm time, in `_startTimer` (`parseFloat`, falling back to `3` on `NaN`/`≤0`). Changing it while armed does not affect the running timer — set it before the user arms.
137
+
138
+ ### Modes
139
+
140
+ - **Two-Element (recommended):** if the button contains `[data-ln-confirm-idle]` and `[data-ln-confirm-active]`, the component only toggles their `hidden` attribute — no DOM mutation, so inner icons / selection-count spans survive and all text stays authored in HTML.
141
+ - **Legacy text:** no child markers → `dom.textContent` is swapped to `confirmText` and restored on revert.
142
+ - **Icon-only:** fires when `originalText === ''` **and** a `svg.ln-icon use` exists at confirm time (checked fresh, to handle async-rendered icons). Swaps `<use href>` to `#ln-icon-check` (ln-icon fetches it on demand), adds `.ln-confirm-tooltip` + `data-tooltip-text`, and handles a11y (below). Everything is restored symmetrically in `_reset`.
143
+
144
+ ### Icon-only accessibility
145
+
146
+ The tooltip bubble is a CSS `::after` — invisible to AT. So the icon branch also:
147
+ 1. swaps `aria-label` to `confirmText` (original captured, restored on reset), and
148
+ 2. appends a transient `<span class="sr-only" role="alert">` carrying the prompt — `role="alert"` is announced on insertion, which an `aria-label` change alone is not. Removed on reset.
149
+
150
+ Text mode needs neither: `textContent` *is* the accessible name.
151
+
152
+ ### CSS hooks
153
+
154
+ Co-located SCSS is two JS-state rules (the accepted co-located exception):
155
+
156
+ ```scss
157
+ [data-confirming]:not(.ln-confirm-tooltip) { --color-primary: var(--color-error); }
158
+ [data-ln-table]:has([data-confirming]) { overflow: visible; }
159
+ ```
160
+
161
+ The first rebinds `--color-primary` to `--color-error` while armed — the red fill falls out of `@mixin btn` reading the primary token, so theme overrides apply automatically. `:not(.ln-confirm-tooltip)` avoids double-coloring icon mode, where `@mixin confirm-tooltip` owns the color. The second lifts `ln-table`'s `overflow: clip` so the icon tooltip isn't clipped by the row while any confirm in the table is armed; other scrollable containers need their own parallel rule.
162
+
163
+ ### Destroy
164
+
165
+ `destroy()` is idempotent, calls `_reset()` **first** (so a destroy-while-armed clears the pending timer and visual state), then unbinds the listener and deletes `el.lnConfirm`. It does not remove `data-ln-confirm` — the caller owns the attribute, so a later observer rescan can re-create the instance.
@@ -11,3 +11,14 @@
11
11
  [data-ln-table]:has([data-confirming]) {
12
12
  overflow: visible;
13
13
  }
14
+
15
+ // ── Show/hide for two-element mode ──
16
+ [data-ln-confirm] {
17
+ &:not([data-confirming="true"]) [data-ln-confirm-active] {
18
+ display: none !important;
19
+ }
20
+ &[data-confirming="true"] [data-ln-confirm-idle] {
21
+ display: none !important;
22
+ }
23
+ }
24
+
@@ -1 +1 @@
1
- (function(){"use strict";function _(t,e,a){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:a||{}}))}function A(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){A(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function g(t,e,a,c){if(t.nodeType!==1)return;const i=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",n=Array.from(t.querySelectorAll(i));t.matches&&t.matches(i)&&n.push(t);for(const o of n)o[a]||(o[a]=new c(o))}function w(t,e,a,c,r={}){const i=r.extraAttributes||[],n=r.onAttributeChange||null,o=r.onInit||null;function m(p){const u=p||document.body;g(u,t,e,a),o&&o(u)}return A(function(){const p=new MutationObserver(function(h){for(let f=0;f<h.length;f++){const s=h[f];if(s.type==="childList"){for(let l=0;l<s.addedNodes.length;l++){const d=s.addedNodes[l];d.nodeType===1&&(g(d,t,e,a),o&&o(d))}for(let l=0;l<s.removedNodes.length;l++){const d=s.removedNodes[l];if(d.nodeType===1){const x=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",b=Array.from(d.querySelectorAll(x));d.matches&&d.matches(x)&&b.push(d);for(let T=0;T<b.length;T++){const C=b[T];if(!document.contains(C)){const y=C[e];y&&typeof y.destroy=="function"&&y.destroy()}}}}}else s.type==="attributes"&&(n&&s.target[e]?n(s.target,s.attributeName):(g(s.target,t,e,a),o&&o(s.target)))}});let u=[];if(t.indexOf("[")!==-1){const h=/\[([\w-]+)/g;let f;for(;(f=h.exec(t))!==null;)u.push(f[1])}else u.push(t);p.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:u.concat(i)})},c),window[e]=m,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){m(document.body)}):m(document.body),m}const v={};function L(t,e){v[t]=e}function N(t){return v[t]||{ingress:e=>e,egress:e=>e}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=L,window.lnCore.getDataMapper=N),(function(){const t="data-ln-confirm",e="lnConfirm",a="data-ln-confirm-timeout";if(window[e]!==void 0)return;function r(i){this.dom=i,this.confirming=!1,this.originalText=i.textContent.trim(),this.confirmText=i.getAttribute(t)||"Confirm?",this.revertTimer=null,this._submitted=!1;const n=this;return this._onClick=function(o){if(!n.confirming)o.preventDefault(),o.stopImmediatePropagation(),n._enterConfirm();else{if(n._submitted)return;n._submitted=!0,n._reset()}},i.addEventListener("click",this._onClick),this}r.prototype._getTimeout=function(){const i=parseFloat(this.dom.getAttribute(a));return isNaN(i)||i<=0?3:i},r.prototype._enterConfirm=function(){this.confirming=!0,this.dom.setAttribute("data-confirming","true");var i=this.dom.querySelector("svg.ln-icon use");i&&this.originalText===""?(this.isIconButton=!0,this.originalIconHref=i.getAttribute("href"),i.setAttribute("href","#ln-check"),this.dom.classList.add("ln-confirm-tooltip"),this.dom.setAttribute("data-tooltip-text",this.confirmText),this.originalAriaLabel=this.dom.getAttribute("aria-label"),this.dom.setAttribute("aria-label",this.confirmText),this.alertNode=document.createElement("span"),this.alertNode.className="sr-only",this.alertNode.setAttribute("role","alert"),this.alertNode.textContent=this.confirmText,this.dom.appendChild(this.alertNode)):this.dom.textContent=this.confirmText,this._startTimer(),_(this.dom,"ln-confirm:waiting",{target:this.dom})},r.prototype._startTimer=function(){this.revertTimer&&clearTimeout(this.revertTimer);const i=this,n=this._getTimeout()*1e3;this.revertTimer=setTimeout(function(){i._reset()},n)},r.prototype._reset=function(){if(this._submitted=!1,this.confirming=!1,this.dom.removeAttribute("data-confirming"),this.isIconButton){var i=this.dom.querySelector("svg.ln-icon use");i&&this.originalIconHref&&i.setAttribute("href",this.originalIconHref),this.dom.classList.remove("ln-confirm-tooltip"),this.dom.removeAttribute("data-tooltip-text"),this.originalAriaLabel!==null&&this.originalAriaLabel!==void 0?this.dom.setAttribute("aria-label",this.originalAriaLabel):this.dom.removeAttribute("aria-label"),this.originalAriaLabel=null,this.alertNode&&this.alertNode.parentNode===this.dom&&this.dom.removeChild(this.alertNode),this.alertNode=null,this.isIconButton=!1,this.originalIconHref=null}else this.dom.textContent=this.originalText;this.revertTimer&&(clearTimeout(this.revertTimer),this.revertTimer=null)},r.prototype.destroy=function(){this.dom[e]&&(this._reset(),this.dom.removeEventListener("click",this._onClick),delete this.dom[e])},w(t,e,r,"ln-confirm")})()})();
1
+ (function(){"use strict";if(typeof window<"u"){const t=console.warn;console.warn=function(...e){typeof e[0]=="string"&&(e[0].startsWith("[ln-")||e[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||t.apply(console,e)}}const p={};function D(t,e){p[t]||(p[t]=document.querySelector('[data-ln-template="'+t+'"]'));const o=p[t];return o?o.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function M(t,e,o){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:o||{}}))}function C(t,e){if(!t||!e)return t;const o=t.querySelectorAll("[data-ln-field]");for(let i=0;i<o.length;i++){const n=o[i],r=n.getAttribute("data-ln-field");e[r]!=null&&(n.textContent=e[r])}const u=t.querySelectorAll("[data-ln-attr]");for(let i=0;i<u.length;i++){const n=u[i],r=n.getAttribute("data-ln-attr").split(",");for(let s=0;s<r.length;s++){const c=r[s].trim().split(":");if(c.length!==2)continue;const d=c[0].trim(),f=c[1].trim();e[f]!=null&&n.setAttribute(d,e[f])}}const a=t.querySelectorAll("[data-ln-show]");for(let i=0;i<a.length;i++){const n=a[i],r=n.getAttribute("data-ln-show");r in e&&n.classList.toggle("hidden",!e[r])}const l=t.querySelectorAll("[data-ln-class]");for(let i=0;i<l.length;i++){const n=l[i],r=n.getAttribute("data-ln-class").split(",");for(let s=0;s<r.length;s++){const c=r[s].trim().split(":");if(c.length!==2)continue;const d=c[0].trim(),f=c[1].trim();f in e&&n.classList.toggle(d,!!e[f])}}return t}function q(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));const o=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let u=0;u<o.length;u++)o[u].dispatchEvent(new CustomEvent("ln-fill",{detail:e??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 e=t.target.querySelectorAll("[data-ln-field]");for(let o=0;o<e.length;o++)e[o].textContent=""}})));function T(t,e){if(!t||!e)return t;const o=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;o.nextNode();){const l=o.currentNode;l.textContent.indexOf("{{")!==-1&&(l.textContent=l.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(i,n){return e[n]!==void 0?e[n]:""}))}const u=function(l,i){return e[i]!==void 0?e[i]:""},a=Array.from(t.querySelectorAll("*"));t.nodeType===1&&a.push(t);for(let l=0;l<a.length;l++){const i=a[l],n=i.attributes;for(let r=0;r<n.length;r++){const s=n[r];s.value.indexOf("{{")!==-1&&i.setAttribute(s.name,s.value.replace(/\{\{\s*(\w+)\s*\}\}/g,u))}}return t}function I(t,e,o,u,a,l){const i={};for(let r=0;r<t.children.length;r++){const s=t.children[r],c=s.getAttribute("data-ln-key");c&&(i[c]=s)}const n=document.createDocumentFragment();for(let r=0;r<e.length;r++){const s=e[r],c=String(u(s));let d=i[c];if(d)a(d,s,r);else{const f=D(o,l);if(!f||(T(f,s),d=f.firstElementChild,!d))continue;d.setAttribute("data-ln-key",c),a(d,s,r)}n.appendChild(d)}t.textContent="",t.appendChild(n)}function x(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){x(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function b(t,e,o,u){if(t.nodeType!==1)return;const l=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",i=Array.from(t.querySelectorAll(l));t.matches&&t.matches(l)&&i.push(t);for(const n of i)n[o]||(n[o]=new u(n))}function N(t,e,o,u,a={}){const l=a.extraAttributes||[],i=a.onAttributeChange||null,n=a.onInit||null;function r(s){const c=s||document.body;b(c,t,e,o),n&&n(c)}return x(function(){const s=new MutationObserver(function(d){for(let f=0;f<d.length;f++){const h=d[f];if(h.type==="childList"){for(let g=0;g<h.addedNodes.length;g++){const m=h.addedNodes[g];m.nodeType===1&&(b(m,t,e,o),n&&n(m))}for(let g=0;g<h.removedNodes.length;g++){const m=h.removedNodes[g];if(m.nodeType===1){const S=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",y=Array.from(m.querySelectorAll(S));m.matches&&m.matches(S)&&y.push(m);for(let A=0;A<y.length;A++){const O=y[A];if(!document.contains(O)){const v=O[e];v&&typeof v.destroy=="function"&&v.destroy()}}}}}else h.type==="attributes"&&(i&&h.target[e]?i(h.target,h.attributeName):(b(h.target,t,e,o),n&&n(h.target)))}});let c=[];if(t.indexOf("[")!==-1){const d=/\[([\w-]+)/g;let f;for(;(f=d.exec(t))!==null;)c.push(f[1])}else c.push(t);s.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:c.concat(l)})},u),window[e]=r,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){r(document.body)}):r(document.body),r}const E={};function F(t,e){E[t]=e}function j(t){return E[t]||{ingress:e=>e,egress:e=>e}}const L={};function _(t,e){if(!t||typeof e!="object")return;const o=t.toLowerCase().split("-")[0];L[o]=e}function B(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return L[e]||null}_("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=F,window.lnCore.getDataMapper=j,window.lnCore.registerLocaleFallback=_,window.lnCore.getLocaleFallback=B,window.lnCore.fillTemplate=T,window.lnCore.fill=C,window.lnCore.lnFill=q,window.lnCore.renderList=I);function k(t){return(t||"").replace(/^#/,"")}function w(t){const e=t===void 0?location.hash:t,o={},u=k(e);if(!u)return o;const a=u.split("&");for(let l=0;l<a.length;l++){const i=a[l];if(!i)continue;const n=i.indexOf(":"),r=n>-1?i.slice(0,n):i,s=n>-1?i.slice(n+1):"";if(r)try{o[r]=decodeURIComponent(s)}catch{o[r]=s}}return o}function W(t){if(!t)return null;const e=w();return t in e?e[t]:null}function H(t,e){if(!t)return;const o=w();e==null?delete o[t]:o[t]=String(e);const a=Object.keys(o).map(function(l){const i=o[l];return i===""?l:l+":"+encodeURIComponent(i)}).join("&");k(location.hash)!==a&&(location.hash=a)}function R(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=w,window.lnCore.hashGet=W,window.lnCore.hashSet=H,window.lnCore.hashLinkClick=R),(function(){const t="data-ln-confirm",e="lnConfirm",o="data-ln-confirm-timeout";if(window[e]!==void 0)return;function a(...i){(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))&&console.warn("[ln-confirm]",...i)}function l(i){a("constructor called on",i),this.dom=i,this.confirming=!1,this.revertTimer=null,this._submitted=!1,this.idleEl=i.querySelector("[data-ln-confirm-idle]"),this.activeEl=i.querySelector("[data-ln-confirm-active]"),this.isTwoElementMode=!!(this.idleEl||this.activeEl),this.isTwoElementMode?(this.originalText="",this.confirmText=""):(this.originalText=i.textContent.trim(),this.confirmText=i.getAttribute(t)||"Confirm?");const n=this;return this._onClick=function(r){if(a("click handler, confirming:",n.confirming,"submitted:",n._submitted,"target:",r.target),!n.confirming)r.preventDefault(),r.stopImmediatePropagation(),n._enterConfirm();else{if(n._submitted)return;n._submitted=!0,r.stopPropagation(),n._reset()}},i.addEventListener("click",this._onClick),this}l.prototype._getTimeout=function(){const i=parseFloat(this.dom.getAttribute(o));return isNaN(i)||i<=0?3:i},l.prototype._enterConfirm=function(){if(this.confirming=!0,this.dom.setAttribute("data-confirming","true"),this.originalAriaLabel=this.dom.getAttribute("aria-label"),this.originalAriaLive=this.dom.getAttribute("aria-live"),this.isTwoElementMode){this.idleEl&&this.idleEl.setAttribute("hidden","true"),this.activeEl&&this.activeEl.removeAttribute("hidden");const n=this.activeEl?this.activeEl.textContent.trim():"";n&&(this.dom.setAttribute("aria-label",n),this.dom.setAttribute("aria-live","polite"))}else{var i=this.dom.querySelector("svg.ln-icon use");i&&this.originalText===""?(this.isIconButton=!0,this.originalIconHref=i.getAttribute("href"),i.setAttribute("href","#ln-icon-check"),this.dom.classList.add("ln-confirm-tooltip"),this.dom.setAttribute("data-tooltip-text",this.confirmText),this.dom.setAttribute("aria-label",this.confirmText),this.dom.setAttribute("aria-live","polite")):this.dom.textContent=this.confirmText}this._startTimer(),M(this.dom,"ln-confirm:waiting",{target:this.dom})},l.prototype._startTimer=function(){this.revertTimer&&clearTimeout(this.revertTimer);const i=this,n=this._getTimeout()*1e3;this.revertTimer=setTimeout(function(){i._reset()},n)},l.prototype._reset=function(){if(this._submitted=!1,this.confirming=!1,this.dom.removeAttribute("data-confirming"),this.isTwoElementMode)this.idleEl&&this.idleEl.removeAttribute("hidden"),this.activeEl&&this.activeEl.setAttribute("hidden","true");else if(this.isIconButton){var i=this.dom.querySelector("svg.ln-icon use");i&&this.originalIconHref&&i.setAttribute("href",this.originalIconHref),this.dom.classList.remove("ln-confirm-tooltip"),this.dom.removeAttribute("data-tooltip-text"),this.isIconButton=!1,this.originalIconHref=null}else this.dom.textContent=this.originalText;this.originalAriaLabel!==null&&this.originalAriaLabel!==void 0?this.dom.setAttribute("aria-label",this.originalAriaLabel):this.dom.removeAttribute("aria-label"),this.originalAriaLabel=null,this.originalAriaLive!==null&&this.originalAriaLive!==void 0?this.dom.setAttribute("aria-live",this.originalAriaLive):this.dom.removeAttribute("aria-live"),this.originalAriaLive=null,this.revertTimer&&(clearTimeout(this.revertTimer),this.revertTimer=null)},l.prototype.destroy=function(){a("destroy called on",this.dom),this.dom[e]&&(this.confirming&&this._reset(),this.dom.removeEventListener("click",this._onClick),delete this.dom[e])},N(t,e,l,"ln-confirm")})()})();