@livenetworks/ashlar 2.0.4 → 2.0.6

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.
@@ -1,8 +1,10 @@
1
- import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../../ln-core';
1
+ import { registerComponent, dispatch, dispatchCancelable, isVisible, shouldIgnoreClick, isTargetDisabled } from '../../ln-core';
2
2
 
3
3
  (function () {
4
4
  const DOM_SELECTOR = 'data-ln-modal';
5
5
  const DOM_ATTRIBUTE = 'lnModal';
6
+ const TRIGGER_ATTRIBUTE = 'data-ln-modal-for';
7
+ const CLOSE_TRIGGER_ATTRIBUTE = 'data-ln-modal-close';
6
8
 
7
9
  if (window[DOM_ATTRIBUTE] !== undefined) return;
8
10
 
@@ -15,12 +17,56 @@ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../.
15
17
  effect: _syncAttribute,
16
18
  description: 'Control state of the modal dialog'
17
19
  },
20
+ 'data-ln-modal-for': {
21
+ type: 'string',
22
+ description: 'Target modal ID to open on trigger click'
23
+ },
24
+ 'data-ln-modal-mode': {
25
+ type: 'enum',
26
+ values: ['new', 'edit'],
27
+ fallback: 'new',
28
+ description: 'Operational mode of the modal (new vs edit)'
29
+ },
18
30
  'data-ln-modal-close': {
19
31
  type: 'trigger',
20
32
  description: 'Click dismiss trigger inside the modal'
21
33
  }
22
34
  };
23
35
 
36
+ const instances = new Set();
37
+ let clickListener = null;
38
+
39
+ function _ensureClickListener() {
40
+ if (clickListener) return;
41
+ clickListener = function (e) {
42
+ if (shouldIgnoreClick(e)) return;
43
+
44
+ const trigger = e.target.closest('[' + TRIGGER_ATTRIBUTE + ']');
45
+ if (!trigger || isTargetDisabled(trigger)) return;
46
+
47
+ const targetId = trigger.getAttribute(TRIGGER_ATTRIBUTE);
48
+ if (!targetId) return;
49
+
50
+ const target = document.getElementById(targetId) || document.querySelector('[' + DOM_SELECTOR + '="' + targetId + '"]');
51
+ if (!target || !target[DOM_ATTRIBUTE]) return;
52
+
53
+ e.preventDefault();
54
+ if (trigger.hasAttribute('data-ln-modal-mode')) {
55
+ target.setAttribute('data-ln-modal-mode', trigger.getAttribute('data-ln-modal-mode'));
56
+ } else if (target.hasAttribute('data-ln-modal-mode')) {
57
+ target.setAttribute('data-ln-modal-mode', 'new');
58
+ }
59
+ target.setAttribute(DOM_SELECTOR, 'open');
60
+ };
61
+ document.addEventListener('click', clickListener);
62
+ }
63
+
64
+ function _maybeRemoveClickListener() {
65
+ if (instances.size > 0 || !clickListener) return;
66
+ document.removeEventListener('click', clickListener);
67
+ clickListener = null;
68
+ }
69
+
24
70
  // ─── Component Constructor ─────────────────────────────
25
71
 
