@livenetworks/ashlar 1.4.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -177
- package/js/COMPONENTS.md +55 -49
- package/js/index.js +22 -5
- package/js/ln-accordion/README.md +186 -137
- package/js/ln-accordion/ln-accordion.js +1 -1
- package/js/ln-ajax/README.md +73 -4
- package/js/ln-ajax/ln-ajax.js +1 -1
- package/js/ln-ajax/src/ln-ajax.js +13 -13
- package/js/ln-api-connector/README.md +261 -150
- package/js/ln-api-connector/ln-api-connector.js +1 -1
- package/js/ln-api-connector/src/ln-api-connector.js +368 -265
- package/js/ln-api-queue/README.md +219 -0
- package/js/ln-api-queue/ln-api-queue.js +1 -0
- package/js/ln-api-queue/src/ln-api-queue.js +320 -0
- package/js/ln-api-queue/src/queue-storage.js +455 -0
- package/js/ln-autoresize/README.md +47 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -1
- package/js/ln-autosave/README.md +33 -1
- package/js/ln-autosave/ln-autosave.js +1 -1
- package/js/ln-autosave/src/ln-autosave.js +4 -4
- package/js/ln-circular-progress/README.md +27 -13
- package/js/ln-circular-progress/ln-circular-progress.js +1 -1
- package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
- package/js/ln-confirm/README.md +162 -86
- package/js/ln-confirm/_ln-confirm.scss +11 -0
- package/js/ln-confirm/ln-confirm.js +1 -1
- package/js/ln-confirm/src/ln-confirm.js +77 -41
- package/js/ln-core/README.md +896 -0
- package/js/ln-core/hash.js +92 -0
- package/js/ln-core/helpers.js +337 -8
- package/js/ln-core/index.js +4 -2
- package/js/ln-core/persist.js +29 -3
- package/js/ln-core/positioning.js +1 -35
- package/js/ln-core/window-cache.js +213 -0
- package/js/ln-couchdb-connector/README.md +177 -156
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
- package/js/ln-data-coordinator/README.md +536 -165
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
- package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
- package/js/ln-data-store/README.md +241 -94
- package/js/ln-data-store/ln-data-store.js +1 -1
- package/js/ln-data-store/src/ln-data-store.js +694 -699
- package/js/ln-date/README.md +39 -1
- package/js/ln-date/ln-date-dev.scss +14 -0
- package/js/ln-date/ln-date.js +1 -1
- package/js/ln-date/src/ln-date.js +236 -178
- package/js/ln-debug/README.md +58 -0
- package/js/ln-debug/ln-debug.js +1 -0
- package/js/ln-debug/src/ln-debug.js +19 -0
- package/js/ln-dropdown/README.md +31 -8
- package/js/ln-dropdown/ln-dropdown.js +1 -1
- package/js/ln-dropdown/src/ln-dropdown.js +30 -13
- package/js/ln-editor/README.md +274 -0
- package/js/ln-editor/ln-editor.js +1 -0
- package/js/ln-editor/ln-editor.scss +43 -0
- package/js/ln-editor/src/ln-editor.js +619 -0
- package/js/ln-external-links/README.md +44 -5
- package/js/ln-external-links/ln-external-links.js +1 -1
- package/js/ln-fill/README.md +199 -0
- package/js/ln-fill/ln-fill.js +1 -0
- package/js/ln-fill/src/ln-fill.js +87 -0
- package/js/ln-filter/README.md +189 -99
- package/js/ln-filter/ln-filter-dev.scss +8 -0
- package/js/ln-filter/ln-filter.js +1 -1
- package/js/ln-filter/src/ln-filter.js +22 -7
- package/js/ln-form/README.md +212 -49
- package/js/ln-form/ln-form.js +1 -1
- package/js/ln-form/src/ln-form.js +99 -199
- package/js/ln-http/README.md +125 -89
- package/js/ln-http/ln-http.js +1 -1
- package/js/ln-http/src/ln-http.js +32 -13
- package/js/ln-icon/README.md +130 -0
- package/js/ln-icon/ln-icon.js +1 -0
- package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
- package/js/ln-link/README.md +47 -9
- package/js/ln-link/ln-link.js +1 -1
- package/js/ln-link/src/ln-link.js +22 -6
- package/js/ln-list/README.md +129 -0
- package/js/ln-list/ln-list.js +1 -0
- package/js/ln-list/ln-list.scss +28 -0
- package/js/ln-list/src/ln-list.js +1216 -0
- package/js/ln-modal/README.md +185 -154
- package/js/ln-modal/ln-modal-dev.scss +13 -0
- package/js/ln-modal/ln-modal.js +1 -1
- package/js/ln-modal/ln-modal.scss +28 -4
- package/js/ln-modal/src/ln-modal.js +137 -201
- package/js/ln-modal-coordinator/README.md +99 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
- package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
- package/js/ln-nav/README.md +46 -3
- package/js/ln-nav/ln-nav.js +1 -1
- package/js/ln-nav/src/ln-nav.js +92 -127
- package/js/ln-number/README.md +28 -1
- package/js/ln-number/ln-number-dev.scss +13 -0
- package/js/ln-number/ln-number.js +1 -1
- package/js/ln-number/src/ln-number.js +443 -350
- package/js/ln-options/README.md +66 -0
- package/js/ln-options/ln-options.js +1 -0
- package/js/ln-options/src/ln-options.js +74 -0
- package/js/ln-popover/README.md +40 -5
- package/js/ln-popover/ln-popover.js +1 -1
- package/js/ln-popover/src/ln-popover.js +38 -11
- package/js/ln-progress/README.md +413 -442
- package/js/ln-progress/ln-progress.js +1 -1
- package/js/ln-progress/src/ln-progress.js +98 -150
- package/js/ln-router/README.md +432 -0
- package/js/ln-router/ln-router.js +1 -0
- package/js/ln-router/src/ln-router.js +539 -0
- package/js/ln-search/README.md +158 -83
- package/js/ln-search/ln-search-dev.scss +8 -0
- package/js/ln-search/ln-search.js +1 -1
- package/js/ln-search/src/ln-search.js +12 -9
- package/js/ln-slug/README.md +110 -0
- package/js/ln-slug/ln-slug.js +1 -0
- package/js/ln-slug/src/ln-slug.js +74 -0
- package/js/ln-sort/README.md +170 -0
- package/js/ln-sort/ln-sort-dev.scss +8 -0
- package/js/ln-sort/ln-sort.js +1 -0
- package/js/ln-sort/src/ln-sort.js +178 -0
- package/js/ln-sortable/README.md +133 -95
- package/js/ln-sortable/ln-sortable.js +1 -1
- package/js/ln-stat/README.md +55 -0
- package/js/ln-stat/ln-stat.js +1 -0
- package/js/ln-stat/src/ln-stat.js +55 -0
- package/js/ln-table/README.md +285 -101
- package/js/ln-table/ln-table-dev.scss +17 -0
- package/js/ln-table/ln-table.js +1 -1
- package/js/ln-table/ln-table.scss +1 -1
- package/js/ln-table/src/ln-table.js +1493 -473
- package/js/ln-table-coordinator/README.md +73 -0
- package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
- package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
- package/js/ln-tabs/README.md +221 -137
- package/js/ln-tabs/ln-tabs-dev.scss +18 -0
- package/js/ln-tabs/ln-tabs.js +1 -1
- package/js/ln-tabs/src/ln-tabs.js +41 -24
- package/js/ln-time/README.md +14 -0
- package/js/ln-time/ln-time.js +1 -1
- package/js/ln-time/src/ln-time.js +223 -192
- package/js/ln-toast/README.md +178 -122
- package/js/ln-toast/ln-toast.js +1 -15
- package/js/ln-toast/src/ln-toast.js +232 -210
- package/js/ln-toast/template.html +16 -12
- package/js/ln-toggle/README.md +61 -2
- package/js/ln-toggle/ln-toggle-dev.scss +13 -0
- package/js/ln-toggle/ln-toggle.js +1 -1
- package/js/ln-toggle/src/ln-toggle.js +56 -43
- package/js/ln-tooltip/README.md +95 -58
- package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -1
- package/js/ln-tooltip/src/ln-tooltip.js +45 -6
- package/js/ln-translations/README.md +118 -96
- package/js/ln-translations/ln-translations.js +1 -1
- package/js/ln-translations/src/ln-translations.js +10 -5
- package/js/ln-upload/README.md +85 -10
- package/js/ln-upload/ln-upload.js +1 -1
- package/js/ln-upload/src/ln-upload.js +26 -10
- package/js/ln-validate/README.md +72 -0
- package/js/ln-validate/ln-validate-dev.scss +15 -0
- package/js/ln-validate/ln-validate.js +1 -1
- package/js/ln-validate/src/ln-validate.js +64 -0
- package/package.json +61 -55
- package/scss/base/_global.scss +2 -2
- package/scss/base/_typography.scss +1 -2
- package/scss/components/_date.scss +1 -0
- package/scss/components/_dropdown.scss +0 -5
- package/scss/components/_editor.scss +5 -0
- package/scss/components/_filter.scss +26 -0
- package/scss/components/_form.scss +7 -0
- package/scss/components/_ln-table.scss +14 -5
- package/scss/components/_sort.scss +10 -0
- package/scss/components/_table.scss +68 -1
- package/scss/components/_toast.scss +68 -41
- package/scss/components/_toggle.scss +16 -0
- package/scss/config/_density.scss +205 -117
- package/scss/config/_icons.scss +10 -9
- package/scss/config/_tokens.scss +69 -47
- package/scss/config/mixins/_accordion.scss +2 -7
- package/scss/config/mixins/_alert.scss +4 -4
- package/scss/config/mixins/_breadcrumbs.scss +2 -2
- package/scss/config/mixins/_btn.scss +1 -1
- package/scss/config/mixins/_card.scss +89 -14
- package/scss/config/mixins/_collapsible.scss +3 -0
- package/scss/config/mixins/_colors.scss +1 -1
- package/scss/config/mixins/_diagnostics.scss +48 -0
- package/scss/config/mixins/_display.scss +1 -0
- package/scss/config/mixins/_dropdown.scss +3 -5
- package/scss/config/mixins/_editor.scss +124 -0
- package/scss/config/mixins/_empty-state.scss +7 -0
- package/scss/config/mixins/_filter.scss +47 -0
- package/scss/config/mixins/_footer.scss +1 -2
- package/scss/config/mixins/_form.scss +207 -21
- package/scss/config/mixins/_index.scss +4 -1
- package/scss/config/mixins/_ln-table.scss +51 -93
- package/scss/config/mixins/_modal.scss +149 -66
- package/scss/config/mixins/_nav.scss +3 -4
- package/scss/config/mixins/_popover.scss +39 -0
- package/scss/config/mixins/_sidebar.scss +1 -1
- package/scss/config/mixins/_sort.scss +42 -0
- package/scss/config/mixins/_table.scss +232 -38
- package/scss/config/mixins/_toast.scss +179 -152
- package/scss/config/mixins/_tooltip.scss +17 -7
- package/scss/config/mixins/_typography.scss +4 -2
- package/scss/ln-ashlar-dev.scss +39 -0
- package/scss/ln-ashlar.scss +8 -2
- package/js/ln-data-table/README.md +0 -110
- package/js/ln-data-table/ln-data-table.js +0 -1
- package/js/ln-data-table/ln-data-table.scss +0 -10
- package/js/ln-data-table/src/ln-data-table.js +0 -1103
- package/js/ln-icons/README.md +0 -88
- package/js/ln-icons/ln-icons.js +0 -1
- package/js/ln-table/ln-table-sort.js +0 -1
- package/js/ln-table/src/ln-table-sort.js +0 -168
- package/scss/components/_data-table.scss +0 -83
- package/scss/config/mixins/_data-table.scss +0 -346
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# ln-options
|
|
2
|
+
|
|
3
|
+
Populates a `<select>` element with records from an `ln-data-store` via the `ln-data-coordinator` binder. Opt-in: add `data-ln-options="<storeName>"` to any `<select>` inside (or reachable by) a coordinator subtree.
|
|
4
|
+
|
|
5
|
+
No `<template>` needed — ln-options writes `<option>` elements directly (value + label only).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Attributes
|
|
10
|
+
|
|
11
|
+
| Attribute | Element | Description |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `data-ln-options="<storeName>"` | `<select>` | Store name to bind. Must match the coordinator's child store name. |
|
|
14
|
+
| `data-ln-options-value="<field>"` | `<select>` | Record field used as `<option value>`. Default: `id`. |
|
|
15
|
+
| `data-ln-options-label="<field>"` | `<select>` | Record field used as `<option>` text. Default: `name`. May be a presenter-computed field. |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Events
|
|
20
|
+
|
|
21
|
+
### Dispatched
|
|
22
|
+
- `ln-options:request-data` `{ options: storeName }` — fired on init (bubbles to coordinator).
|
|
23
|
+
|
|
24
|
+
### Received
|
|
25
|
+
- `ln-options:set-data` `{ data }` — coordinator delivers this; component rebuilds options and restores previous selection.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<div data-ln-data-coordinator>
|
|
33
|
+
<div data-ln-data-store="tenants"></div>
|
|
34
|
+
<div data-ln-api-connector data-ln-api-endpoint="/api/tenants"></div>
|
|
35
|
+
|
|
36
|
+
<select data-ln-options="tenants"
|
|
37
|
+
data-ln-options-value="id"
|
|
38
|
+
data-ln-options-label="name">
|
|
39
|
+
<option value="">All tenants</option>
|
|
40
|
+
</select>
|
|
41
|
+
</div>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The placeholder `<option value="">` is preserved across refreshes. The previous selection is restored if it still exists in the refreshed data.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- ln-options never imports or reads `ln-data-store` directly — it is a pure event client.
|
|
51
|
+
- The coordinator re-serves `ln-options:set-data` on every store mutation (`ln-data-store:ready`, `created`, `updated`, `deleted`, `synced`).
|
|
52
|
+
- `data-ln-options-label` may reference a presenter-computed field (e.g. `full_name`) because the coordinator delivers already-decorated records from `store.getAll({})`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 🔧 Internals
|
|
57
|
+
|
|
58
|
+
Source: `js/ln-options/src/ln-options.js`. Pure event client — never imports or reads `ln-data-store` directly.
|
|
59
|
+
|
|
60
|
+
### Coordinator wiring
|
|
61
|
+
|
|
62
|
+
The coordinator's `_serveOptions` handles `ln-options:request-data`: reads `data-ln-options` off the dispatching element, guards with `_ownsStore(name)`, calls `store.getAll({})` (no sort/filter/pagination — options always show the full set), and dispatches `ln-options:set-data { data }` back on the element. On every store mutation (`ready`, `created`, `updated`, `deleted`, `synced`), the coordinator's `_refreshAll()` re-serves every bound `<select>`.
|
|
63
|
+
|
|
64
|
+
### `<option>` creation
|
|
65
|
+
|
|
66
|
+
The one sanctioned `document.createElement` case in the library (mirrors `ln-table`'s select-all checkbox) — a native `<option>` carries only `value` + text, so a `<template>` would add ceremony for two string assignments.
|
|
@@ -0,0 +1 @@
|
|
|
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 g={};function T(t,e){g[t]||(g[t]=document.querySelector('[data-ln-template="'+t+'"]'));const n=g[t];return n?n.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function q(t,e,n){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:n||{}}))}function A(t,e){if(!t||!e)return t;const n=t.querySelectorAll("[data-ln-field]");for(let o=0;o<n.length;o++){const i=n[o],l=i.getAttribute("data-ln-field");e[l]!=null&&(i.textContent=e[l])}const c=t.querySelectorAll("[data-ln-attr]");for(let o=0;o<c.length;o++){const i=c[o],l=i.getAttribute("data-ln-attr").split(",");for(let d=0;d<l.length;d++){const r=l[d].trim().split(":");if(r.length!==2)continue;const u=r[0].trim(),f=r[1].trim();e[f]!=null&&i.setAttribute(u,e[f])}}const a=t.querySelectorAll("[data-ln-show]");for(let o=0;o<a.length;o++){const i=a[o],l=i.getAttribute("data-ln-show");l in e&&i.classList.toggle("hidden",!e[l])}const s=t.querySelectorAll("[data-ln-class]");for(let o=0;o<s.length;o++){const i=s[o],l=i.getAttribute("data-ln-class").split(",");for(let d=0;d<l.length;d++){const r=l[d].trim().split(":");if(r.length!==2)continue;const u=r[0].trim(),f=r[1].trim();f in e&&i.classList.toggle(u,!!e[f])}}return t}function F(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));const n=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let c=0;c<n.length;c++)n[c].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)A(t.target,t.detail);else{const e=t.target.querySelectorAll("[data-ln-field]");for(let n=0;n<e.length;n++)e[n].textContent=""}})));function x(t,e){if(!t||!e)return t;const n=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const s=n.currentNode;s.textContent.indexOf("{{")!==-1&&(s.textContent=s.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(o,i){return e[i]!==void 0?e[i]:""}))}const c=function(s,o){return e[o]!==void 0?e[o]:""},a=Array.from(t.querySelectorAll("*"));t.nodeType===1&&a.push(t);for(let s=0;s<a.length;s++){const o=a[s],i=o.attributes;for(let l=0;l<i.length;l++){const d=i[l];d.value.indexOf("{{")!==-1&&o.setAttribute(d.name,d.value.replace(/\{\{\s*(\w+)\s*\}\}/g,c))}}return t}function N(t,e,n,c,a,s){const o={};for(let l=0;l<t.children.length;l++){const d=t.children[l],r=d.getAttribute("data-ln-key");r&&(o[r]=d)}const i=document.createDocumentFragment();for(let l=0;l<e.length;l++){const d=e[l],r=String(c(d));let u=o[r];if(u)a(u,d,l);else{const f=T(n,s);if(!f||(x(f,d),u=f.firstElementChild,!u))continue;u.setAttribute("data-ln-key",r),a(u,d,l)}i.appendChild(u)}t.textContent="",t.appendChild(i)}function L(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){L(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function w(t,e,n,c){if(t.nodeType!==1)return;const s=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",o=Array.from(t.querySelectorAll(s));t.matches&&t.matches(s)&&o.push(t);for(const i of o)i[n]||(i[n]=new c(i))}function M(t,e,n,c,a={}){const s=a.extraAttributes||[],o=a.onAttributeChange||null,i=a.onInit||null;function l(d){const r=d||document.body;w(r,t,e,n),i&&i(r)}return L(function(){const d=new MutationObserver(function(u){for(let f=0;f<u.length;f++){const h=u[f];if(h.type==="childList"){for(let m=0;m<h.addedNodes.length;m++){const p=h.addedNodes[m];p.nodeType===1&&(w(p,t,e,n),i&&i(p))}for(let m=0;m<h.removedNodes.length;m++){const p=h.removedNodes[m];if(p.nodeType===1){const k=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",y=Array.from(p.querySelectorAll(k));p.matches&&p.matches(k)&&y.push(p);for(let C=0;C<y.length;C++){const D=y[C];if(!document.contains(D)){const v=D[e];v&&typeof v.destroy=="function"&&v.destroy()}}}}}else h.type==="attributes"&&(o&&h.target[e]?o(h.target,h.attributeName):(w(h.target,t,e,n),i&&i(h.target)))}});let r=[];if(t.indexOf("[")!==-1){const u=/\[([\w-]+)/g;let f;for(;(f=u.exec(t))!==null;)r.push(f[1])}else r.push(t);d.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:r.concat(s)})},c),window[e]=l,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){l(document.body)}):l(document.body),l}const S={};function j(t,e){S[t]=e}function W(t){return S[t]||{ingress:e=>e,egress:e=>e}}const _={};function E(t,e){if(!t||typeof e!="object")return;const n=t.toLowerCase().split("-")[0];_[n]=e}function B(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return _[e]||null}E("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=j,window.lnCore.getDataMapper=W,window.lnCore.registerLocaleFallback=E,window.lnCore.getLocaleFallback=B,window.lnCore.fillTemplate=x,window.lnCore.fill=A,window.lnCore.lnFill=F,window.lnCore.renderList=N);function O(t){return(t||"").replace(/^#/,"")}function b(t){const e=t===void 0?location.hash:t,n={},c=O(e);if(!c)return n;const a=c.split("&");for(let s=0;s<a.length;s++){const o=a[s];if(!o)continue;const i=o.indexOf(":"),l=i>-1?o.slice(0,i):o,d=i>-1?o.slice(i+1):"";if(l)try{n[l]=decodeURIComponent(d)}catch{n[l]=d}}return n}function I(t){if(!t)return null;const e=b();return t in e?e[t]:null}function K(t,e){if(!t)return;const n=b();e==null?delete n[t]:n[t]=String(e);const a=Object.keys(n).map(function(s){const o=n[s];return o===""?s:s+":"+encodeURIComponent(o)}).join("&");O(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=b,window.lnCore.hashGet=I,window.lnCore.hashSet=K,window.lnCore.hashLinkClick=R),(function(){const t="data-ln-options",e="lnOptions";if(window[e]!==void 0)return;function n(c){this.dom=c,this._storeName=c.getAttribute(t),this._valueField=c.getAttribute("data-ln-options-value")||"id",this._labelField=c.getAttribute("data-ln-options-label")||"name";const a=this;return this._onSetData=function(s){a._rebuild(s.detail.data||[])},c.addEventListener("ln-options:set-data",this._onSetData),q(c,"ln-options:request-data",{options:this._storeName}),this}n.prototype._rebuild=function(c){const a=this.dom,s=this._valueField,o=this._labelField,i=a.value,l=a.querySelectorAll("option");for(let r=l.length-1;r>=0;r--)l[r].value!==""&&a.removeChild(l[r]);for(let r=0;r<c.length;r++){const u=c[r],f=document.createElement("option");f.value=String(u[s]),f.textContent=u[o]!=null?u[o]:"",a.appendChild(f)}const d=a.options;for(let r=0;r<d.length;r++)if(d[r].value===i){a.value=i;break}},n.prototype.destroy=function(){this.dom[e]&&(this.dom.removeEventListener("ln-options:set-data",this._onSetData),delete this.dom[e])},M(t,e,n,"ln-options")})()})();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { registerComponent, dispatch } from '../../ln-core';
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
const DOM_SELECTOR = 'data-ln-options';
|
|
5
|
+
const DOM_ATTRIBUTE = 'lnOptions';
|
|
6
|
+
|
|
7
|
+
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
8
|
+
|
|
9
|
+
// ─── Component ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function _component(dom) {
|
|
12
|
+
this.dom = dom;
|
|
13
|
+
this._storeName = dom.getAttribute(DOM_SELECTOR);
|
|
14
|
+
this._valueField = dom.getAttribute('data-ln-options-value') || 'id';
|
|
15
|
+
this._labelField = dom.getAttribute('data-ln-options-label') || 'name';
|
|
16
|
+
|
|
17
|
+
const self = this;
|
|
18
|
+
|
|
19
|
+
this._onSetData = function (e) {
|
|
20
|
+
self._rebuild(e.detail.data || []);
|
|
21
|
+
};
|
|
22
|
+
dom.addEventListener('ln-options:set-data', this._onSetData);
|
|
23
|
+
|
|
24
|
+
// Request initial data from coordinator
|
|
25
|
+
dispatch(dom, 'ln-options:request-data', { options: this._storeName });
|
|
26
|
+
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_component.prototype._rebuild = function (records) {
|
|
31
|
+
const dom = this.dom;
|
|
32
|
+
const valueField = this._valueField;
|
|
33
|
+
const labelField = this._labelField;
|
|
34
|
+
|
|
35
|
+
// Preserve current selection
|
|
36
|
+
const prev = dom.value;
|
|
37
|
+
|
|
38
|
+
// Remove all non-placeholder options (keep option[value=""])
|
|
39
|
+
const existing = dom.querySelectorAll('option');
|
|
40
|
+
for (let i = existing.length - 1; i >= 0; i--) {
|
|
41
|
+
if (existing[i].value !== '') {
|
|
42
|
+
dom.removeChild(existing[i]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Append one option per record
|
|
47
|
+
for (let i = 0; i < records.length; i++) {
|
|
48
|
+
const rec = records[i];
|
|
49
|
+
const opt = document.createElement('option');
|
|
50
|
+
opt.value = String(rec[valueField]);
|
|
51
|
+
opt.textContent = rec[labelField] != null ? rec[labelField] : '';
|
|
52
|
+
dom.appendChild(opt);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Restore previous selection if still available
|
|
56
|
+
const opts = dom.options;
|
|
57
|
+
for (let i = 0; i < opts.length; i++) {
|
|
58
|
+
if (opts[i].value === prev) {
|
|
59
|
+
dom.value = prev;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
_component.prototype.destroy = function () {
|
|
66
|
+
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
67
|
+
this.dom.removeEventListener('ln-options:set-data', this._onSetData);
|
|
68
|
+
delete this.dom[DOM_ATTRIBUTE];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ─── Init ──────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-options');
|
|
74
|
+
})();
|
package/js/ln-popover/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
In `ln-ashlar`, the core design principle is **orthogonality**. Rather than creating a heavy component that bundles state, positioning algorithms, LIFO click stacks, and styles, `ln-popover` splits them cleanly:
|
|
10
10
|
|
|
11
|
-
1. **State & Positioning (JavaScript)**: The `ln-popover` script handles binary `open` / `closed` states in the DOM,
|
|
11
|
+
1. **State & Positioning (JavaScript + native Popover API)**: The `ln-popover` script handles binary `open` / `closed` states in the DOM, bridged to the native Popover API (`popover="manual"`, `showPopover()`/`hidePopover()`) purely for top-layer rendering — no ancestor `overflow`/`z-index` can clip or bury it. Because `manual` mode has no native light-dismiss, the script still owns 100% of the behavior: viewport-boundary flip placement, keyboard focus save/restore, and the LIFO (Last-In, First-Out) ESC dismissal stack.
|
|
12
12
|
2. **Visual Presentation (CSS)**: Visual layouts, background shadows, and borders are handled in Vanilla CSS. The library ships a premium `@mixin popover` to style popover cards.
|
|
13
13
|
3. **Trigger decoupling (HTML)**: Triggers (`data-ln-popover-for="popoverId"`) and popover containers (`id="popoverId"`) are bound purely by ID, allowing the trigger button and the popup card to live anywhere in the document.
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ Triggers and popovers are paired by ID. Wrap content in a `div` carrying the `da
|
|
|
22
22
|
<!-- Trigger anywhere -->
|
|
23
23
|
<button data-ln-popover-for="account-menu">Account</button>
|
|
24
24
|
|
|
25
|
-
<!-- Popover anywhere — role="dialog"
|
|
25
|
+
<!-- Popover anywhere — role="dialog", tabindex="-1", and popover="manual" are injected automatically -->
|
|
26
26
|
<div data-ln-popover id="account-menu">
|
|
27
27
|
<p><strong>user@example.com</strong></p>
|
|
28
28
|
<nav>
|
|
@@ -41,7 +41,7 @@ Triggers and popovers are paired by ID. Wrap content in a `div` carrying the `da
|
|
|
41
41
|
|
|
42
42
|
## 3. Declarative API & State Contract
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The initialized instance is exposed as `dom.lnPopover`, with `open(trigger)`, `close()`, `toggle(trigger)`, and `destroy()` methods, plus `isOpen` and `trigger` properties. **The HTML attribute remains the primary contract** — the instance methods are thin wrappers that ultimately write the same attribute.
|
|
45
45
|
|
|
46
46
|
Triggers, LIFO click outside handlers, viewport edge flips, and custom scripts all change state by writing the active attribute on the popover element:
|
|
47
47
|
|
|
@@ -54,6 +54,10 @@ popover.setAttribute('data-ln-popover', 'open');
|
|
|
54
54
|
// Close the popover
|
|
55
55
|
popover.setAttribute('data-ln-popover', 'closed');
|
|
56
56
|
|
|
57
|
+
// Equivalent imperative calls via the instance
|
|
58
|
+
popover.lnPopover.open();
|
|
59
|
+
popover.lnPopover.close();
|
|
60
|
+
|
|
57
61
|
// Read-only state query
|
|
58
62
|
popover.lnPopover.isOpen; // Returns true/false
|
|
59
63
|
```
|
|
@@ -122,6 +126,37 @@ Opening B from inside A leaves A open. Pressing `ESC` once closes B first, and t
|
|
|
122
126
|
---
|
|
123
127
|
|
|
124
128
|
## Related
|
|
125
|
-
- **[`ln-dropdown`](../ln-dropdown/README.md)** — Menu wrapper adding click-outside/
|
|
129
|
+
- **[`ln-dropdown`](../ln-dropdown/README.md)** — Menu wrapper adding click-outside/top-layer promotion.
|
|
126
130
|
- **[`ln-toggle`](../ln-toggle/README.md)** — Binary disclosure state primitive.
|
|
127
|
-
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🔧 Internals
|
|
135
|
+
|
|
136
|
+
Source: `js/ln-popover/ln-popover.js`.
|
|
137
|
+
|
|
138
|
+
### Open-stack
|
|
139
|
+
|
|
140
|
+
A module-level `openStack` array tracks all currently-open instances. It drives two behaviors: a single `document` `keydown` listener, registered only while the stack is non-empty, pops the top entry and calls `.close()` on it (LIFO Escape); and nested popovers stay independent because each owns its own outside-click listener scoped to its own subtree.
|
|
141
|
+
|
|
142
|
+
### `_applyOpen(trigger)` — after the cancelable `before-open` passes
|
|
143
|
+
|
|
144
|
+
Set `isOpen = true`, store `trigger` (`null` if opened via direct attribute mutation) and `document.activeElement` (for focus restore) → `teleportToBody(dom)` (moves the element to end of `<body>`, storing a restore function, so `position: fixed` coordinates are reliable regardless of ancestor `transform`/`contain`) → `measureHidden(dom)` for dimensions even if `display: none` → `computePlacement(triggerRect, size, preferred, 8)` writes `top`/`left` inline (unavoidable for floating UI) and `data-ln-popover-placement` → `aria-expanded="true"` on trigger → focus the first focusable child, or the container itself → outside-click listener registered one tick later via `setTimeout` (so the opening click itself doesn't immediately close it) → scroll/resize listeners for repositioning while open → push onto `openStack`, ensure ESC listener → dispatch `ln-popover:open`.
|
|
145
|
+
|
|
146
|
+
### `_applyClose()` — mirror of `_applyOpen`
|
|
147
|
+
|
|
148
|
+
Clears `isOpen`, removes the outside-click/scroll/resize listeners, clears inline `top`/`left` and the placement attribute, sets `aria-expanded="false"`, runs the teleport-restore function, splices itself from `openStack` (removing the ESC listener if the stack is now empty), then restores focus to the trigger **only if** the trigger was the previously-focused element (click→Escape, click→programmatic close) or `activeElement === document.body` (Escape from inside the popover, outside-click on inert whitespace) — an outside-click landing on another focusable element keeps that element's focus. Dispatches `ln-popover:close`.
|
|
149
|
+
|
|
150
|
+
### Focus management
|
|
151
|
+
|
|
152
|
+
A disclosure pattern, not a modal — no Tab trap. Because the popover is teleported to the end of `<body>`, Tab exits it into whatever follows in body order. `tabindex="-1"` and `role="dialog"` are set automatically at construction so the container itself is a valid focus target when there's no focusable child.
|
|
153
|
+
|
|
154
|
+
### Nested popovers
|
|
155
|
+
|
|
156
|
+
Outside-click tests `self.dom.contains(e.target)`, so a click inside A's subtree (including a button that opens B) never triggers A's close path. Both popovers land on `openStack`; LIFO Escape closes B first, then A on the next press. There is no "close all."
|
|
157
|
+
|
|
158
|
+
### Observer & registration
|
|
159
|
+
|
|
160
|
+
Single observer on `document.body` (`subtree`/`childList`/`attributes`, filtered to `data-ln-popover`/`data-ln-popover-for`): childList additions re-run `_findPopovers`/`_attachTriggers`; attribute mutations dispatch to `_syncAttribute` for existing instances or re-run discovery for new ones. Two separate `registerComponent` calls wire popover elements (`el.lnPopover`) and trigger elements (`el.lnPopoverTrigger`) independently — trigger init sets `aria-haspopup="dialog"`, `aria-expanded="false"`, `aria-controls`, once, on attach.
|
|
161
|
+
|
|
162
|
+
`destroy()` on a popover instance closes it if open, removes its listeners, splices it from `openStack`, and dispatches `ln-popover:destroyed`. Trigger `destroy` is independent and only removes the trigger's click handler.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){"use strict";function A(t,e,r){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:r||{}}))}function E(t,e,r){const g=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:r||{}});return t.dispatchEvent(g),g}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 O(t,e,r,g){if(t.nodeType!==1)return;const f=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",m=Array.from(t.querySelectorAll(f));t.matches&&t.matches(f)&&m.push(t);for(const d of m)d[r]||(d[r]=new g(d))}function I(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function k(t,e,r,g,u={}){const f=u.extraAttributes||[],m=u.onAttributeChange||null,d=u.onInit||null;function l(y){const i=y||document.body;O(i,t,e,r),d&&d(i)}return x(function(){const y=new MutationObserver(function(s){for(let a=0;a<s.length;a++){const n=s[a];if(n.type==="childList"){for(let o=0;o<n.addedNodes.length;o++){const p=n.addedNodes[o];p.nodeType===1&&(O(p,t,e,r),d&&d(p))}for(let o=0;o<n.removedNodes.length;o++){const p=n.removedNodes[o];if(p.nodeType===1){const h=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",b=Array.from(p.querySelectorAll(h));p.matches&&p.matches(h)&&b.push(p);for(let c=0;c<b.length;c++){const v=b[c];if(!document.contains(v)){const C=v[e];C&&typeof C.destroy=="function"&&C.destroy()}}}}}else n.type==="attributes"&&(m&&n.target[e]?m(n.target,n.attributeName):(O(n.target,t,e,r),d&&d(n.target)))}});let i=[];if(t.indexOf("[")!==-1){const s=/\[([\w-]+)/g;let a;for(;(a=s.exec(t))!==null;)i.push(a[1])}else i.push(t);y.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:i.concat(f)})},g||(t.indexOf("[")===-1?t.replace("data-",""):"component")),window[e]=l,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){l(document.body)}):l(document.body),l}const L={};function R(t,e){L[t]=e}function N(t){return L[t]||{ingress:e=>e,egress:e=>e}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=R,window.lnCore.getDataMapper=N);function T(t,e,r,g){const u=g,f=window.innerWidth,m=window.innerHeight,d=e.width,l=e.height,y=(r||"bottom").split("-"),i=y[0],s=y[1]==="start"||y[1]==="end"?y[1]:"center",a={top:["top","bottom","right","left"],bottom:["bottom","top","right","left"],left:["left","right","top","bottom"],right:["right","left","top","bottom"]},n=a[i]||a.bottom;function o(c){return c==="top"||c==="bottom"?s==="start"?t.left:s==="end"?t.right-d:t.left+(t.width-d)/2:s==="start"?t.top:s==="end"?t.bottom-l:t.top+(t.height-l)/2}function p(c){let v,C,w=!0;return c==="top"?(v=t.top-u-l,C=o(c),v<0&&(w=!1)):c==="bottom"?(v=t.bottom+u,C=o(c),v+l>m&&(w=!1)):c==="left"?(v=o(c),C=t.left-u-d,C<0&&(w=!1)):(v=o(c),C=t.right+u,C+d>f&&(w=!1)),{top:v,left:C,side:c,fits:w}}let _=null;for(let c=0;c<n.length;c++){const v=p(n[c]);if(v.fits){_=v;break}}_||(_=p(n[0]));let h=_.top,b=_.left;return d>=f?b=0:(b<0&&(b=0),b+d>f&&(b=f-d)),l>=m?h=0:(h<0&&(h=0),h+l>m&&(h=m-l)),{top:h,left:b,placement:_.side}}function M(t){if(!t||t.parentNode===document.body)return function(){};const e=t.parentNode,r=document.createComment("ln-teleport");return e.insertBefore(r,t),document.body.appendChild(t),function(){r.parentNode&&(r.parentNode.insertBefore(t,r),r.parentNode.removeChild(r))}}function D(t){if(!t)return{width:0,height:0};const e=t.style,r=e.visibility,g=e.display,u=e.position;e.visibility="hidden",e.display="block",e.position="fixed";const f=t.offsetWidth,m=t.offsetHeight;return e.visibility=r,e.display=g,e.position=u,{width:f,height:m}}(function(){const t="data-ln-popover",e="lnPopover",r="data-ln-popover-for",g="data-ln-popover-position";if(window[e]!==void 0)return;const u=[];let f=null;function m(){f||(f=function(i){if(i.key!=="Escape"||u.length===0)return;u[u.length-1].close()},document.addEventListener("keydown",f))}function d(){u.length>0||f&&(document.removeEventListener("keydown",f),f=null)}function l(i){return this.dom=i,this.isOpen=i.getAttribute(t)==="open",this.trigger=null,this._teleportRestore=null,this._previousFocus=null,this._boundDocClick=null,this._docClickTimeout=null,this._boundReposition=null,i.hasAttribute("tabindex")||i.setAttribute("tabindex","-1"),i.hasAttribute("role")||i.setAttribute("role","dialog"),this.isOpen&&this._applyOpen(null),this}l.prototype.open=function(i){this.isOpen||(this.trigger=i||null,this.dom.setAttribute(t,"open"))},l.prototype.close=function(){this.isOpen&&this.dom.setAttribute(t,"closed")},l.prototype.toggle=function(i){this.isOpen?this.close():this.open(i)},l.prototype._applyOpen=function(i){this.isOpen=!0,i&&(this.trigger=i),this._previousFocus=document.activeElement,this._teleportRestore=M(this.dom);const s=D(this.dom);if(this.trigger){const p=this.trigger.getBoundingClientRect(),_=this.dom.getAttribute(g)||"bottom",h=T(p,s,_,8);this.dom.style.top=h.top+"px",this.dom.style.left=h.left+"px",this.dom.setAttribute("data-ln-popover-placement",h.placement),this.trigger.setAttribute("aria-expanded","true")}const a=this.dom.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),n=Array.prototype.find.call(a,I);n?n.focus():this.dom.focus();const o=this;this._boundDocClick=function(p){o.dom.contains(p.target)||o.trigger&&o.trigger.contains(p.target)||o.close()},o._docClickTimeout=setTimeout(function(){o._docClickTimeout=null,document.addEventListener("click",o._boundDocClick)},0),this._boundReposition=function(){if(!o.trigger)return;const p=o.trigger.getBoundingClientRect(),_=D(o.dom),h=o.dom.getAttribute(g)||"bottom",b=T(p,_,h,8);o.dom.style.top=b.top+"px",o.dom.style.left=b.left+"px",o.dom.setAttribute("data-ln-popover-placement",b.placement)},window.addEventListener("scroll",this._boundReposition,{passive:!0,capture:!0}),window.addEventListener("resize",this._boundReposition),u.push(this),m(),A(this.dom,"ln-popover:open",{popoverId:this.dom.id,target:this.dom,trigger:this.trigger})},l.prototype._applyClose=function(){this.isOpen=!1,this._docClickTimeout&&(clearTimeout(this._docClickTimeout),this._docClickTimeout=null),this._boundDocClick&&(document.removeEventListener("click",this._boundDocClick),this._boundDocClick=null),this._boundReposition&&(window.removeEventListener("scroll",this._boundReposition,{capture:!0}),window.removeEventListener("resize",this._boundReposition),this._boundReposition=null),this.dom.style.top="",this.dom.style.left="",this.dom.removeAttribute("data-ln-popover-placement"),this.trigger&&this.trigger.setAttribute("aria-expanded","false"),this._teleportRestore&&(this._teleportRestore(),this._teleportRestore=null);const i=u.indexOf(this);i!==-1&&u.splice(i,1),d(),this._previousFocus&&this.trigger&&this._previousFocus===this.trigger?this.trigger.focus():this.trigger&&document.activeElement===document.body&&this.trigger.focus(),this._previousFocus=null,A(this.dom,"ln-popover:close",{popoverId:this.dom.id,target:this.dom,trigger:this.trigger}),this.trigger=null},l.prototype.destroy=function(){this.dom[e]&&(this.isOpen&&this._applyClose(),delete this.dom[e],A(this.dom,"ln-popover:destroyed",{popoverId:this.dom.id,target:this.dom}))};function y(i){this.dom=i;const s=i.getAttribute(r);return i.setAttribute("aria-haspopup","dialog"),i.setAttribute("aria-expanded","false"),i.setAttribute("aria-controls",s),this._onClick=function(a){if(a.ctrlKey||a.metaKey||a.button===1)return;a.preventDefault();const n=document.getElementById(s);!n||!n[e]||n[e].toggle(i)},i.addEventListener("click",this._onClick),this}y.prototype.destroy=function(){this.dom.removeEventListener("click",this._onClick),delete this.dom[e+"Trigger"]},k(t,e,l,"ln-popover",{onAttributeChange:function(i){const s=i[e];if(!s)return;const n=i.getAttribute(t)==="open";if(n!==s.isOpen)if(n){if(E(i,"ln-popover:before-open",{popoverId:i.id,target:i,trigger:s.trigger}).defaultPrevented){i.setAttribute(t,"closed");return}s._applyOpen(s.trigger)}else{if(E(i,"ln-popover:before-close",{popoverId:i.id,target:i,trigger:s.trigger}).defaultPrevented){i.setAttribute(t,"open");return}s._applyClose()}}}),k(r,e+"Trigger",y,"ln-popover-trigger")})()})();
|
|
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 A={};function N(t,e){A[t]||(A[t]=document.querySelector('[data-ln-template="'+t+'"]'));const s=A[t];return s?s.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function E(t,e,s){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:s||{}}))}function k(t,e,s){const h=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:s||{}});return t.dispatchEvent(h),h}function O(t,e){if(!t||!e)return t;const s=t.querySelectorAll("[data-ln-field]");for(let o=0;o<s.length;o++){const r=s[o],i=r.getAttribute("data-ln-field");e[i]!=null&&(r.textContent=e[i])}const h=t.querySelectorAll("[data-ln-attr]");for(let o=0;o<h.length;o++){const r=h[o],i=r.getAttribute("data-ln-attr").split(",");for(let f=0;f<i.length;f++){const n=i[f].trim().split(":");if(n.length!==2)continue;const u=n[0].trim(),d=n[1].trim();e[d]!=null&&r.setAttribute(u,e[d])}}const c=t.querySelectorAll("[data-ln-show]");for(let o=0;o<c.length;o++){const r=c[o],i=r.getAttribute("data-ln-show");i in e&&r.classList.toggle("hidden",!e[i])}const l=t.querySelectorAll("[data-ln-class]");for(let o=0;o<l.length;o++){const r=l[o],i=r.getAttribute("data-ln-class").split(",");for(let f=0;f<i.length;f++){const n=i[f].trim().split(":");if(n.length!==2)continue;const u=n[0].trim(),d=n[1].trim();d in e&&r.classList.toggle(u,!!e[d])}}return t}function W(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));const s=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let h=0;h<s.length;h++)s[h].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)O(t.target,t.detail);else{const e=t.target.querySelectorAll("[data-ln-field]");for(let s=0;s<e.length;s++)e[s].textContent=""}})));function T(t,e){if(!t||!e)return t;const s=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;s.nextNode();){const l=s.currentNode;l.textContent.indexOf("{{")!==-1&&(l.textContent=l.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(o,r){return e[r]!==void 0?e[r]:""}))}const h=function(l,o){return e[o]!==void 0?e[o]:""},c=Array.from(t.querySelectorAll("*"));t.nodeType===1&&c.push(t);for(let l=0;l<c.length;l++){const o=c[l],r=o.attributes;for(let i=0;i<r.length;i++){const f=r[i];f.value.indexOf("{{")!==-1&&o.setAttribute(f.name,f.value.replace(/\{\{\s*(\w+)\s*\}\}/g,h))}}return t}function B(t,e,s,h,c,l){const o={};for(let i=0;i<t.children.length;i++){const f=t.children[i],n=f.getAttribute("data-ln-key");n&&(o[n]=f)}const r=document.createDocumentFragment();for(let i=0;i<e.length;i++){const f=e[i],n=String(h(f));let u=o[n];if(u)c(u,f,i);else{const d=N(s,l);if(!d||(T(d,f),u=d.firstElementChild,!u))continue;u.setAttribute("data-ln-key",n),c(u,f,i)}r.appendChild(u)}t.textContent="",t.appendChild(r)}function q(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){q(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function L(t,e,s,h){if(t.nodeType!==1)return;const l=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",o=Array.from(t.querySelectorAll(l));t.matches&&t.matches(l)&&o.push(t);for(const r of o)r[s]||(r[s]=new h(r))}function j(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function S(t,e,s,h,c={}){const l=c.extraAttributes||[],o=c.onAttributeChange||null,r=c.onInit||null;function i(f){const n=f||document.body;L(n,t,e,s),r&&r(n)}return q(function(){const f=new MutationObserver(function(u){for(let d=0;d<u.length;d++){const p=u[d];if(p.type==="childList"){for(let a=0;a<p.addedNodes.length;a++){const g=p.addedNodes[a];g.nodeType===1&&(L(g,t,e,s),r&&r(g))}for(let a=0;a<p.removedNodes.length;a++){const g=p.removedNodes[a];if(g.nodeType===1){const b=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",y=Array.from(g.querySelectorAll(b));g.matches&&g.matches(b)&&y.push(g);for(let m=0;m<y.length;m++){const v=y[m];if(!document.contains(v)){const C=v[e];C&&typeof C.destroy=="function"&&C.destroy()}}}}}else p.type==="attributes"&&(o&&p.target[e]?o(p.target,p.attributeName):(L(p.target,t,e,s),r&&r(p.target)))}});let n=[];if(t.indexOf("[")!==-1){const u=/\[([\w-]+)/g;let d;for(;(d=u.exec(t))!==null;)n.push(d[1])}else n.push(t);f.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:n.concat(l)})},h||(t.indexOf("[")===-1?t.replace("data-",""):"component")),window[e]=i,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){i(document.body)}):i(document.body),i}const D={};function K(t,e){D[t]=e}function H(t){return D[t]||{ingress:e=>e,egress:e=>e}}const R={};function I(t,e){if(!t||typeof e!="object")return;const s=t.toLowerCase().split("-")[0];R[s]=e}function G(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return R[e]||null}I("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=K,window.lnCore.getDataMapper=H,window.lnCore.registerLocaleFallback=I,window.lnCore.getLocaleFallback=G,window.lnCore.fillTemplate=T,window.lnCore.fill=O,window.lnCore.lnFill=W,window.lnCore.renderList=B);function F(t){return(t||"").replace(/^#/,"")}function x(t){const e=t===void 0?location.hash:t,s={},h=F(e);if(!h)return s;const c=h.split("&");for(let l=0;l<c.length;l++){const o=c[l];if(!o)continue;const r=o.indexOf(":"),i=r>-1?o.slice(0,r):o,f=r>-1?o.slice(r+1):"";if(i)try{s[i]=decodeURIComponent(f)}catch{s[i]=f}}return s}function z(t){if(!t)return null;const e=x();return t in e?e[t]:null}function U(t,e){if(!t)return;const s=x();e==null?delete s[t]:s[t]=String(e);const c=Object.keys(s).map(function(l){const o=s[l];return o===""?l:l+":"+encodeURIComponent(o)}).join("&");F(location.hash)!==c&&(location.hash=c)}function V(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=x,window.lnCore.hashGet=z,window.lnCore.hashSet=U,window.lnCore.hashLinkClick=V);function P(t,e,s,h){const c=h,l=window.innerWidth,o=window.innerHeight,r=e.width,i=e.height,f=(s||"bottom").split("-"),n=f[0],u=f[1]==="start"||f[1]==="end"?f[1]:"center",d={top:["top","bottom","right","left"],bottom:["bottom","top","right","left"],left:["left","right","top","bottom"],right:["right","left","top","bottom"]},p=d[n]||d.bottom;function a(m){return m==="top"||m==="bottom"?u==="start"?t.left:u==="end"?t.right-r:t.left+(t.width-r)/2:u==="start"?t.top:u==="end"?t.bottom-i:t.top+(t.height-i)/2}function g(m){let v,C,_=!0;return m==="top"?(v=t.top-c-i,C=a(m),v<0&&(_=!1)):m==="bottom"?(v=t.bottom+c,C=a(m),v+i>o&&(_=!1)):m==="left"?(v=a(m),C=t.left-c-r,C<0&&(_=!1)):(v=a(m),C=t.right+c,C+r>l&&(_=!1)),{top:v,left:C,side:m,fits:_}}let w=null;for(let m=0;m<p.length;m++){const v=g(p[m]);if(v.fits){w=v;break}}w||(w=g(p[0]));let b=w.top,y=w.left;return r>=l?y=0:(y<0&&(y=0),y+r>l&&(y=l-r)),i>=o?b=0:(b<0&&(b=0),b+i>o&&(b=o-i)),{top:b,left:y,placement:w.side}}function M(t){if(!t)return{width:0,height:0};const e=t.style,s=e.visibility,h=e.display,c=e.position;e.visibility="hidden",e.display="block",e.position="fixed";const l=t.offsetWidth,o=t.offsetHeight;return e.visibility=s,e.display=h,e.position=c,{width:l,height:o}}(function(){const t="data-ln-popover",e="lnPopover",s="data-ln-popover-for",h="data-ln-popover-position";if(window[e]!==void 0)return;const c=[];let l=null;function o(){l||(l=function(n){if(n.key!=="Escape"||c.length===0)return;c[c.length-1].close()},document.addEventListener("keydown",l))}function r(){c.length>0||l&&(document.removeEventListener("keydown",l),l=null)}function i(n){this.dom=n,this.isOpen=n.getAttribute(t)==="open",this.trigger=null,this._previousFocus=null,this._boundDocClick=null,this._docClickTimeout=null,this._boundReposition=null;const u=this;return this._onRequestOpen=function(d){const p=d.detail&&d.detail.trigger?d.detail.trigger:null;u.open(p)},this._onRequestClose=function(){u.close()},this._onRequestToggle=function(d){const p=d.detail&&d.detail.trigger?d.detail.trigger:null;u.toggle(p)},n.addEventListener("ln-popover:request-open",this._onRequestOpen),n.addEventListener("ln-popover:request-close",this._onRequestClose),n.addEventListener("ln-popover:request-toggle",this._onRequestToggle),n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),n.hasAttribute("role")||n.setAttribute("role","dialog"),n.hasAttribute("popover")||n.setAttribute("popover","manual"),this.isOpen&&this._applyOpen(null),this}i.prototype.open=function(n){this.isOpen||(this.trigger=n||null,this.dom.setAttribute(t,"open"))},i.prototype.close=function(){this.isOpen&&this.dom.setAttribute(t,"closed")},i.prototype.toggle=function(n){this.isOpen?this.close():this.open(n)},i.prototype._applyOpen=function(n){this.isOpen=!0,n&&(this.trigger=n),this._previousFocus=document.activeElement,typeof this.dom.showPopover=="function"&&this.dom.showPopover();const u=M(this.dom);if(this.trigger){const g=this.trigger.getBoundingClientRect(),w=this.dom.getAttribute(h)||"bottom",b=P(g,u,w,8);this.dom.style.top=b.top+"px",this.dom.style.left=b.left+"px",this.dom.setAttribute("data-ln-popover-placement",b.placement),this.trigger.setAttribute("aria-expanded","true")}const d=this.dom.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),p=Array.prototype.find.call(d,j);p?p.focus():this.dom.focus();const a=this;this._boundDocClick=function(g){a.dom.contains(g.target)||a.trigger&&a.trigger.contains(g.target)||a.close()},a._docClickTimeout=setTimeout(function(){a._docClickTimeout=null,document.addEventListener("click",a._boundDocClick)},0),this._boundReposition=function(){if(!a.trigger)return;const g=a.trigger.getBoundingClientRect(),w=M(a.dom),b=a.dom.getAttribute(h)||"bottom",y=P(g,w,b,8);a.dom.style.top=y.top+"px",a.dom.style.left=y.left+"px",a.dom.setAttribute("data-ln-popover-placement",y.placement)},window.addEventListener("scroll",this._boundReposition,{passive:!0,capture:!0}),window.addEventListener("resize",this._boundReposition),c.push(this),o(),E(this.dom,"ln-popover:open",{popoverId:this.dom.id,target:this.dom,trigger:this.trigger})},i.prototype._applyClose=function(){this.isOpen=!1,this._docClickTimeout&&(clearTimeout(this._docClickTimeout),this._docClickTimeout=null),this._boundDocClick&&(document.removeEventListener("click",this._boundDocClick),this._boundDocClick=null),this._boundReposition&&(window.removeEventListener("scroll",this._boundReposition,{capture:!0}),window.removeEventListener("resize",this._boundReposition),this._boundReposition=null),this.dom.style.top="",this.dom.style.left="",this.dom.removeAttribute("data-ln-popover-placement"),this.trigger&&this.trigger.setAttribute("aria-expanded","false"),typeof this.dom.hidePopover=="function"&&this.dom.matches(":popover-open")&&this.dom.hidePopover();const n=c.indexOf(this);n!==-1&&c.splice(n,1),r(),this._previousFocus&&this.trigger&&this._previousFocus===this.trigger?this.trigger.focus():this.trigger&&document.activeElement===document.body&&this.trigger.focus(),this._previousFocus=null,E(this.dom,"ln-popover:close",{popoverId:this.dom.id,target:this.dom,trigger:this.trigger}),this.trigger=null},i.prototype.destroy=function(){this.dom[e]&&(this.dom.removeEventListener("ln-popover:request-open",this._onRequestOpen),this.dom.removeEventListener("ln-popover:request-close",this._onRequestClose),this.dom.removeEventListener("ln-popover:request-toggle",this._onRequestToggle),this.isOpen&&this._applyClose(),delete this.dom[e],E(this.dom,"ln-popover:destroyed",{popoverId:this.dom.id,target:this.dom}))};function f(n){this.dom=n;const u=n.getAttribute(s);return n.setAttribute("aria-haspopup","dialog"),n.setAttribute("aria-expanded","false"),n.setAttribute("aria-controls",u),this._onClick=function(d){if(d.ctrlKey||d.metaKey||d.button===1)return;d.preventDefault();const p=document.getElementById(u);if(!p)return;p[e]&&(p[e].trigger=n);const a=p.getAttribute(t);p.setAttribute(t,a==="open"?"closed":"open")},n.addEventListener("click",this._onClick),this}f.prototype.destroy=function(){this.dom.removeEventListener("click",this._onClick),delete this.dom[e+"Trigger"]},S(t,e,i,"ln-popover",{onAttributeChange:function(n){const u=n[e];if(!u)return;const p=n.getAttribute(t)==="open";if(p!==u.isOpen)if(p){if(k(n,"ln-popover:before-open",{popoverId:n.id,target:n,trigger:u.trigger}).defaultPrevented){n.setAttribute(t,"closed");return}u._applyOpen(u.trigger)}else{if(k(n,"ln-popover:before-close",{popoverId:n.id,target:n,trigger:u.trigger}).defaultPrevented){n.setAttribute(t,"open");return}u._applyClose()}}}),S(s,e+"Trigger",f,"ln-popover-trigger")})()})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dispatch, dispatchCancelable, computePlacement,
|
|
1
|
+
import { dispatch, dispatchCancelable, computePlacement, measureHidden, isVisible, registerComponent } from '../../ln-core';
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
const DOM_SELECTOR = 'data-ln-popover';
|
|
@@ -37,12 +37,28 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
37
37
|
this.dom = dom;
|
|
38
38
|
this.isOpen = dom.getAttribute(DOM_SELECTOR) === 'open';
|
|
39
39
|
this.trigger = null;
|
|
40
|
-
this._teleportRestore = null;
|
|
41
40
|
this._previousFocus = null;
|
|
42
41
|
this._boundDocClick = null;
|
|
43
42
|
this._docClickTimeout = null;
|
|
44
43
|
this._boundReposition = null;
|
|
45
44
|
|
|
45
|
+
const self = this;
|
|
46
|
+
this._onRequestOpen = function (e) {
|
|
47
|
+
const trigger = e.detail && e.detail.trigger ? e.detail.trigger : null;
|
|
48
|
+
self.open(trigger);
|
|
49
|
+
};
|
|
50
|
+
this._onRequestClose = function () {
|
|
51
|
+
self.close();
|
|
52
|
+
};
|
|
53
|
+
this._onRequestToggle = function (e) {
|
|
54
|
+
const trigger = e.detail && e.detail.trigger ? e.detail.trigger : null;
|
|
55
|
+
self.toggle(trigger);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
dom.addEventListener('ln-popover:request-open', this._onRequestOpen);
|
|
59
|
+
dom.addEventListener('ln-popover:request-close', this._onRequestClose);
|
|
60
|
+
dom.addEventListener('ln-popover:request-toggle', this._onRequestToggle);
|
|
61
|
+
|
|
46
62
|
// Make the popover container itself programmatically focusable
|
|
47
63
|
// as a fallback when it has no focusable children.
|
|
48
64
|
if (!dom.hasAttribute('tabindex')) {
|
|
@@ -51,6 +67,11 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
51
67
|
if (!dom.hasAttribute('role')) {
|
|
52
68
|
dom.setAttribute('role', 'dialog');
|
|
53
69
|
}
|
|
70
|
+
// Popover API gives no implicit ARIA role and no focus management —
|
|
71
|
+
// unlike <dialog>, everything above/below this line stays manual.
|
|
72
|
+
if (!dom.hasAttribute('popover')) {
|
|
73
|
+
dom.setAttribute('popover', 'manual');
|
|
74
|
+
}
|
|
54
75
|
|
|
55
76
|
// If the markup says open at boot, sync immediately.
|
|
56
77
|
if (this.isOpen) {
|
|
@@ -86,9 +107,9 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
86
107
|
if (trigger) this.trigger = trigger;
|
|
87
108
|
this._previousFocus = document.activeElement;
|
|
88
109
|
|
|
89
|
-
//
|
|
90
|
-
// regardless of any ancestor with `transform` or `
|
|
91
|
-
this.
|
|
110
|
+
// Promote to the top layer so position:fixed coordinates are reliable
|
|
111
|
+
// regardless of any ancestor with `transform`, `contain`, or `overflow: hidden`.
|
|
112
|
+
if (typeof this.dom.showPopover === 'function') this.dom.showPopover();
|
|
92
113
|
|
|
93
114
|
// Measure (works even though it just got `display: block` by the
|
|
94
115
|
// attribute switch — measureHidden is safe either way).
|
|
@@ -180,10 +201,9 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
180
201
|
this.trigger.setAttribute('aria-expanded', 'false');
|
|
181
202
|
}
|
|
182
203
|
|
|
183
|
-
//
|
|
184
|
-
if (this.
|
|
185
|
-
this.
|
|
186
|
-
this._teleportRestore = null;
|
|
204
|
+
// Exit the top layer.
|
|
205
|
+
if (typeof this.dom.hidePopover === 'function' && this.dom.matches(':popover-open')) {
|
|
206
|
+
this.dom.hidePopover();
|
|
187
207
|
}
|
|
188
208
|
|
|
189
209
|
// Remove from open stack.
|
|
@@ -211,6 +231,9 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
211
231
|
|
|
212
232
|
_component.prototype.destroy = function () {
|
|
213
233
|
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
234
|
+
this.dom.removeEventListener('ln-popover:request-open', this._onRequestOpen);
|
|
235
|
+
this.dom.removeEventListener('ln-popover:request-close', this._onRequestClose);
|
|
236
|
+
this.dom.removeEventListener('ln-popover:request-toggle', this._onRequestToggle);
|
|
214
237
|
if (this.isOpen) this._applyClose();
|
|
215
238
|
delete this.dom[DOM_ATTRIBUTE];
|
|
216
239
|
dispatch(this.dom, 'ln-popover:destroyed', {
|
|
@@ -233,8 +256,12 @@ import { dispatch, dispatchCancelable, computePlacement, teleportToBody, measure
|
|
|
233
256
|
if (e.ctrlKey || e.metaKey || e.button === 1) return;
|
|
234
257
|
e.preventDefault();
|
|
235
258
|
const target = document.getElementById(popoverId);
|
|
236
|
-
if (!target
|
|
237
|
-
target[DOM_ATTRIBUTE]
|
|
259
|
+
if (!target) return;
|
|
260
|
+
if (target[DOM_ATTRIBUTE]) {
|
|
261
|
+
target[DOM_ATTRIBUTE].trigger = dom;
|
|
262
|
+
}
|
|
263
|
+
const current = target.getAttribute(DOM_SELECTOR);
|
|
264
|
+
target.setAttribute(DOM_SELECTOR, current === 'open' ? 'closed' : 'open');
|
|
238
265
|
};
|
|
239
266
|
dom.addEventListener('click', this._onClick);
|
|
240
267
|
return this;
|