26
72
  function _component(dom) {
@@ -46,7 +92,7 @@ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../.
46
92
 
47
93
  // Dismiss trigger buttons inside modal [data-ln-modal-close]
48
94
  this._onClickClose = function (e) {
49
- const closeBtn = e.target.closest('[data-ln-modal-close]');
95
+ const closeBtn = e.target.closest('[' + CLOSE_TRIGGER_ATTRIBUTE + ']');
50
96
  if (closeBtn && self.dom.contains(closeBtn)) {
51
97
  e.preventDefault();
52
98
  self.dom.setAttribute(DOM_SELECTOR, 'close');
@@ -58,6 +104,9 @@ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../.
58
104
  this.dom.addEventListener('cancel', this._onCancel);
59
105
  this.dom.addEventListener('click', this._onClickClose);
60
106
 
107
+ instances.add(this);
108
+ _ensureClickListener();
109
+
61
110
  // Apply initial state if rendered with open attribute
62
111
  if (this.isOpen) {
63
112
  if (typeof this.dom.showModal === 'function') this.dom.showModal();
@@ -89,6 +138,9 @@ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../.
89
138
  this.dom.removeEventListener('cancel', this._onCancel);
90
139
  this.dom.removeEventListener('click', this._onClickClose);
91
140
 
141
+ instances.delete(this);
142
+ _maybeRemoveClickListener();
143
+
92
144
  if (this.isOpen) {
93
145
  const dom = this.dom;
94
146
  const otherOpen = Array.prototype.some.call(
@@ -147,6 +199,9 @@ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../.
147
199
  return;
148
200
  }
149
201
  instance.isOpen = false;
202
+ if (el.hasAttribute('data-ln-modal-mode')) {
203
+ el.setAttribute('data-ln-modal-mode', 'new');
204
+ }
150
205
  dispatch(el, 'ln-modal:close', { modalId: el.id, target: el });
151
206
 
152
207
  if (typeof el.close === 'function') el.close();
@@ -1,101 +1,62 @@
1
- # ln-ui-coordinator
2
-
3
- > Applied to a container element (`<main data-ln-ui-coordinator>`). It listens for clicks on `[data-ln-modal-for]` triggers and `a[href^="#"]` anchors to open `[data-ln-modal]` dialogs and synchronize the URL hash, intercepts bubbling `ln-ajax:success`/`ln-ajax:error` and `ln-upload:error` events to dispatch `ln-toast:enqueue` notifications, and extracts `data-ln-fill-*` attributes to dispatch `ln-fill:request`. It never inspects internal form validation states, keeping forms and dialogs strictly encapsulated.
4
-
5
- A general-purpose **Layer 2 Coordinator** that mediates UI triggers, modals (`ln-modal`), AJAX (`ln-ajax`), uploads (`ln-upload`), record filling (`ln-fill`), and toast notifications (`ln-toast`).
6
-
7
- ---
8
-
9
- ## 1. Responsibilities
10
-
11
- 1. **Modal Triggers & Hash Navigation:** Listens for clicks on `[data-ln-modal-for]` triggers and `a[href^="#"]` hash anchors, synchronizing the active modal state with the URL hash and handling `new` / `edit` modes.
12
- 2. **Dynamic Form & Record Population:** Bridges trigger payloads (`data-ln-fill-*`) and emits `ln-fill:request` on modal open.
13
- 3. **AJAX Submit Mediation:** Catches `ln-ajax:success` events:
14
- - Dispatches `ln-toast:enqueue` notifications when the server returns `{ message: { body, title?, type? } }` (where `body` is required).
15
- - Closes the active modal (if submit originated inside a modal), cleans URL hash, and resets the form.
16
- 4. **Error Feedback & Resilience:** Catches `ln-ajax:error`:
17
- - Dispatches error toasts on server error envelopes or network failures (with fallback support via `data-ln-ui-coordinator-dict`).
18
- - Climbs parent coordinator containers to inherit and merge dictionary translations hierarchically.
19
- - Keeps modals open on error so form validation messages remain visible.
20
- 5. **Upload Feedback Mediation:** Catches `ln-upload:invalid` and `ln-upload:error`:
21
- - Dispatches error toasts with translated messages (`upload-invalid-title`, `upload-invalid-type`, `upload-max-size`, `upload-max-files`, `upload-error-title`, `upload-failed`).
22
-
23
- ---
24
-
25
- ## 2. HTML Markup
26
-
27
- ```html
28
- <!-- Wrapper container or dialog with localized fallback messages -->
29
- <main data-ln-ui-coordinator>
30
- <ul hidden>
31
- <li data-ln-ui-coordinator-dict="network-error">Network connection failed.</li>
32
- <li data-ln-ui-coordinator-dict="network-error-title">Connection Error</li>
33
- <li data-ln-ui-coordinator-dict="server-error">The server encountered an error.</li>
34
- <li data-ln-ui-coordinator-dict="server-error-title">Server Error</li>
35
- <li data-ln-ui-coordinator-dict="upload-invalid-title">Invalid File</li>
36
- <li data-ln-ui-coordinator-dict="upload-invalid-type">This file type is not allowed.</li>
37
- <li data-ln-ui-coordinator-dict="upload-max-size">File size exceeds maximum allowed limit.</li>
38
- <li data-ln-ui-coordinator-dict="upload-max-files">Maximum number of uploaded files exceeded.</li>
39
- <li data-ln-ui-coordinator-dict="upload-error-title">Upload Error</li>
40
- <li data-ln-ui-coordinator-dict="upload-failed">Failed to upload file.</li>
41
- </ul>
42
-
43
- <!-- Triggers -->
44
- <button type="button" data-ln-modal-for="user-modal">New User</button>
45
- <a href="#user-modal:42" data-ln-fill-id="42" data-ln-fill-name="Ada Lovelace">Edit #42</a>
46
-
47
- <!-- Modal Dialog -->
48
- <dialog class="ln-modal" data-ln-modal id="user-modal">
49
- <form action="/api/users" method="POST" data-ln-ajax id="user-form">
50
- <input type="text" name="name" required />
51
- <button type="submit">Save</button>
52
- <button type="button" data-ln-modal-close>Cancel</button>
53
- </form>
54
- </dialog>
55
- </main>
56
- ```
57
-
58
- ---
59
-
60
- ## 3. Declarative Attributes
61
-
62
- | Attribute | Element | Description |
63
- |:---|:---|:---|
64
- | `data-ln-ui-coordinator` | Container / Dialog | Activates UI orchestration on the element and its subtree, acting as dictionary host. |
65
- | `data-ln-ui-coordinator-dict` | `<li>` element | Translatable fallback toast messages (see Dictionary Keys below). |
66
- | `data-ln-modal-for="id"` | Trigger `<button>` | Opens the target `ln-modal` by id. |
67
- | `data-ln-modal-mode="new\|edit"` | Trigger / `<dialog>` | Forces explicit modal creation or edit mode. |
68
- | `data-ln-fill-*` | Trigger `<a>` | Passes record values to form controls inside the opened modal. |
69
-
70
- ### Dictionary Keys
71
-
72
- | Key | Used For | Fallback |
73
- |:---|:---|:---|
74
- | `network-error` | Network disconnection error message | `Network error` |
75
- | `network-error-title` | Network error toast title | `""` |
76
- | `server-error` | HTTP server error message | `Server error` |
77
- | `server-error-title` | HTTP server error toast title | `""` |
78
- | `upload-invalid-title` | Invalid file toast title | `Invalid File` |
79
- | `upload-invalid-type` | Invalid file extension / MIME message | `This file type is not allowed` |
80
- | `upload-max-size` | Exceeded max size message | `File is too large` |
81
- | `upload-max-files` | Exceeded max file count message | `Maximum file count exceeded` |
82
- | `upload-error-title` | Upload failure toast title | `Upload Error` |
83
- | `upload-failed` | Upload failure message | `Failed to upload file` |
84
-
85
- ---
86
-
87
- ## 4. DOM Events
88
-
89
- | Event | Direction | Target | Description |
90
- |:---|:---|:---|:---|
91
- | `ln-modal:request-open` | Emits | `<dialog>` | Requests panel opening on target `ln-modal`. |
92
- | `ln-modal:request-close` | Emits | `<dialog>` | Requests panel closing on target `ln-modal`. |
93
- | `ln-fill:request` | Emits | `<dialog>` | Requests form data population for the opened modal. |
94
- | `ln-toast:enqueue` | Emits | `window` | Dispatched with `{ type, title, message }` on AJAX responses or error fallbacks. |
95
- | `click` | Listens | `document` | Intercepts modal triggers and hash links. |
96
- | `submit` | Listens | `document` | Tracks native submissions in modals for clean hash/reload lifecycle. |
97
- | `hashchange` | Listens | `window` | Synchronizes URL hash and active modals. |
98
- | `ln-ajax:success` | Listens | `document` | Enqueues success toast, closes modal, and resets forms. |
99
- | `ln-ajax:error` | Listens | `document` | Enqueues error toast while keeping modals open. |
100
- | `ln-upload:invalid` | Listens | `document` | Enqueues error toast on file validation failure. |
101
- | `ln-upload:error` | Listens | `document` | Enqueues error toast on upload failure. |
1
+ # ln-ui-coordinator
2
+
3
+ > Applied to a container element (`<main data-ln-ui-coordinator>`). It listens for clicks on `a[href^="#"]` hash anchors and window `hashchange` events to synchronize `[data-ln-modal]` dialogs with the URL hash, intercepts bubbling `ln-ajax:success`/`ln-ajax:error` and `ln-upload:error` events to dispatch `ln-toast:enqueue` notifications from server response envelopes, and clears the modal URL hash and closes active modals upon successful AJAX submit. Button triggers (`data-ln-modal-for`) are handled autonomously by `ln-modal`.
4
+
5
+ A minimal **Layer 2 Coordinator** that mediates URL hash navigation, modal lifecycle, and server toast notifications.
6
+
7
+ ---
8
+
9
+ ## 1. Responsibilities
10
+
11
+ 1. **Hash Navigation & URL Synchronization:** Listens for clicks on `a[href^="#"]` hash anchors and window `hashchange` events, dispatching `ln-modal:request-open` to matching dialogs. Listens to `ln-modal:close` to clean up the URL hash segment.
12
+ 2. **AJAX Submit Mediation:** Catches `ln-ajax:success` events:
13
+ - Dispatches `ln-toast:enqueue` notifications when the server returns `{ message: ... }`.
14
+ - Closes the active modal (if submit originated inside a modal) and cleans URL hash (`hashSet(modal.id, null)`).
15
+ 3. **Server Error Feedback:** Catches `ln-ajax:error` and `ln-upload:error`:
16
+ - Dispatches error toasts when the server returns an error message.
17
+ - Keeps modals open on error so form validation messages remain visible.
18
+
19
+ ---
20
+
21
+ ## 2. HTML Markup
22
+
23
+ ```html
24
+ <main data-ln-ui-coordinator>
25
+ <!-- Triggers (button opens autonomously; hash anchor deep-links) -->
26
+ <button type="button" data-ln-modal-for="user-modal">New User</button>
27
+ <a href="#user-modal">Open User Modal</a>
28
+
29
+ <!-- Modal Dialog -->
30
+ <dialog class="ln-modal" data-ln-modal id="user-modal">
31
+ <form action="/api/users" method="POST" data-ln-ajax id="user-form">
32
+ <input type="text" name="name" required />
33
+ <button type="submit">Save</button>
34
+ <button type="button" data-ln-modal-close>Cancel</button>
35
+ </form>
36
+ </dialog>
37
+ </main>
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 3. Declarative Attributes
43
+
44
+ | Attribute | Element | Description |
45
+ |:---|:---|:---|
46
+ | `data-ln-ui-coordinator` | Container | Activates UI coordination for hash-routing, modal closing on AJAX success, and server toast dispatching. |
47
+
48
+ ---
49
+
50
+ ## 4. DOM Events
51
+
52
+ | Event | Direction | Target | Description |
53
+ |:---|:---|:---|:---|
54
+ | `ln-modal:request-open` | Emits | `<dialog>` | Requests panel opening on target `ln-modal` from URL hash routes. |
55
+ | `ln-modal:request-close` | Emits | `<dialog>` | Requests panel closing on target `ln-modal` upon AJAX success. |
56
+ | `ln-toast:enqueue` | Emits | `window` | Dispatched with server `{ type, title, message }` from response envelopes. |
57
+ | `click` | Listens | `document` | Intercepts `a[href^="#"]` hash navigation links. |
58
+ | `hashchange` | Listens | `window` | Synchronizes URL hash and active modals. |
59
+ | `ln-modal:close` | Listens | `document` | Cleans up modal hash segment when modal closes. |
60
+ | `ln-ajax:success` | Listens | `document` | Relays server message to toast, cleans modal URL hash, and closes parent modal. |
61
+ | `ln-ajax:error` | Listens | `document` | Relays server error message to toast while keeping modal open. |
62
+ | `ln-upload:error` | Listens | `document` | Relays server error message to toast. |
@@ -1 +1 @@
1
- (function(){"use strict";function j(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function L(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let o=null;for(let r=1;r<t.length;r++)if(t[r]&&t[r].nodeType===1){o=t[r];break}if(!L(o))return}e.apply(console,t)}}function m(e,t,n){const o=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,o),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:o}))}function x(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){x(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const B={};function J(e,t){B[e]=t}function P(e){return B[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=J,window.lnCore.getDataMapper=P);function X(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,x(function(){new MutationObserver(function(){m(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const W={};function Y(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];W[n]=t}function Z(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return W[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=Y,window.lnCore.getLocaleFallback=Z,window.lnCore.ensureLocaleObserver=X);const D={};function ee(e,t){D[e]||(D[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=D[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function N(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let l=0;l<n.length;l++){const u=n[l],d=u.getAttribute("data-ln-field");t[d]!=null&&(u.textContent=t[d])}const o=e.querySelectorAll("[data-ln-attr]");for(let l=0;l<o.length;l++){const u=o[l],d=u.getAttribute("data-ln-attr").split(",");for(let h=0;h<d.length;h++){const g=d[h].trim().split(":");if(g.length!==2)continue;const b=g[0].trim(),C=g[1].trim();t[C]!=null&&u.setAttribute(b,t[C])}}const r=e.querySelectorAll("[data-ln-show]");for(let l=0;l<r.length;l++){const u=r[l],d=u.getAttribute("data-ln-show");d in t&&u.classList.toggle("hidden",!t[d])}const i=e.querySelectorAll("[data-ln-class]");for(let l=0;l<i.length;l++){const u=i[l],d=u.getAttribute("data-ln-class").split(",");for(let h=0;h<d.length;h++){const g=d[h].trim().split(":");if(g.length!==2)continue;const b=g[0].trim(),C=g[1].trim();C in t&&u.classList.toggle(b,!!t[C])}}return e}function te(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let o=0;o<n.length;o++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[o],t??null),n[o].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)N(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function U(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const i=n.currentNode;i.textContent.indexOf("{{")!==-1&&(i.textContent=i.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(l,u){return t[u]!==void 0?t[u]:""}))}const o=function(i,l){return t[l]!==void 0?t[l]:""},r=Array.from(e.querySelectorAll("*"));e.nodeType===1&&r.push(e);for(let i=0;i<r.length;i++){const l=r[i],u=l.attributes;for(let d=0;d<u.length;d++){const h=u[d];h.value.indexOf("{{")!==-1&&l.setAttribute(h.name,h.value.replace(/\{\{\s*(\w+)\s*\}\}/g,o))}}return e}function ne(e,t,n,o,r,i){const l={};for(let d=0;d<e.children.length;d++){const h=e.children[d],g=h.getAttribute("data-ln-render-key");g&&(l[g]=h)}const u=document.createDocumentFragment();for(let d=0;d<t.length;d++){const h=t[d],g=String(o(h));let b=l[g];if(b)r(b,h,d);else{const C=ee(n,i);if(!C||(U(C,h),b=C.firstElementChild,!b))continue;b.setAttribute("data-ln-render-key",g),r(b,h,d)}u.appendChild(b)}e.textContent="",e.appendChild(u)}function oe(e,t){const n={},o=e.querySelectorAll("["+t+"]");for(let r=0;r<o.length;r++)n[o[r].getAttribute(t)]=o[r].textContent,o[r].remove();return n}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=U,window.lnCore.fill=N,window.lnCore.lnFill=te,window.lnCore.renderList=ne);function V(e,t,n,o,r){if(!e||t===null)return!0;const i=o||"ln-component",l=e.type;if(l==="trigger"||l==="marker"||l==="string"||l==="list"||!l||t===""&&e.fallback!==void 0)return!0;const u=d=>{r?console.warn(d,r):console.warn(d)};if(l==="boolean"){const d=t.trim().toLowerCase();return d!==""&&d!=="true"&&d!=="false"&&d!=="1"&&d!=="0"?(u(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(l==="enum"){const d=e.values||[];return d.includes(t)?!0:(u(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${d.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(l==="integer"){if(!/^-?\d+$/.test(t))return u(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const d=parseInt(t,10);return e.min!==void 0&&d<e.min?(u(`[${i}] Value ${d} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&d>e.max?(u(`[${i}] Value ${d} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(l==="float")return isNaN(Number(t))?(u(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(l==="json")try{return JSON.parse(t),!0}catch(d){return u(`[${i}] Invalid JSON for attribute "${n}": ${d.message}. Fallback: ${e.fallback}.`),!1}return!0}function re(e){const t={};for(const n in e){const o=e[n];o&&o.effect&&(t[n]=o.effect)}return Object.keys(t).length?t:null}function ie(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function T(e,t,n,o){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",l=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&l.push(e);for(const u of l)if(!u[n]){window.lnCore._persistSink&&u.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(u,t);try{u[n]=new o(u)}catch(d){console.error("["+n+"] init failed",u,d)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function le(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function $(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function H(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function se(e){const t=H(),n=e.observed||[];for(let o=0;o<n.length;o++){const r=n[o];t.byAttr.has(r)||t.byAttr.set(r,[]),t.byAttr.get(r).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const o in e.attributes){t.byDeclaredAttr.has(o)||t.byDeclaredAttr.set(o,[]);const r=t.byDeclaredAttr.get(o);r.some(i=>i.componentTag===e.componentTag)||r.push({spec:e.attributes[o],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const o=ie(e);if(o===null)t.reactiveWildcard.push(e);else for(const r of o)t.byReactive.has(r)||t.byReactive.set(r,[]),t.byReactive.get(r).push(e)}e.persist&&t.persist.push(e)}function K(e,t,n,o){for(let r=0;r<e.length;r++){const i=e[r];if(!t[i.attribute])continue;const l=i.effects&&i.effects[n];l?l(t,n,o):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,o)}}function de(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const o=H(),r=o.byAttr.get(n);if(j()&&o.byDeclaredAttr&&o.byDeclaredAttr.has(n)&&L(t)){const i=o.byDeclaredAttr.get(n),l=t.getAttribute(n);for(let u=0;u<i.length;u++)V(i[u].spec,l,n,i[u].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||r)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=o.byReactive.get(n);i&&K(i,t,n,e.oldValue),o.reactiveWildcard.length&&K(o.reactiveWildcard,t,n,e.oldValue)}if(r)for(let i=0;i<r.length;i++){const l=r[i];if(l.handler){l.handler(t,n,e.oldValue);continue}l.onAttributeChange&&t[l.attribute]?l.onAttributeChange(t,n):(T(t,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(t))}}function ae(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,x(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{de(t[n])}catch(o){console.error("[ln-core] mutation handler failed",t[n].target,o)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function Q(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function ce(e){const t=Q();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const o=t[n];if(o.onSubtreeChange){const r=o.query,i=e.target.nodeType===1?e.target.matches(r)?e.target:e.target.closest(r):e.target.parentElement?e.target.parentElement.closest(r):null;i&&o.onSubtreeChange(i,e)}}for(let n=0;n<e.addedNodes.length;n++){const o=e.addedNodes[n];if(o.nodeType===1)for(let r=0;r<t.length;r++){const i=t[r];T(o,i.selector,i.attribute,i.ComponentFn),i.onInit&&i.onInit(o)}}for(let n=0;n<e.removedNodes.length;n++){const o=e.removedNodes[n];if(o.nodeType===1)for(let r=0;r<t.length;r++){const i=t[r],l=i.query,u=Array.from(o.querySelectorAll(l));o.matches&&o.matches(l)&&u.push(o);for(let d=0;d<u.length;d++){const h=u[d];if(!document.contains(h)){const g=h[i.attribute];if(g&&typeof g.destroy=="function")try{g.destroy()}catch(b){console.error("["+i.attribute+"] destroy failed",h,b)}delete h[i.attribute]}}}}}}function ue(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,x(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const o=t[n];if(o.type==="childList")try{ce(o)}catch(r){console.error("[ln-core] lifecycle handler failed",o.target,r)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function fe(e,t,n,o,r={}){const i=r.extraAttributes||[],l=r.onAttributeChange||null,u=r.onSubtreeChange||null,d=r.onInit||null,h=r.onAttrChange||null,g=r.effects||null,b=r.attributes||null,C=b?re(b):g,F=b?new Set(Object.keys(b)):null,I=r.persist||null;function v(w){const c=w||document.body;if(T(c,e,t,n),b&&j())for(const p in b){const y=b[p],A=Array.from(c.querySelectorAll("["+p+"]"));c.matches&&c.matches("["+p+"]")&&A.push(c);for(let S=0;S<A.length;S++){const E=A[S];L(E)&&V(y,E.getAttribute(p),p,o,E)}}d&&d(c)}const M=[];if(e.indexOf("[")!==-1){const w=/\[([\w-]+)/g;let c;for(;(c=w.exec(e))!==null;)M.push(c[1])}else M.push(e);se({selector:e,attribute:t,componentTag:o,attributes:b,ComponentFn:n,onInit:d,observed:M.concat(i),onAttributeChange:l,onAttrChange:h,effects:C,declared:F,persist:I}),ae();const s=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";Q().push({selector:e,attribute:t,ComponentFn:n,onInit:d,onSubtreeChange:u,query:s}),ue(),window[t]=v;function a(){le()>0?$(function(){v(document.body)}):v(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a):a(),v}function z(e){return(e||"").replace(/^#/,"")}function O(e){const t=e===void 0?location.hash:e,n={},o=z(t);if(!o)return n;const r=o.split("&");for(let i=0;i<r.length;i++){const l=r[i];if(!l)continue;const u=l.indexOf(":"),d=u>-1?l.slice(0,u):l,h=u>-1?l.slice(u+1):"";if(d)try{n[d]=decodeURIComponent(h)}catch{n[d]=h}}return n}function k(e){if(!e)return null;const t=O();return e in t?t[e]:null}function _(e,t){if(!e)return;const n=O();t==null?delete n[e]:n[e]=String(t);const r=Object.keys(n).map(function(i){const l=n[i];return l===""?i:i+":"+encodeURIComponent(l)}).join("&");z(location.hash)!==r&&(location.hash=r)}function G(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function we(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const o=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return o?t?o+"-"+t:o:t||null}function he(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function be(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function ge(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function pe(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),o=e.slice(t+1),r=o?o.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:r}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=O,window.lnCore.hashGet=k,window.lnCore.hashSet=_,window.lnCore.hashLinkClick=G,window.lnCore.resolveHashNamespace=we,window.lnCore.hashSortEncode=he,window.lnCore.hashSortDecode=be,window.lnCore.hashFilterEncode=ge,window.lnCore.hashFilterDecode=pe),(function(){const e="data-ln-ui-coordinator",t="lnUiCoordinator",n="data-ln-ui-coordinator-dict";if(window[t]!==void 0)return;const o={"data-ln-ui-coordinator":{type:"marker",description:"Mounts UI coordinator mediating global hash-routing, modals, and toasts"},"data-ln-ui-coordinator-dict":{type:"string",description:"Dictionary key prefix mapping for translatable UI coordinator messages"}};function r(f){const s={};let a=f;const w=[];for(;a;){const c=a.closest("["+e+"]");if(!c)break;c[t]&&c[t].dict&&w.unshift(c[t].dict),a=c.parentElement}for(const c of w)Object.assign(s,c);return s}function i(f,s){if(s){if(f){const w=f.closest("["+e+"]");if(w){if(w.id===s&&w.hasAttribute("data-ln-modal"))return w;const c=w.querySelector("#"+CSS.escape(s)+'[data-ln-modal], [data-ln-modal="'+s+'"]');if(c)return c}}const a=document.getElementById(s)||document.querySelector('[data-ln-modal="'+s+'"]');if(a)return a}if(f){const a=f.closest("["+e+"]");if(a){if(a.hasAttribute("data-ln-modal"))return a;const c=a.querySelector("[data-ln-modal]");if(c)return c}const w=f.closest("[data-ln-modal]");if(w)return w}return document.querySelector("[data-ln-modal]")}function l(f,s){if(f!=="edit")return"";if(s){const a=s.getAttribute("data-ln-fill-id");if(a)return a}return"edit"}function u(f){if(!f)return;const s=f.querySelectorAll("[data-ln-field]");for(let w=0;w<s.length;w++)s[w].textContent="";const a=f.querySelectorAll("form");for(let w=0;w<a.length;w++)window.lnCore&&typeof window.lnCore.lnFill=="function"?window.lnCore.lnFill(a[w],null):a[w].reset()}document.addEventListener("click",function(f){if(f.ctrlKey||f.metaKey||f.button===1)return;const s=f.target.closest("[data-ln-modal-for]");if(s){const w=s.getAttribute("data-ln-modal-for"),c=i(s,w);if(c&&c.lnModal){f.preventDefault();const p={lnModalFor:!0,lnModalClose:!0,lnModalMode:!0},y={},A=s.dataset;for(const q in A){if(!q.startsWith("lnModal")||p[q])continue;const R=q.slice(7);R&&(y[R.charAt(0).toLowerCase()+R.slice(1)]=A[q])}const S=Object.keys(y).length>0;s.hasAttribute("data-ln-modal-mode")?c.dataset.lnModalMode=s.getAttribute("data-ln-modal-mode"):c.dataset.lnModalMode=S?"edit":"new",S&&window.lnCore&&typeof window.lnCore.lnFill=="function"?window.lnCore.lnFill(c,y):c.dataset.lnModalMode==="new"&&u(c),c.getAttribute("data-ln-modal")==="open"?m(c,"ln-modal:request-close",{}):(c.id&&_(c.id,l(c.dataset.lnModalMode,s)),m(c,"ln-modal:request-open",{}))}return}const a=f.target.closest('a[href^="#"]');if(a){const w=O(a.getAttribute("href"));for(const c in w){const p=document.getElementById(c);if(p&&p.lnModal){if(!G(f))return;_(c,w[c]);return}}}}),document.addEventListener("ln-modal:before-open",function(f){const s=f.target;if(!s||!s.lnModal)return;(s.dataset.lnModalMode||"new")==="new"&&u(s)}),document.addEventListener("ln-modal:open",function(f){const s=f.target;if(!s||!s.lnModal||!s.id)return;let a=k(s.id);a===null&&(a=l(s.dataset.lnModalMode,null),_(s.id,a)),a?(s.dataset.lnModalMode="edit",m(s,"ln-fill:request",{id:a})):(s.dataset.lnModalMode="new",u(s))});let d=!1;function h(){if(!d){d=!0;try{const f=document.querySelectorAll("[data-ln-modal][id]");for(let s=0;s<f.length;s++){const a=f[s];if(!a.lnModal)continue;const w=a.id,c=k(w),p=c!==null,y=a.lnModal.isOpen;if(p){const A=c?"edit":"new";a.dataset.lnModalMode=A,y?c?m(a,"ln-fill:request",{id:c}):u(a):m(a,"ln-modal:request-open",{})}else y&&m(a,"ln-modal:request-close",{})}}finally{d=!1}}}function g(){const f=document.querySelectorAll('[data-ln-modal="open"][id]');for(let s=0;s<f.length;s++){const a=f[s];a.lnModal&&k(a.id)===null&&_(a.id,l(a.dataset.lnModalMode,null))}}window.addEventListener("hashchange",h);function b(){g(),h()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){$(b)}):$(b);function C(f){const a=(f.detail||{}).data;if(a&&a.message){const c=a.message;m(window,"ln-toast:enqueue",{type:c.type||"success",title:c.title||"",message:c.body||""})}const w=f.target.closest("[data-ln-modal]");w&&w.lnModal&&(w.id&&_(w.id,null),m(w,"ln-modal:request-close",{}),u(w))}function F(f){const s=f.detail||{},a=s.data,w=s.status||0,c=r(f.target);if(a&&a.message){const p=a.message;m(window,"ln-toast:enqueue",{type:p.type||"error",title:p.title||"",message:p.body||""})}else w===0?m(window,"ln-toast:enqueue",{type:"error",title:c["network-error-title"]||"",message:c["network-error"]||"Network error"}):m(window,"ln-toast:enqueue",{type:"error",title:c["server-error-title"]||"",message:c["server-error"]||"Server error"})}document.addEventListener("ln-ajax:success",C),document.addEventListener("ln-ajax:error",F);function I(f){const s=f.detail||{},a=r(f.target),w=s.message||(s.reason==="max-size"?a["upload-max-size"]||"File is too large":s.reason==="max-files"?a["upload-max-files"]||"Maximum file count exceeded":a["upload-invalid-type"]||"This file type is not allowed"),c=a["upload-invalid-title"]||"Invalid File";m(window,"ln-toast:enqueue",{type:"error",title:c,message:w})}function v(f){const s=f.detail||{},a=r(f.target),w=s.message||a["upload-failed"]||"Failed to upload file",c=a["upload-error-title"]||"Upload Error";m(window,"ln-toast:enqueue",{type:"error",title:c,message:w})}document.addEventListener("ln-upload:invalid",I),document.addEventListener("ln-upload:error",v),document.addEventListener("ln-modal:close",function(f){const s=f.target;!s||!s.lnModal||(s.id&&k(s.id)!==null&&_(s.id,null),s.dataset.lnModalMode==="new"&&u(s))});function M(f){return this.dom=f,this.dict=oe(f,n),this}M.prototype.destroy=function(){this.dom[t]&&(this.dict={},delete this.dom[t])},fe(e,t,M,"ln-ui-coordinator",{attributes:o})})()})();
1
+ (function(){"use strict";function L(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function y(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let r=null;for(let s=1;s<t.length;s++)if(t[s]&&t[s].nodeType===1){r=t[s];break}if(!y(r))return}e.apply(console,t)}}function h(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function p(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){p(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const $={};function N(e,t){$[e]=t}function H(e){return $[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=N,window.lnCore.getDataMapper=H);function V(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,p(function(){new MutationObserver(function(){h(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const D={};function U(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];D[n]=t}function K(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return D[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=U,window.lnCore.getLocaleFallback=K,window.lnCore.ensureLocaleObserver=V);const A={};function Q(e,t){A[e]||(A[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=A[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function q(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let i=0;i<n.length;i++){const d=n[i],l=d.getAttribute("data-ln-field");t[l]!=null&&(d.textContent=t[l])}const r=e.querySelectorAll("[data-ln-attr]");for(let i=0;i<r.length;i++){const d=r[i],l=d.getAttribute("data-ln-attr").split(",");for(let c=0;c<l.length;c++){const a=l[c].trim().split(":");if(a.length!==2)continue;const u=a[0].trim(),f=a[1].trim();t[f]!=null&&d.setAttribute(u,t[f])}}const s=e.querySelectorAll("[data-ln-show]");for(let i=0;i<s.length;i++){const d=s[i],l=d.getAttribute("data-ln-show");l in t&&d.classList.toggle("hidden",!t[l])}const o=e.querySelectorAll("[data-ln-class]");for(let i=0;i<o.length;i++){const d=o[i],l=d.getAttribute("data-ln-class").split(",");for(let c=0;c<l.length;c++){const a=l[c].trim().split(":");if(a.length!==2)continue;const u=a[0].trim(),f=a[1].trim();f in t&&d.classList.toggle(u,!!t[f])}}return e}function G(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)q(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function I(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const o=n.currentNode;o.textContent.indexOf("{{")!==-1&&(o.textContent=o.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(i,d){return t[d]!==void 0?t[d]:""}))}const r=function(o,i){return t[i]!==void 0?t[i]:""},s=Array.from(e.querySelectorAll("*"));e.nodeType===1&&s.push(e);for(let o=0;o<s.length;o++){const i=s[o],d=i.attributes;for(let l=0;l<d.length;l++){const c=d[l];c.value.indexOf("{{")!==-1&&i.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function J(e,t,n,r,s,o){const i={};for(let l=0;l<e.children.length;l++){const c=e.children[l],a=c.getAttribute("data-ln-render-key");a&&(i[a]=c)}const d=document.createDocumentFragment();for(let l=0;l<t.length;l++){const c=t[l],a=String(r(c));let u=i[a];if(u)s(u,c,l);else{const f=Q(n,o);if(!f||(I(f,c),u=f.firstElementChild,!u))continue;u.setAttribute("data-ln-render-key",a),s(u,c,l)}d.appendChild(u)}e.textContent="",e.appendChild(d)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=I,window.lnCore.fill=q,window.lnCore.lnFill=G,window.lnCore.renderList=J);function M(e,t,n,r,s){if(!e||t===null)return!0;const o=r||"ln-component",i=e.type;if(i==="trigger"||i==="marker"||i==="string"||i==="list"||!i||t===""&&e.fallback!==void 0)return!0;const d=l=>{s?console.warn(l,s):console.warn(l)};if(i==="boolean"){const l=t.trim().toLowerCase();return l!==""&&l!=="true"&&l!=="false"&&l!=="1"&&l!=="0"?(d(`[${o}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(i==="enum"){const l=e.values||[];return l.includes(t)?!0:(d(`[${o}] Invalid value "${t}" for attribute "${n}". Allowed: ${l.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(i==="integer"){if(!/^-?\d+$/.test(t))return d(`[${o}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const l=parseInt(t,10);return e.min!==void 0&&l<e.min?(d(`[${o}] Value ${l} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&l>e.max?(d(`[${o}] Value ${l} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(i==="float")return isNaN(Number(t))?(d(`[${o}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(i==="json")try{return JSON.parse(t),!0}catch(l){return d(`[${o}] Invalid JSON for attribute "${n}": ${l.message}. Fallback: ${e.fallback}.`),!1}return!0}function P(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function X(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function _(e,t,n,r){if(e.nodeType!==1)return;const o=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",i=Array.from(e.querySelectorAll(o));e.matches&&e.matches(o)&&i.push(e);for(const d of i)if(!d[n]){window.lnCore._persistSink&&d.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(d,t);try{d[n]=new r(d)}catch(l){console.error("["+n+"] init failed",d,l)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function z(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function Y(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function T(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function Z(e){const t=T(),n=e.observed||[];for(let r=0;r<n.length;r++){const s=n[r];t.byAttr.has(s)||t.byAttr.set(s,[]),t.byAttr.get(s).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const s=t.byDeclaredAttr.get(r);s.some(o=>o.componentTag===e.componentTag)||s.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=X(e);if(r===null)t.reactiveWildcard.push(e);else for(const s of r)t.byReactive.has(s)||t.byReactive.set(s,[]),t.byReactive.get(s).push(e)}e.persist&&t.persist.push(e)}function R(e,t,n,r){for(let s=0;s<e.length;s++){const o=e[s];if(!t[o.attribute])continue;const i=o.effects&&o.effects[n];i?i(t,n,r):o.onAttrChange&&(!o.declared||o.declared.has(n))&&o.onAttrChange(t,n,r)}}function ee(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=T(),s=r.byAttr.get(n);if(L()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&y(t)){const o=r.byDeclaredAttr.get(n),i=t.getAttribute(n);for(let d=0;d<o.length;d++)M(o[d].spec,i,n,o[d].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||s)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const o=r.byReactive.get(n);o&&R(o,t,n,e.oldValue),r.reactiveWildcard.length&&R(r.reactiveWildcard,t,n,e.oldValue)}if(s)for(let o=0;o<s.length;o++){const i=s[o];if(i.handler){i.handler(t,n,e.oldValue);continue}i.onAttributeChange&&t[i.attribute]?i.onAttributeChange(t,n):(_(t,i.selector,i.attribute,i.ComponentFn),i.onInit&&i.onInit(t))}}function te(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,p(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{ee(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function B(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function ne(e){const t=B();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const s=r.query,o=e.target.nodeType===1?e.target.matches(s)?e.target:e.target.closest(s):e.target.parentElement?e.target.parentElement.closest(s):null;o&&r.onSubtreeChange(o,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let s=0;s<t.length;s++){const o=t[s];_(r,o.selector,o.attribute,o.ComponentFn),o.onInit&&o.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let s=0;s<t.length;s++){const o=t[s],i=o.query,d=Array.from(r.querySelectorAll(i));r.matches&&r.matches(i)&&d.push(r);for(let l=0;l<d.length;l++){const c=d[l];if(!document.contains(c)){const a=c[o.attribute];if(a&&typeof a.destroy=="function")try{a.destroy()}catch(u){console.error("["+o.attribute+"] destroy failed",c,u)}delete c[o.attribute]}}}}}}function oe(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,p(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{ne(r)}catch(s){console.error("[ln-core] lifecycle handler failed",r.target,s)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function re(e,t,n,r,s={}){const o=s.extraAttributes||[],i=s.onAttributeChange||null,d=s.onSubtreeChange||null,l=s.onInit||null,c=s.onAttrChange||null,a=s.effects||null,u=s.attributes||null,f=u?P(u):a,ae=u?new Set(Object.keys(u)):null,ue=s.persist||null;function m(k){const w=k||document.body;if(_(w,e,t,n),u&&L())for(const g in u){const we=u[g],O=Array.from(w.querySelectorAll("["+g+"]"));w.matches&&w.matches("["+g+"]")&&O.push(w);for(let E=0;E<O.length;E++){const x=O[E];y(x)&&M(we,x.getAttribute(g),g,r,x)}}l&&l(w)}const S=[];if(e.indexOf("[")!==-1){const k=/\[([\w-]+)/g;let w;for(;(w=k.exec(e))!==null;)S.push(w[1])}else S.push(e);Z({selector:e,attribute:t,componentTag:r,attributes:u,ComponentFn:n,onInit:l,observed:S.concat(o),onAttributeChange:i,onAttrChange:c,effects:f,declared:ae,persist:ue}),te();const fe=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";B().push({selector:e,attribute:t,ComponentFn:n,onInit:l,onSubtreeChange:d,query:fe}),oe(),window[t]=m;function W(){z()>0?Y(function(){m(document.body)}):m(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",W):W(),m}function F(e){return(e||"").replace(/^#/,"")}function b(e){const t=e===void 0?location.hash:e,n={},r=F(t);if(!r)return n;const s=r.split("&");for(let o=0;o<s.length;o++){const i=s[o];if(!i)continue;const d=i.indexOf(":"),l=d>-1?i.slice(0,d):i,c=d>-1?i.slice(d+1):"";if(l)try{n[l]=decodeURIComponent(c)}catch{n[l]=c}}return n}function v(e){if(!e)return null;const t=b();return e in t?t[e]:null}function C(e,t){if(!e)return;const n=b();t==null?delete n[e]:n[e]=String(t);const s=Object.keys(n).map(function(o){const i=n[o];return i===""?o:o+":"+encodeURIComponent(i)}).join("&");F(location.hash)!==s&&(location.hash=s)}function j(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ie(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function le(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function se(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function de(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function ce(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),s=r?r.split(",").map(function(o){try{return decodeURIComponent(o)}catch{return o}}).filter(Boolean):[];return{key:n,values:s}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=b,window.lnCore.hashGet=v,window.lnCore.hashSet=C,window.lnCore.hashLinkClick=j,window.lnCore.resolveHashNamespace=ie,window.lnCore.hashSortEncode=le,window.lnCore.hashSortDecode=se,window.lnCore.hashFilterEncode=de,window.lnCore.hashFilterDecode=ce),(function(){const e="data-ln-ui-coordinator",t="lnUiCoordinator";if(window[t]!==void 0)return;const n={"data-ln-ui-coordinator":{type:"marker",description:"Mounts UI coordinator mediating global hash-routing, modals, and toasts"}};document.addEventListener("click",function(o){if(o.ctrlKey||o.metaKey||o.button===1)return;const i=o.target.closest('a[href^="#"]');if(!i)return;const d=i.getAttribute("href"),l=b(d);for(const c in l){const a=document.getElementById(c);if(a&&a.lnModal){if(!j(o))return;C(c,l[c]),a.lnModal.isOpen||h(a,"ln-modal:request-open",{});return}}});function r(){const o=location.hash;if(!o)return;const i=b(o);for(const d in i){const l=document.getElementById(d);l&&l.lnModal&&!l.lnModal.isOpen&&h(l,"ln-modal:request-open",{})}}window.addEventListener("hashchange",r),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",r):r(),document.addEventListener("ln-modal:close",function(o){const i=o.target;i&&i.id&&v(i.id)!==null&&C(i.id,null)}),document.addEventListener("ln-ajax:success",function(o){const d=(o.detail||{}).data;if(d&&d.message){const c=d.message;h(window,"ln-toast:enqueue",{type:c.type||"success",title:c.title||"",message:typeof c=="string"?c:c.body||""})}const l=o.target.closest("[data-ln-modal]");l&&l.lnModal&&(l.id&&v(l.id)!==null&&C(l.id,null),h(l,"ln-modal:request-close",{}))}),document.addEventListener("ln-ajax:error",function(o){const d=(o.detail||{}).data;if(d&&d.message){const l=d.message;h(window,"ln-toast:enqueue",{type:l.type||"error",title:l.title||"",message:typeof l=="string"?l:l.body||""})}}),document.addEventListener("ln-upload:error",function(o){const i=o.detail||{};i.message&&h(window,"ln-toast:enqueue",{type:"error",title:"",message:i.message})});function s(o){return this.dom=o,this}s.prototype.destroy=function(){delete this.dom[t]},re(e,t,s,"ln-ui-coordinator",{attributes:n})})()})();
@@ -9,52 +9,20 @@
9
9
  "ln-ui-coordinator-dev.scss"
10
10
  ]
11
11
  },
12
- "data-ln-field": {
13
- "direction": "author",
14
- "sources": [
15
- "src/ln-ui-coordinator.js"
16
- ]
17
- },
18
- "data-ln-fill-id": {
19
- "direction": "author",
20
- "sources": [
21
- "src/ln-ui-coordinator.js"
22
- ]
23
- },
24
12
  "data-ln-modal": {
25
13
  "direction": "author",
26
14
  "sources": [
27
15
  "src/ln-ui-coordinator.js"
28
16
  ]
29
17
  },
30
- "data-ln-modal-for": {
31
- "direction": "author",
32
- "sources": [
33
- "src/ln-ui-coordinator.js"
34
- ]
35
- },
36
- "data-ln-modal-mode": {
37
- "direction": "both",
38
- "sources": [
39
- "src/ln-ui-coordinator.js"
40
- ]
41
- },
42
18
  "data-ln-ui-coordinator": {
43
- "direction": "author",
19
+ "direction": null,
44
20
  "sources": [
45
21
  "ln-ui-coordinator-dev.scss",
46
22
  "src/ln-ui-coordinator.js"
47
23
  ],
48
24
  "type": "marker",
49
25
  "description": "Mounts UI coordinator mediating global hash-routing, modals, and toasts"
50
- },
51
- "data-ln-ui-coordinator-dict": {
52
- "direction": null,
53
- "sources": [
54
- "src/ln-ui-coordinator.js"
55
- ],
56
- "type": "string",
57
- "description": "Dictionary key prefix mapping for translatable UI coordinator messages"
58
26
  }
59
27
  }
60
28
  }