@faqir-ui/core 1.1.1 → 1.1.2
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/cdn.json +35 -35
- package/dist/faqir-core.js +496 -328
- package/dist/faqir-core.min.js +3 -3
- package/dist/faqir-core.min.js.map +3 -3
- package/dist/faqir-core.mjs +518 -477
- package/dist/faqir.aurora.css +1486 -1036
- package/dist/faqir.brutalist.css +1486 -1036
- package/dist/faqir.candy.css +1486 -1036
- package/dist/faqir.clinical.css +1486 -1036
- package/dist/faqir.contrast.css +1486 -1036
- package/dist/faqir.default.css +1486 -1036
- package/dist/faqir.document-serif.css +1486 -1036
- package/dist/faqir.document.css +1486 -1036
- package/dist/faqir.editorial-document.css +1486 -1036
- package/dist/faqir.editorial.css +1486 -1036
- package/dist/faqir.fintech.css +1486 -1036
- package/dist/faqir.glass.css +1486 -1036
- package/dist/faqir.ink-document.css +1486 -1036
- package/dist/faqir.ink.css +1486 -1036
- package/dist/faqir.luxe.css +1486 -1036
- package/dist/faqir.midnight.css +1486 -1036
- package/dist/faqir.neo.css +1486 -1036
- package/dist/faqir.neumorph.css +1486 -1036
- package/dist/faqir.nordic.css +1486 -1036
- package/dist/faqir.organic.css +1486 -1036
- package/dist/faqir.paper.css +1486 -1036
- package/dist/faqir.slate.css +1486 -1036
- package/dist/faqir.soft.css +1486 -1036
- package/dist/faqir.sunset.css +1486 -1036
- package/dist/faqir.swiss-document.css +1486 -1036
- package/dist/faqir.swiss.css +1486 -1036
- package/dist/faqir.terminal.css +1486 -1036
- package/dist/plugins/faqir-mask.js +1 -2
- package/dist/plugins/faqir-rules.js +1 -1
- package/dist/sri.json +33 -33
- package/faqir-core.d.ts +10 -2
- package/package.json +1 -1
package/dist/faqir-core.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// PRODUCTION BUILD — dev-only diagnostics stripped at assembly time.
|
|
11
11
|
// Load faqir-core.dev.js instead while developing for full warnings.
|
|
12
12
|
// Regenerate with: bun run build:core
|
|
13
|
-
// Package version: 1.1.
|
|
13
|
+
// Package version: 1.1.2
|
|
14
14
|
// ============================================================================
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
function evaluate(expression, scope, el) {
|
|
228
228
|
try {
|
|
229
229
|
const fn = compileExpression(expression);
|
|
230
|
-
return fn.call(scope, scope, el);
|
|
230
|
+
return fn.call(scope, scope, el, el);
|
|
231
231
|
} catch (e) {
|
|
232
232
|
console.warn('[Faqir] Expression error: "' + expression + '"', e);
|
|
233
233
|
return undefined;
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
function evaluateAssignment(expression, scope, el) {
|
|
238
238
|
try {
|
|
239
239
|
const fn = compileStatement(expression);
|
|
240
|
-
fn.call(scope, scope, el);
|
|
240
|
+
fn.call(scope, scope, el, el);
|
|
241
241
|
} catch (e) {
|
|
242
242
|
console.warn('[Faqir] Statement error: "' + expression + '"', e);
|
|
243
243
|
}
|
|
@@ -261,12 +261,17 @@
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// `$el` and `$this` are both the element the directive is written on. Inside
|
|
265
|
+
// `with($scope)` the scope's own `$el` magic (the root) wins, so `$el` only
|
|
266
|
+
// reaches this parameter where nothing shadows it: a top-level `l-data`,
|
|
267
|
+
// evaluated against `{}`. `$this` is never a scope key, so it always reaches
|
|
268
|
+
// it. [1.1F-06]
|
|
264
269
|
function compileExpression(expr) {
|
|
265
270
|
var key = 'expr:' + expr;
|
|
266
271
|
if (expressionCache.has(key)) return expressionCache.get(key);
|
|
267
272
|
|
|
268
273
|
var fn = new Function(
|
|
269
|
-
'$scope', '$el',
|
|
274
|
+
'$scope', '$el', '$this',
|
|
270
275
|
'with($scope) { return (' + expr + ') }'
|
|
271
276
|
);
|
|
272
277
|
expressionCache.set(key, fn);
|
|
@@ -278,7 +283,7 @@
|
|
|
278
283
|
if (expressionCache.has(key)) return expressionCache.get(key);
|
|
279
284
|
|
|
280
285
|
var fn = new Function(
|
|
281
|
-
'$scope', '$el',
|
|
286
|
+
'$scope', '$el', '$this',
|
|
282
287
|
'with($scope) { ' + expr + ' }'
|
|
283
288
|
);
|
|
284
289
|
expressionCache.set(key, fn);
|
|
@@ -316,21 +321,21 @@
|
|
|
316
321
|
// (§3.9) and MOTION_PRESETS (§3.14) — are read from the code itself, not
|
|
317
322
|
// repeated here.
|
|
318
323
|
//
|
|
319
|
-
// @ui:directive l-data | — | any element, which becomes the scope root | l-data="{ count: 0 }" | Declares a reactive scope from an object literal, or from a name registered with `Faqir.data()`. `data-prop-*` attributes are JSON-parsed and merged over it. Descendants share the scope until the next `l-data`.
|
|
320
|
-
// @ui:directive l-init | — | a scope root, beside `l-data` | l-init="load()" | Runs once, after the scope exists and its `l-source` bindings are injected.
|
|
324
|
+
// @ui:directive l-data | — | any element, which becomes the scope root | l-data="{ count: 0 }" | Declares a reactive scope from an object literal, or from a name registered with `Faqir.data()`. `data-prop-*` attributes are JSON-parsed and merged over it. Descendants share the scope until the next `l-data`. Its methods are bound to the scope, so `this` is the component even when an `l-for` row calls them; a function assigned later is not bound.
|
|
325
|
+
// @ui:directive l-init | — | a scope root, beside `l-data` | l-init="load()" | Runs once, after the scope exists and its `l-source` bindings are injected, with the subtree's controllers already started. In `l-if` / `l-for` content it runs after insertion: bindings, insert, controllers, then `l-init` / first `l-effect`.
|
|
321
326
|
// @ui:directive l-source:<name> | — | a scope root, beside `l-data` | l-source:tasks="/api/tasks" | Binds a REST collection into the scope as `<name>`, `<name>Loading`, `<name>Error` and the `$<name>` CRUD controller. See below.
|
|
322
327
|
// @ui:directive l-text | — | any element | l-text="count" | Writes the value to `textContent`; `null` and `undefined` write an empty string.
|
|
323
328
|
// @ui:directive l-html | — | any element | l-html="body" | Writes the value to `innerHTML`, unsanitized — never pass user input. The dev engine reports every use.
|
|
324
|
-
// @ui:directive l-bind:<attr> | :<attr> | any element | :disabled="loading" | Binds one attribute. `class` and `style` take a string, an array or an object
|
|
329
|
+
// @ui:directive l-bind:<attr> | :<attr> | any element | :disabled="loading" | Binds one attribute. `class` and `style` take a string, an array or an object and merge with what is there: each run removes only what its previous run set, so static markup, `l-show` and controllers keep theirs. Boolean attributes are added or removed; `null`, `undefined` and `false` remove the attribute.
|
|
325
330
|
// @ui:directive l-on:<event> | @<event> | any element | @click="count++" | Adds a listener for `<event>`. Inside the expression `$event` is the DOM event.
|
|
326
331
|
// @ui:directive l-model | — | an input, textarea or select, or `[data-ui="switch"]` | l-model="name" | Two-way binding. A checkbox bound to an array is a checkbox group; radios bind by value; `<select multiple>` is not handled. The modifiers below apply to the text-like branch (input, textarea, number) only.
|
|
327
332
|
// @ui:directive l-show | — | any element | l-show="open" | Toggles `display: none`, leaving the element in the DOM. Runs `l-transition` on each flip.
|
|
328
333
|
// @ui:directive l-if | — | a `<template>` element | <template l-if="open"> | Inserts and removes the template's content. Removal tears the subtree down: cleanups run and in-flight `l-source` requests abort.
|
|
329
334
|
// @ui:directive l-for | — | a `<template>` element | <template l-for="(task, i) in tasks"> | Repeats the template's content once per item, with the item and index names bound in a child scope.
|
|
330
335
|
// @ui:directive l-key | — | the same `<template>` as `l-for` | l-key="task.id" | The reconciliation key. Without it items are matched by position, so a reorder re-renders rather than moves; the dev engine reports that case.
|
|
331
|
-
// @ui:directive l-ref | — | any element | l-ref="field" | Registers the element on the scope's `$refs` under that name.
|
|
336
|
+
// @ui:directive l-ref | — | any element | l-ref="field" | Registers the element on the scope's `$refs` under that name — also from inside `l-if`, `l-for` and `l-teleport`, where the scope is the enclosing one. In an `l-for` the last row rendered wins, and the ref is cleared when that row goes. Effects that read it re-run when it appears or goes.
|
|
332
337
|
// @ui:directive l-effect | — | any element | l-effect="document.title = title" | Runs the expression immediately, then again whenever a value it read changes.
|
|
333
|
-
// @ui:directive l-cloak | — | any element | l-cloak | Removed from every element once the tree is initialized.
|
|
338
|
+
// @ui:directive l-cloak | — | any element | l-cloak | Removed from every element once the tree is initialized. Faqir's CSS (`base/reset.css`) hides `[l-cloak]` until then; the engine injects no style, so a page without that CSS needs its own `[l-cloak] { display: none }` rule.
|
|
334
339
|
// @ui:directive l-transition | — | an `l-show` element, or a top-level element inside a `<template l-if>` | l-transition="slide-up" | Names the motion preset for that element's enter/leave cycle. The engine only stamps `data-motion`; the CSS animates.
|
|
335
340
|
// @ui:directive l-teleport | — | any element | l-teleport="body" | Moves the element into the first element matching the value, which is a plain CSS selector rather than an expression.
|
|
336
341
|
//
|
|
@@ -354,6 +359,7 @@
|
|
|
354
359
|
// @ui:modifier l-source .key | Names the identity property, `id` by default: `.key.uuid`.
|
|
355
360
|
//
|
|
356
361
|
// @ui:magic $el | every expression | The scope ROOT — the element carrying `l-data`, not the element the expression is written on. Every magic that walks the DOM starts from here.
|
|
362
|
+
// @ui:magic $this | every expression | The element the directive is written on — the `<input>` carrying `l-init`, the button carrying `@click`. In `l-data` and a root's `l-init` it is the root; in `l-for`'s list and `l-key` expressions, the `<template>`. Not a scope key, so `Faqir.magic()` cannot shadow it.
|
|
357
363
|
// @ui:magic $refs | every expression | The scope's `l-ref` elements, keyed by name. Cleared entry by entry as elements are destroyed.
|
|
358
364
|
// @ui:magic $store | every expression | Every store registered with `Faqir.store()`.
|
|
359
365
|
// @ui:magic $state | every expression | `data-state` of the `[data-ui]` closest to the scope root. Writable — assigning sets the attribute — and reads re-run when a controller changes it.
|
|
@@ -478,6 +484,37 @@
|
|
|
478
484
|
// point authors at ("a scope root, beside `l-data`"). [W2-1]
|
|
479
485
|
var ROOT_CONSUMED = { data: 1, source: 1, init: 1 };
|
|
480
486
|
|
|
487
|
+
// `l-init` and each `l-effect`'s first run, held back while a structural
|
|
488
|
+
// render builds content that is not in the document yet. [1.1F-03]
|
|
489
|
+
var pendingInits = null;
|
|
490
|
+
|
|
491
|
+
function runInit(fn) {
|
|
492
|
+
if (pendingInits) pendingInits.push(fn); else fn();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// `l-if` / `l-for` content ran its `l-init` and its effects' first pass while
|
|
496
|
+
// still detached — before insertion, and before any controller inside it had
|
|
497
|
+
// started — so `$ui('#x')` there was null, and an effect that read nothing
|
|
498
|
+
// reactive never ran again. `render` binds and inserts, and returns the nodes
|
|
499
|
+
// it inserted; their controllers start next, and only then does the held work
|
|
500
|
+
// run: bindings → insertion → controllers → `l-init` / `l-effect`. A render
|
|
501
|
+
// nested inside another (an `l-if` in an `l-for` row) joins the outer one,
|
|
502
|
+
// which alone knows when the content is attached. Controllers are not started
|
|
503
|
+
// on content that is still detached. Untracked, so nothing the held work reads
|
|
504
|
+
// subscribes the structural effect that rendered it. [1.1F-03]
|
|
505
|
+
function renderThenInit(render) {
|
|
506
|
+
if (pendingInits) return render();
|
|
507
|
+
var queue = pendingInits = [];
|
|
508
|
+
var nodes;
|
|
509
|
+
try { nodes = render(); } finally { pendingInits = null; }
|
|
510
|
+
untrack(function() {
|
|
511
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
512
|
+
if (nodes[i].nodeType === 1 && nodes[i].isConnected) startControllers(nodes[i]);
|
|
513
|
+
}
|
|
514
|
+
for (var q = 0; q < queue.length; q++) queue[q]();
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
481
518
|
function initTree(root, parentScope) {
|
|
482
519
|
var scope = initScope(root, parentScope);
|
|
483
520
|
|
|
@@ -558,6 +595,17 @@
|
|
|
558
595
|
// at `<body>`. Created once, on first use, so a page with no stray directive
|
|
559
596
|
// pays nothing for it.
|
|
560
597
|
var strayScope = null;
|
|
598
|
+
|
|
599
|
+
// The element a scope's `$refs` live on, keyed by the scope object. Not
|
|
600
|
+
// `findScopeRoot`: `__faqirScope` also marks "already initialised", so an
|
|
601
|
+
// `l-if` clone or an `l-for` row carries it too, and a ref resolved by walking
|
|
602
|
+
// up landed on the clone while `$refs` read the real root — invisible. A
|
|
603
|
+
// teleported element has left the root's subtree, and a stray element has no
|
|
604
|
+
// stamped ancestor at all. A WeakMap rather than a scope property, because a
|
|
605
|
+
// read through the reactive scope inside a structural effect would subscribe
|
|
606
|
+
// it. [1.1F-01]
|
|
607
|
+
var scopeRoots = new WeakMap();
|
|
608
|
+
|
|
561
609
|
function getStrayScope() {
|
|
562
610
|
if (!strayScope) {
|
|
563
611
|
if (!document.body.__scopeId) document.body.__scopeId = ++scopeCounter;
|
|
@@ -567,6 +615,7 @@
|
|
|
567
615
|
// MutationObserver would stop giving standalone `[data-ui]` islands their
|
|
568
616
|
// own scope.
|
|
569
617
|
strayScope = createScopeWithMagics({}, document.body, document.body);
|
|
618
|
+
scopeRoots.set(strayScope, document.body);
|
|
570
619
|
}
|
|
571
620
|
return strayScope;
|
|
572
621
|
}
|
|
@@ -619,6 +668,7 @@
|
|
|
619
668
|
|
|
620
669
|
var scopeId = ++scopeCounter;
|
|
621
670
|
var scope = createScopeWithMagics(userData, root, root);
|
|
671
|
+
scopeRoots.set(scope, root);
|
|
622
672
|
root.__faqirScope = scope;
|
|
623
673
|
root.__scopeId = scopeId;
|
|
624
674
|
root.__faqirCleanups = [];
|
|
@@ -643,7 +693,7 @@
|
|
|
643
693
|
|
|
644
694
|
var initExpr = root.getAttribute('l-init');
|
|
645
695
|
if (initExpr) {
|
|
646
|
-
evaluateAssignment(initExpr, scope, root);
|
|
696
|
+
runInit(function() { evaluateAssignment(initExpr, scope, root); });
|
|
647
697
|
}
|
|
648
698
|
|
|
649
699
|
return scope;
|
|
@@ -733,6 +783,15 @@
|
|
|
733
783
|
Object.defineProperties(target, descriptors);
|
|
734
784
|
|
|
735
785
|
var scope = reactive(target);
|
|
786
|
+
// A bare call `pick()` inside `with(rowScope)` gets the l-for row scope as
|
|
787
|
+
// `this`, and that scope owns the loop variable and index: `this.item = x`
|
|
788
|
+
// wrote the row instead of the component. Bind each own method to the
|
|
789
|
+
// scope. Getters keep their receiver; functions assigned later stay
|
|
790
|
+
// unbound. Written to the target, so nothing is triggered. [1.1F-05]
|
|
791
|
+
for (var k in descriptors) {
|
|
792
|
+
var d = descriptors[k];
|
|
793
|
+
if (d.writable && typeof d.value === 'function') target[k] = d.value.bind(scope);
|
|
794
|
+
}
|
|
736
795
|
return scope;
|
|
737
796
|
}
|
|
738
797
|
|
|
@@ -751,8 +810,10 @@
|
|
|
751
810
|
|
|
752
811
|
// --- Scope utilities ---
|
|
753
812
|
|
|
813
|
+
// Created on first read, so an effect that reads a ref before it exists
|
|
814
|
+
// subscribes to the same object `handleRef` later writes through.
|
|
754
815
|
function getScopeRefs(root) {
|
|
755
|
-
return root.__faqirRefs || {};
|
|
816
|
+
return root.__faqirRefs || (root.__faqirRefs = {});
|
|
756
817
|
}
|
|
757
818
|
|
|
758
819
|
function findScopeRoot(el) {
|
|
@@ -1185,16 +1246,38 @@
|
|
|
1185
1246
|
'open', 'novalidate', 'formnovalidate', 'inert'
|
|
1186
1247
|
]);
|
|
1187
1248
|
|
|
1249
|
+
// Properties whose live state stops following the attribute once the user
|
|
1250
|
+
// has touched the control (the "dirty" flag), so an attribute-only write
|
|
1251
|
+
// leaves a stale control. `muted` and `indeterminate` have no attribute at all.
|
|
1252
|
+
var PROPS = new Set(['checked', 'selected', 'muted', 'indeterminate', 'value']);
|
|
1253
|
+
|
|
1254
|
+
// Write a DOM property only when it differs. `checked`/`selected` writes
|
|
1255
|
+
// notify with a bubbling `faqir:model` — never a native `change`, which would
|
|
1256
|
+
// re-enter l-model's own listener and fire user `@change` handlers on a
|
|
1257
|
+
// store-driven write.
|
|
1258
|
+
function writeProp(el, name, value) {
|
|
1259
|
+
if (!(name in el) || el[name] === value) return;
|
|
1260
|
+
el[name] = value;
|
|
1261
|
+
if (name === 'checked' || name === 'selected') {
|
|
1262
|
+
el.dispatchEvent(new CustomEvent('faqir:model', { bubbles: true }));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1188
1266
|
function handleBind(el, dir, scope) {
|
|
1189
1267
|
var attrName = dir.arg;
|
|
1268
|
+
// What this binding's last run set on `class`/`style`. Both merge: a run
|
|
1269
|
+
// applies its own classes/declarations and removes only the ones its
|
|
1270
|
+
// previous run set and this one dropped, so static markup, `l-show` and
|
|
1271
|
+
// controller writes survive a re-run. [1.1F-04]
|
|
1272
|
+
var prev = {};
|
|
1190
1273
|
|
|
1191
1274
|
var cl = effect(function() {
|
|
1192
1275
|
var value = evaluate(dir.expression, scope, el);
|
|
1193
1276
|
|
|
1194
1277
|
if (attrName === 'class') {
|
|
1195
|
-
applyClassBinding(el, value);
|
|
1278
|
+
prev = applyClassBinding(el, value, prev);
|
|
1196
1279
|
} else if (attrName === 'style') {
|
|
1197
|
-
applyStyleBinding(el, value);
|
|
1280
|
+
prev = applyStyleBinding(el, value, prev);
|
|
1198
1281
|
} else if (BOOLEAN_ATTRS.has(attrName)) {
|
|
1199
1282
|
if (value) {
|
|
1200
1283
|
el.setAttribute(attrName, '');
|
|
@@ -1208,41 +1291,66 @@
|
|
|
1208
1291
|
el.setAttribute(attrName, String(value));
|
|
1209
1292
|
}
|
|
1210
1293
|
}
|
|
1294
|
+
if (PROPS.has(attrName)) {
|
|
1295
|
+
writeProp(el, attrName, attrName === 'value'
|
|
1296
|
+
? (value == null || value === false ? '' : String(value))
|
|
1297
|
+
: !!value);
|
|
1298
|
+
}
|
|
1211
1299
|
});
|
|
1212
1300
|
|
|
1213
1301
|
addCleanup(el, cl);
|
|
1214
1302
|
}
|
|
1215
1303
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
} else if (
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1304
|
+
// A `:class` value as { class: on }. Strings and arrays turn classes on; an
|
|
1305
|
+
// object entry sets its own, so a falsy one removes a class markup set.
|
|
1306
|
+
function classMap(v, out, on) {
|
|
1307
|
+
if (typeof v === 'string') {
|
|
1308
|
+
v.split(/\s+/).forEach(function(c) { if (c) out[c] = on; });
|
|
1309
|
+
} else if (Array.isArray(v)) {
|
|
1310
|
+
v.forEach(function(x) { if (x) classMap(x, out, true); });
|
|
1311
|
+
} else if (v && typeof v === 'object') {
|
|
1312
|
+
for (var k in v) classMap(k, out, !!v[k]);
|
|
1227
1313
|
}
|
|
1314
|
+
return out;
|
|
1228
1315
|
}
|
|
1229
1316
|
|
|
1230
|
-
function
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1317
|
+
function applyClassBinding(el, value, prev) {
|
|
1318
|
+
var next = classMap(value, {}, true), c;
|
|
1319
|
+
for (c in prev) if (prev[c] && !(c in next)) el.classList.remove(c);
|
|
1320
|
+
for (c in next) el.classList.toggle(c, next[c]);
|
|
1321
|
+
return next;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// A `:style` value as { property: [value, priority] }, or null to remove it.
|
|
1325
|
+
// A string is parsed by a detached element's CSSOM, which handles custom
|
|
1326
|
+
// properties, `!important`, shorthands and `;` inside `url()`.
|
|
1327
|
+
var styleScratch;
|
|
1328
|
+
function styleMap(v, out) {
|
|
1329
|
+
if (typeof v === 'string') {
|
|
1330
|
+
var s = (styleScratch || (styleScratch = document.createElement('div'))).style;
|
|
1331
|
+
s.cssText = v;
|
|
1332
|
+
for (var i = 0; i < s.length; i++) {
|
|
1333
|
+
out[s[i]] = [s.getPropertyValue(s[i]), s.getPropertyPriority(s[i])];
|
|
1334
|
+
}
|
|
1335
|
+
} else if (Array.isArray(v)) {
|
|
1336
|
+
v.forEach(function(x) { styleMap(x, out); });
|
|
1337
|
+
} else if (v && typeof v === 'object') {
|
|
1338
|
+
for (var k in v) {
|
|
1339
|
+
out[/^--/.test(k) ? k : k.replace(/[A-Z]/g, '-$&').toLowerCase()] =
|
|
1340
|
+
v[k] == null || v[k] === false ? null : [String(v[k]), ''];
|
|
1244
1341
|
}
|
|
1245
1342
|
}
|
|
1343
|
+
return out;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function applyStyleBinding(el, value, prev) {
|
|
1347
|
+
var next = styleMap(value, {}), p;
|
|
1348
|
+
for (p in prev) if (prev[p] && !(p in next)) el.style.removeProperty(p);
|
|
1349
|
+
for (p in next) {
|
|
1350
|
+
if (next[p]) el.style.setProperty(p, next[p][0], next[p][1]);
|
|
1351
|
+
else el.style.removeProperty(p);
|
|
1352
|
+
}
|
|
1353
|
+
return next;
|
|
1246
1354
|
}
|
|
1247
1355
|
|
|
1248
1356
|
// --- 3.9 l-on / @event ---
|
|
@@ -1344,13 +1452,17 @@
|
|
|
1344
1452
|
|
|
1345
1453
|
function handleRef(el, dir, scope) {
|
|
1346
1454
|
var name = dir.expression;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1455
|
+
// Written through the reactive proxy, so an effect that read `$refs.name`
|
|
1456
|
+
// re-runs when the ref appears or goes. [1.1F-01]
|
|
1457
|
+
var refs = reactive(getScopeRefs(scopeRoots.get(scope) || findScopeRoot(el)));
|
|
1458
|
+
refs[name] = el;
|
|
1350
1459
|
|
|
1351
1460
|
addCleanup(el, function() {
|
|
1352
|
-
|
|
1353
|
-
|
|
1461
|
+
// `__target`: a tracked read here would subscribe whichever structural
|
|
1462
|
+
// effect is tearing this element down.
|
|
1463
|
+
if (refs.__target[name] === el) {
|
|
1464
|
+
refs[name] = undefined;
|
|
1465
|
+
delete refs.__target[name];
|
|
1354
1466
|
}
|
|
1355
1467
|
});
|
|
1356
1468
|
}
|
|
@@ -1358,29 +1470,24 @@
|
|
|
1358
1470
|
// --- 3.11 l-init ---
|
|
1359
1471
|
|
|
1360
1472
|
function handleInit(el, dir, scope) {
|
|
1361
|
-
evaluateAssignment(dir.expression, scope, el);
|
|
1473
|
+
runInit(function() { evaluateAssignment(dir.expression, scope, el); });
|
|
1362
1474
|
}
|
|
1363
1475
|
|
|
1364
1476
|
// --- 3.12 l-effect ---
|
|
1365
1477
|
|
|
1366
1478
|
function handleEffect(el, dir, scope) {
|
|
1367
|
-
|
|
1368
|
-
|
|
1479
|
+
runInit(function() {
|
|
1480
|
+
addCleanup(el, effect(function() {
|
|
1481
|
+
evaluateAssignment(dir.expression, scope, el);
|
|
1482
|
+
}));
|
|
1369
1483
|
});
|
|
1370
|
-
addCleanup(el, cl);
|
|
1371
1484
|
}
|
|
1372
1485
|
|
|
1373
1486
|
// --- 3.13 l-cloak ---
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
if (document.querySelector('style[data-faqir-cloak]')) return;
|
|
1379
|
-
var style = document.createElement('style');
|
|
1380
|
-
style.setAttribute('data-faqir-cloak', '');
|
|
1381
|
-
style.textContent = '[l-cloak] { display: none !important; }';
|
|
1382
|
-
document.head.appendChild(style);
|
|
1383
|
-
}
|
|
1487
|
+
//
|
|
1488
|
+
// The engine injects no stylesheet. The `[l-cloak]` rule ships in
|
|
1489
|
+
// `base/reset.css` (so in every bundle); the injected copy it used to append
|
|
1490
|
+
// at boot was redundant, and the only inline style a CSP had to allow. [1.1F-07]
|
|
1384
1491
|
|
|
1385
1492
|
/**
|
|
1386
1493
|
* Strip `l-cloak` from `within` and everything under it (the whole document
|
|
@@ -1389,7 +1496,7 @@
|
|
|
1389
1496
|
* The sweep used to run exactly once, at the end of bootstrap, against a
|
|
1390
1497
|
* document-wide selector. Anything inserted afterwards — an `l-if` branch, an
|
|
1391
1498
|
* `l-for` row, a fragment an application appended — kept the attribute, and
|
|
1392
|
-
* the
|
|
1499
|
+
* the `[l-cloak] { display: none !important }` rule then hid it
|
|
1393
1500
|
* permanently: content that had bound correctly and could never be seen. The
|
|
1394
1501
|
* MutationObserver runs this over every node it is handed. [W3-1]
|
|
1395
1502
|
*/
|
|
@@ -1546,7 +1653,7 @@
|
|
|
1546
1653
|
if (isFaqirSwitch) {
|
|
1547
1654
|
var cl = effect(function() {
|
|
1548
1655
|
var value = evaluate(prop, scope, el);
|
|
1549
|
-
el
|
|
1656
|
+
writeProp(el, 'checked', !!value);
|
|
1550
1657
|
el.dataset.state = value ? 'on' : 'off';
|
|
1551
1658
|
el.setAttribute('aria-checked', value ? 'true' : 'false');
|
|
1552
1659
|
});
|
|
@@ -1558,11 +1665,9 @@
|
|
|
1558
1665
|
} else if (tag === 'input' && type === 'checkbox') {
|
|
1559
1666
|
var cl = effect(function() {
|
|
1560
1667
|
var current = evaluate(prop, scope, el);
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
el.checked = !!current;
|
|
1565
|
-
}
|
|
1668
|
+
writeProp(el, 'checked', Array.isArray(current)
|
|
1669
|
+
? current.indexOf(el.value) >= 0
|
|
1670
|
+
: !!current);
|
|
1566
1671
|
});
|
|
1567
1672
|
bind('change', function() {
|
|
1568
1673
|
var current = evaluate(prop, scope, el);
|
|
@@ -1580,7 +1685,7 @@
|
|
|
1580
1685
|
|
|
1581
1686
|
} else if (tag === 'input' && type === 'radio') {
|
|
1582
1687
|
var cl = effect(function() {
|
|
1583
|
-
el
|
|
1688
|
+
writeProp(el, 'checked', evaluate(prop, scope, el) === el.value);
|
|
1584
1689
|
});
|
|
1585
1690
|
bind('change', function() {
|
|
1586
1691
|
if (el.checked) {
|
|
@@ -1591,7 +1696,7 @@
|
|
|
1591
1696
|
|
|
1592
1697
|
} else if (tag === 'select') {
|
|
1593
1698
|
var cl = effect(function() {
|
|
1594
|
-
el
|
|
1699
|
+
writeProp(el, 'value', evaluate(prop, scope, el) || '');
|
|
1595
1700
|
});
|
|
1596
1701
|
bind('change', function() {
|
|
1597
1702
|
writeModel(prop, el.value, scope, el);
|
|
@@ -1684,7 +1789,7 @@
|
|
|
1684
1789
|
var value = evaluate(dir.expression, scope, el);
|
|
1685
1790
|
|
|
1686
1791
|
if (value) {
|
|
1687
|
-
if (insertedNodes.length === 0) {
|
|
1792
|
+
if (insertedNodes.length === 0) renderThenInit(function() {
|
|
1688
1793
|
var fragment = el.content.cloneNode(true);
|
|
1689
1794
|
var nodes = [].slice.call(fragment.childNodes);
|
|
1690
1795
|
|
|
@@ -1727,7 +1832,8 @@
|
|
|
1727
1832
|
runEnterTransition(insertedNodes[i]);
|
|
1728
1833
|
}
|
|
1729
1834
|
}
|
|
1730
|
-
|
|
1835
|
+
return insertedNodes;
|
|
1836
|
+
});
|
|
1731
1837
|
} else {
|
|
1732
1838
|
for (var i = 0; i < insertedNodes.length; i++) {
|
|
1733
1839
|
var node = insertedNodes[i];
|
|
@@ -1843,6 +1949,8 @@
|
|
|
1843
1949
|
var indexName = match[2] || 'index';
|
|
1844
1950
|
var listExpr = match[3];
|
|
1845
1951
|
var keyExpr = el.getAttribute('l-key');
|
|
1952
|
+
// Dev hint: a loop variable that shadows a name the scope already has. Only
|
|
1953
|
+
// names the author wrote; `in` reads no reactive dependency. [1.1F-05]
|
|
1846
1954
|
|
|
1847
1955
|
var anchor = document.createComment('l-for');
|
|
1848
1956
|
el.parentNode.insertBefore(anchor, el);
|
|
@@ -1877,6 +1985,7 @@
|
|
|
1877
1985
|
own[itemName] = item;
|
|
1878
1986
|
own[indexName] = i;
|
|
1879
1987
|
var childScope = createForItemScope(own, scope);
|
|
1988
|
+
scopeRoots.set(childScope, scopeRoots.get(scope));
|
|
1880
1989
|
for (var j = 0; j < nodes.length; j++) {
|
|
1881
1990
|
if (nodes[j].nodeType !== 1) continue;
|
|
1882
1991
|
// A row whose top node declares `l-data` is a scope root: build it with
|
|
@@ -1901,7 +2010,9 @@
|
|
|
1901
2010
|
|
|
1902
2011
|
var currentEntries = [];
|
|
1903
2012
|
|
|
1904
|
-
|
|
2013
|
+
// One reconciliation. Returns the rows it built, for `renderThenInit` to
|
|
2014
|
+
// start their controllers and run their held inits. [1.1F-03]
|
|
2015
|
+
function renderList() {
|
|
1905
2016
|
var list = evaluate(listExpr, scope, el);
|
|
1906
2017
|
var items = Array.isArray(list) ? list :
|
|
1907
2018
|
typeof list === 'number' ? Array.from({ length: list }, function(_, i) { return i + 1; }) :
|
|
@@ -1974,7 +2085,12 @@
|
|
|
1974
2085
|
}
|
|
1975
2086
|
|
|
1976
2087
|
currentEntries = newEntries;
|
|
1977
|
-
|
|
2088
|
+
var fresh = [];
|
|
2089
|
+
for (var i = 0; i < n; i++) if (!source[i]) fresh.push.apply(fresh, newEntries[i].nodes);
|
|
2090
|
+
return fresh;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
var cl = effect(function() { renderThenInit(renderList); });
|
|
1978
2094
|
|
|
1979
2095
|
addCleanup(el, cl, anchor);
|
|
1980
2096
|
}
|
|
@@ -2113,41 +2229,20 @@
|
|
|
2113
2229
|
// ═══════════════════════════════════════════════════════
|
|
2114
2230
|
|
|
2115
2231
|
// --- From dom.js ---
|
|
2116
|
-
var $ = function(selector, scope) { return (scope || document).querySelector(selector); };
|
|
2117
2232
|
var $$ = function(selector, scope) { return [].slice.call((scope || document).querySelectorAll(selector)); };
|
|
2118
2233
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
for (var i = 0; i < keys.length; i++) {
|
|
2124
|
-
el.setAttribute(keys[i], attrs[keys[i]]);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
for (var j = 2; j < arguments.length; j++) {
|
|
2128
|
-
var child = arguments[j];
|
|
2129
|
-
el.append(typeof child === 'string' ? document.createTextNode(child) : child);
|
|
2130
|
-
}
|
|
2131
|
-
return el;
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
// --- From events.js ---
|
|
2135
|
-
function delegate(root, event, selector, handler) {
|
|
2136
|
-
function listener(e) {
|
|
2137
|
-
var target = e.target.closest(selector);
|
|
2138
|
-
if (target && root.contains(target)) handler(e, target);
|
|
2139
|
-
}
|
|
2140
|
-
root.addEventListener(event, listener);
|
|
2141
|
-
return function() { root.removeEventListener(event, listener); };
|
|
2142
|
-
}
|
|
2234
|
+
// A part's owner is its nearest [data-ui] above it that is not a layout
|
|
2235
|
+
// primitive, starting from the parent since a part may carry data-ui itself.
|
|
2236
|
+
// Same selector as registry/core/dom.js `owns`.
|
|
2237
|
+
var OWNER = '[data-ui]:not([data-ui=stack],[data-ui=cluster],[data-ui=grid],[data-ui=container],[data-ui=surface],[data-ui=switcher],[data-ui=aspect-ratio])';
|
|
2143
2238
|
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
return cleanupOnce;
|
|
2239
|
+
function ownParts(root, part) {
|
|
2240
|
+
return $$("[data-part='" + part + "']", root).filter(function(el) {
|
|
2241
|
+
return el.parentElement.closest(OWNER) === root;
|
|
2242
|
+
});
|
|
2149
2243
|
}
|
|
2150
2244
|
|
|
2245
|
+
// --- From events.js ---
|
|
2151
2246
|
function onOutsideClick(el, handler) {
|
|
2152
2247
|
function listener(e) { if (!el.contains(e.target)) handler(e); }
|
|
2153
2248
|
document.addEventListener('pointerdown', listener);
|
|
@@ -2192,25 +2287,14 @@
|
|
|
2192
2287
|
return function() { container.removeEventListener('keydown', onKeyDown); };
|
|
2193
2288
|
}
|
|
2194
2289
|
|
|
2195
|
-
//
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
(style.animationName !== 'none' && parseFloat(style.animationDuration) > 0);
|
|
2201
|
-
if (!hasDuration) return Promise.resolve();
|
|
2202
|
-
return new Promise(function(resolve) {
|
|
2203
|
-
function done(e) {
|
|
2204
|
-
if (e.target !== el) return;
|
|
2205
|
-
el.removeEventListener('transitionend', done);
|
|
2206
|
-
el.removeEventListener('animationend', done);
|
|
2207
|
-
resolve();
|
|
2208
|
-
}
|
|
2209
|
-
el.addEventListener('transitionend', done);
|
|
2210
|
-
el.addEventListener('animationend', done);
|
|
2211
|
-
});
|
|
2290
|
+
// Back to the opener once an overlay has closed, unless focus moved on while
|
|
2291
|
+
// it closed (`active` was read before the panel hid) or the opener is gone.
|
|
2292
|
+
function returnFocus(root, target, active) {
|
|
2293
|
+
if ((!active || active === document.body || root.contains(active)) &&
|
|
2294
|
+
target && target.isConnected && !target.closest('[hidden]')) target.focus();
|
|
2212
2295
|
}
|
|
2213
2296
|
|
|
2297
|
+
// --- From motion.js (prefersReducedMotion already defined in Section 3.14) ---
|
|
2214
2298
|
// The four events an exit can end with, in one list: bound and unbound
|
|
2215
2299
|
// together, so neither half can drift from the other.
|
|
2216
2300
|
var EXIT_EVENTS = ['transitionend', 'transitioncancel', 'animationend', 'animationcancel'];
|
|
@@ -2513,7 +2597,8 @@ function createAccordion(root) {
|
|
|
2513
2597
|
// Prevent double-init
|
|
2514
2598
|
if (root._faqirAccordion) return root._faqirAccordion;
|
|
2515
2599
|
|
|
2516
|
-
|
|
2600
|
+
// Direct children only: a nested accordion's items are its own.
|
|
2601
|
+
const getItems = () => [...root.querySelectorAll(":scope > [data-part='item']")];
|
|
2517
2602
|
const isSingle = () => root.dataset.variant === "single";
|
|
2518
2603
|
|
|
2519
2604
|
function expand(index) {
|
|
@@ -2573,26 +2658,30 @@ function createAccordion(root) {
|
|
|
2573
2658
|
if (content) content.hidden = true;
|
|
2574
2659
|
}
|
|
2575
2660
|
|
|
2576
|
-
|
|
2661
|
+
/**
|
|
2662
|
+
* The index of the item whose own trigger `e` came from, or -1. A trigger of
|
|
2663
|
+
* a component nested in the content (a dropdown, another accordion) is not
|
|
2664
|
+
* the item's: an item's own trigger is the first one in it, since it comes
|
|
2665
|
+
* before the content.
|
|
2666
|
+
*/
|
|
2667
|
+
function triggerIndex(e) {
|
|
2577
2668
|
const trigger = e.target.closest("[data-part='trigger']");
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2669
|
+
const item = trigger?.closest("[data-part='item']");
|
|
2670
|
+
if (!item || item.querySelector("[data-part='trigger']") !== trigger) return -1;
|
|
2671
|
+
return getItems().indexOf(item);
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function onTriggerClick(e) {
|
|
2675
|
+
const index = triggerIndex(e);
|
|
2583
2676
|
if (index >= 0) toggle(index);
|
|
2584
2677
|
}
|
|
2585
2678
|
|
|
2586
2679
|
function onKeyDown(e) {
|
|
2587
2680
|
if (e.key === "Enter" || e.key === " ") {
|
|
2588
|
-
const
|
|
2589
|
-
if (
|
|
2681
|
+
const index = triggerIndex(e);
|
|
2682
|
+
if (index >= 0) {
|
|
2590
2683
|
e.preventDefault();
|
|
2591
|
-
|
|
2592
|
-
if (!item) return;
|
|
2593
|
-
const items = getItems();
|
|
2594
|
-
const index = items.indexOf(item);
|
|
2595
|
-
if (index >= 0) toggle(index);
|
|
2684
|
+
toggle(index);
|
|
2596
2685
|
}
|
|
2597
2686
|
}
|
|
2598
2687
|
}
|
|
@@ -3367,12 +3456,14 @@ function createCarousel(root) {
|
|
|
3367
3456
|
if (!viewport) return null;
|
|
3368
3457
|
|
|
3369
3458
|
const loop = root.hasAttribute("data-loop");
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
const
|
|
3373
|
-
const
|
|
3374
|
-
const
|
|
3375
|
-
const
|
|
3459
|
+
// A carousel nested in a slide brings its own slides, buttons and dots:
|
|
3460
|
+
// slides are the viewport's children, the rest go through the owner guard.
|
|
3461
|
+
const slides = () => [...viewport.querySelectorAll(":scope > [data-part='slide']")];
|
|
3462
|
+
const dots = () => ownParts(root, "dot");
|
|
3463
|
+
const [prevBtn] = ownParts(root, "prev");
|
|
3464
|
+
const [nextBtn] = ownParts(root, "next");
|
|
3465
|
+
const [status] = ownParts(root, "status");
|
|
3466
|
+
const enhanced = [...ownParts(root, "controls"), ...ownParts(root, "dots")];
|
|
3376
3467
|
|
|
3377
3468
|
let index = 0;
|
|
3378
3469
|
|
|
@@ -3468,12 +3559,13 @@ function createCarousel(root) {
|
|
|
3468
3559
|
|
|
3469
3560
|
// ── events ──────────────────────────────────────────────────────────────────
|
|
3470
3561
|
function onClick(e) {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
const
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
else if (
|
|
3562
|
+
// Matched by identity, so a nested carousel's buttons are not these. A
|
|
3563
|
+
// missing button is `undefined`, never equal to a `null` miss here.
|
|
3564
|
+
const btn = e.target.closest("[data-part]");
|
|
3565
|
+
const dot = dots().indexOf(btn);
|
|
3566
|
+
if (btn === nextBtn) next();
|
|
3567
|
+
else if (btn === prevBtn) prev();
|
|
3568
|
+
else if (dot >= 0) goTo(dot);
|
|
3477
3569
|
}
|
|
3478
3570
|
|
|
3479
3571
|
// One rAF-throttled measurement per scroll burst keeps tracking cheap.
|
|
@@ -3824,8 +3916,11 @@ function createCommandPalette(root) {
|
|
|
3824
3916
|
}
|
|
3825
3917
|
|
|
3826
3918
|
function close() {
|
|
3919
|
+
const active = document.activeElement;
|
|
3827
3920
|
dismiss();
|
|
3828
|
-
|
|
3921
|
+
// The opener may have left with another overlay — the downstream palette
|
|
3922
|
+
// was opened from inside a dialog that closed underneath it.
|
|
3923
|
+
returnFocus(root, previouslyFocused, active);
|
|
3829
3924
|
}
|
|
3830
3925
|
|
|
3831
3926
|
/**
|
|
@@ -4019,14 +4114,29 @@ function createCommandPalette(root) {
|
|
|
4019
4114
|
}
|
|
4020
4115
|
}
|
|
4021
4116
|
|
|
4117
|
+
/**
|
|
4118
|
+
* Cmd/Ctrl+K — one owner per press.
|
|
4119
|
+
*
|
|
4120
|
+
* Every instance listens on `document`, so a page with three palettes used to
|
|
4121
|
+
* open three stacked focus traps. The first instance to act prevents default
|
|
4122
|
+
* and every later one (or one an app handler pre-empted) stands down;
|
|
4123
|
+
* `data-no-shortcut` opts an instance out entirely, read live, and a palette
|
|
4124
|
+
* whose root has left the document without `destroy()` (an SPA route change)
|
|
4125
|
+
* no longer claims the press from the live one. The key is
|
|
4126
|
+
* matched case-insensitively (Caps Lock sends "K"), falling back to the
|
|
4127
|
+
* physical key on a non-Latin layout; Shift is left alone because Cmd+Shift+K
|
|
4128
|
+
* is a browser and app chord of its own.
|
|
4129
|
+
*/
|
|
4022
4130
|
function onGlobalKeyDown(e) {
|
|
4023
|
-
if (
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4131
|
+
if (e.defaultPrevented || !root.isConnected || root.hasAttribute("data-no-shortcut")) return;
|
|
4132
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey || e.shiftKey || e.repeat || e.isComposing) return;
|
|
4133
|
+
const k = (e.key || "").toLowerCase();
|
|
4134
|
+
if (k !== "k" && (/^[a-z]$/.test(k) || e.code !== "KeyK")) return;
|
|
4135
|
+
e.preventDefault();
|
|
4136
|
+
if (root.dataset.state === "open") {
|
|
4137
|
+
close();
|
|
4138
|
+
} else {
|
|
4139
|
+
open();
|
|
4030
4140
|
}
|
|
4031
4141
|
}
|
|
4032
4142
|
|
|
@@ -4062,8 +4172,10 @@ function createCommandPalette(root) {
|
|
|
4062
4172
|
function createContextMenu(root) {
|
|
4063
4173
|
if (root._faqirContextMenu) return root._faqirContextMenu;
|
|
4064
4174
|
|
|
4065
|
-
|
|
4066
|
-
|
|
4175
|
+
// Matched by owner: a dropdown inside the target has a `menu` part too, and
|
|
4176
|
+
// it comes first in document order.
|
|
4177
|
+
const [target] = ownParts(root, "target");
|
|
4178
|
+
const [menu] = ownParts(root, "menu");
|
|
4067
4179
|
|
|
4068
4180
|
// Same as `dropdown`: a missing part must name itself rather than throwing a
|
|
4069
4181
|
// TypeError out of `createMenuNavigation(null)`. [W3-2]
|
|
@@ -4367,17 +4479,24 @@ function createDialog(root) {
|
|
|
4367
4479
|
// Prevent double-init
|
|
4368
4480
|
if (root._faqirDialog) return root._faqirDialog;
|
|
4369
4481
|
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
const
|
|
4374
|
-
const
|
|
4375
|
-
const
|
|
4482
|
+
// Overlay and panel are direct children. Trigger and actions may be wrapped
|
|
4483
|
+
// in layout primitives, so they are matched by owner: a popover's close
|
|
4484
|
+
// button inside the panel belongs to the popover and must not close this.
|
|
4485
|
+
const [trigger] = ownParts(root, "trigger");
|
|
4486
|
+
const overlay = root.querySelector(":scope > [data-part='overlay']");
|
|
4487
|
+
const panel = root.querySelector(":scope > [data-part='panel']");
|
|
4488
|
+
const closeButtons = ownParts(root, "close");
|
|
4489
|
+
const confirmButtons = ownParts(root, "confirm");
|
|
4490
|
+
const cancelButtons = ownParts(root, "cancel");
|
|
4376
4491
|
|
|
4377
4492
|
// The role is the seam between `dialog` and `alert-dialog` — read it from the
|
|
4378
4493
|
// markup so a single controller serves both recipes.
|
|
4379
4494
|
const isAlert = !!panel && panel.getAttribute("role") === "alertdialog";
|
|
4380
4495
|
|
|
4496
|
+
// `panel.focus()` on open does nothing without a tabindex: focus stayed on
|
|
4497
|
+
// `body`, out of reach of the trap and the root's Escape listener.
|
|
4498
|
+
if (panel && !panel.hasAttribute("tabindex")) panel.setAttribute("tabindex", "-1");
|
|
4499
|
+
|
|
4381
4500
|
let focusCleanup = null;
|
|
4382
4501
|
let previouslyFocused = null;
|
|
4383
4502
|
|
|
@@ -4400,10 +4519,7 @@ function createDialog(root) {
|
|
|
4400
4519
|
/** On open, focus the least-destructive action for an alert, else the panel. */
|
|
4401
4520
|
function focusInitial() {
|
|
4402
4521
|
if (isAlert) {
|
|
4403
|
-
const target =
|
|
4404
|
-
root.querySelector("[data-part='cancel']") ||
|
|
4405
|
-
root.querySelector("[data-part='close']") ||
|
|
4406
|
-
panel;
|
|
4522
|
+
const target = cancelButtons[0] || closeButtons[0] || panel;
|
|
4407
4523
|
target?.focus?.();
|
|
4408
4524
|
} else {
|
|
4409
4525
|
panel?.focus?.();
|
|
@@ -4437,12 +4553,13 @@ function createDialog(root) {
|
|
|
4437
4553
|
|
|
4438
4554
|
const onEnd = () => {
|
|
4439
4555
|
cancelExitWait = null;
|
|
4556
|
+
const active = document.activeElement;
|
|
4440
4557
|
root.dataset.state = "closed";
|
|
4441
4558
|
overlay.hidden = true;
|
|
4442
4559
|
panel.hidden = true;
|
|
4443
4560
|
if (focusCleanup) focusCleanup();
|
|
4444
4561
|
focusCleanup = null;
|
|
4445
|
-
previouslyFocused
|
|
4562
|
+
returnFocus(root, previouslyFocused, active);
|
|
4446
4563
|
};
|
|
4447
4564
|
|
|
4448
4565
|
// The panel's own exit motion. A footer button's `background` transitionend
|
|
@@ -4575,10 +4692,16 @@ function createDrawer(root) {
|
|
|
4575
4692
|
// Prevent double-init
|
|
4576
4693
|
if (root._faqirDrawer) return root._faqirDrawer;
|
|
4577
4694
|
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
const
|
|
4581
|
-
const
|
|
4695
|
+
// Overlay and panel are direct children; trigger and close buttons are
|
|
4696
|
+
// matched by owner, so a nested component's close leaves this one alone.
|
|
4697
|
+
const [trigger] = ownParts(root, "trigger");
|
|
4698
|
+
const overlay = root.querySelector(":scope > [data-part='overlay']");
|
|
4699
|
+
const panel = root.querySelector(":scope > [data-part='panel']");
|
|
4700
|
+
const closeButtons = ownParts(root, "close");
|
|
4701
|
+
|
|
4702
|
+
// `panel.focus()` on open does nothing without a tabindex: focus stayed on
|
|
4703
|
+
// `body`, out of reach of the trap and the root's Escape listener.
|
|
4704
|
+
if (panel && !panel.hasAttribute("tabindex")) panel.setAttribute("tabindex", "-1");
|
|
4582
4705
|
|
|
4583
4706
|
let focusCleanup = null;
|
|
4584
4707
|
let previouslyFocused = null;
|
|
@@ -4622,13 +4745,14 @@ function createDrawer(root) {
|
|
|
4622
4745
|
|
|
4623
4746
|
const onEnd = () => {
|
|
4624
4747
|
cancelExitWait = null;
|
|
4748
|
+
const active = document.activeElement;
|
|
4625
4749
|
root.dataset.state = "closed";
|
|
4626
4750
|
overlay.hidden = true;
|
|
4627
4751
|
panel.hidden = true;
|
|
4628
4752
|
unlockScroll();
|
|
4629
4753
|
if (focusCleanup) focusCleanup();
|
|
4630
4754
|
focusCleanup = null;
|
|
4631
|
-
previouslyFocused
|
|
4755
|
+
returnFocus(root, previouslyFocused, active);
|
|
4632
4756
|
};
|
|
4633
4757
|
|
|
4634
4758
|
// The panel's OWN `transform` — not the close button's `background`, which
|
|
@@ -6103,7 +6227,8 @@ function createPopover(root) {
|
|
|
6103
6227
|
|
|
6104
6228
|
const trigger = root.querySelector("[data-part='trigger']");
|
|
6105
6229
|
const content = root.querySelector("[data-part='content']");
|
|
6106
|
-
|
|
6230
|
+
// Not a close button belonging to a dialog or popover inside the content.
|
|
6231
|
+
const [closeBtn] = ownParts(root, "close");
|
|
6107
6232
|
|
|
6108
6233
|
let outsideClickCleanup = null;
|
|
6109
6234
|
|
|
@@ -6995,10 +7120,16 @@ function createSheet(root) {
|
|
|
6995
7120
|
// Prevent double-init
|
|
6996
7121
|
if (root._faqirSheet) return root._faqirSheet;
|
|
6997
7122
|
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
const
|
|
7001
|
-
const
|
|
7123
|
+
// Overlay and panel are direct children; trigger and close buttons are
|
|
7124
|
+
// matched by owner, so a nested component's close leaves this one alone.
|
|
7125
|
+
const [trigger] = ownParts(root, "trigger");
|
|
7126
|
+
const overlay = root.querySelector(":scope > [data-part='overlay']");
|
|
7127
|
+
const panel = root.querySelector(":scope > [data-part='panel']");
|
|
7128
|
+
const closeButtons = ownParts(root, "close");
|
|
7129
|
+
|
|
7130
|
+
// `panel.focus()` on open does nothing without a tabindex: focus stayed on
|
|
7131
|
+
// `body`, out of reach of the trap and the root's Escape listener.
|
|
7132
|
+
if (panel && !panel.hasAttribute("tabindex")) panel.setAttribute("tabindex", "-1");
|
|
7002
7133
|
|
|
7003
7134
|
let focusCleanup = null;
|
|
7004
7135
|
let previouslyFocused = null;
|
|
@@ -7042,13 +7173,14 @@ function createSheet(root) {
|
|
|
7042
7173
|
|
|
7043
7174
|
const onEnd = () => {
|
|
7044
7175
|
cancelExitWait = null;
|
|
7176
|
+
const active = document.activeElement;
|
|
7045
7177
|
root.dataset.state = "closed";
|
|
7046
7178
|
overlay.hidden = true;
|
|
7047
7179
|
panel.hidden = true;
|
|
7048
7180
|
unlockScroll();
|
|
7049
7181
|
if (focusCleanup) focusCleanup();
|
|
7050
7182
|
focusCleanup = null;
|
|
7051
|
-
previouslyFocused
|
|
7183
|
+
returnFocus(root, previouslyFocused, active);
|
|
7052
7184
|
};
|
|
7053
7185
|
|
|
7054
7186
|
// The panel's OWN `transform` — not a close button's `background`, which
|
|
@@ -7158,12 +7290,14 @@ function createSidebar(root) {
|
|
|
7158
7290
|
// Prevent double-init.
|
|
7159
7291
|
if (root._faqirSidebar) return root._faqirSidebar;
|
|
7160
7292
|
|
|
7161
|
-
const overlay = root.querySelector("[data-part='overlay']");
|
|
7162
|
-
const panel = root.querySelector("[data-part='panel']") || root;
|
|
7293
|
+
const overlay = root.querySelector(":scope > [data-part='overlay']");
|
|
7294
|
+
const panel = root.querySelector(":scope > [data-part='panel']") || root;
|
|
7163
7295
|
|
|
7164
|
-
// Toggle buttons:
|
|
7165
|
-
// triggers in the app shell that point at this sidebar by id. Both call
|
|
7166
|
-
|
|
7296
|
+
// Toggle buttons: the sidebar's own `[data-part='trigger']`s, plus external
|
|
7297
|
+
// triggers in the app shell that point at this sidebar by id. Both call
|
|
7298
|
+
// toggle(). A dropdown's trigger in the footer belongs to the dropdown: it
|
|
7299
|
+
// used to have its aria-expanded forced and its click rail the sidebar.
|
|
7300
|
+
const internalTriggers = ownParts(root, "trigger");
|
|
7167
7301
|
const externalTriggers = root.id
|
|
7168
7302
|
? [...document.querySelectorAll(`[data-sidebar-toggle="${root.id}"]`)]
|
|
7169
7303
|
: [];
|
|
@@ -7773,10 +7907,13 @@ function createTable(root) {
|
|
|
7773
7907
|
// Prevent double-init
|
|
7774
7908
|
if (root._faqirTable) return root._faqirTable;
|
|
7775
7909
|
|
|
7910
|
+
// A table nested in a detail row keeps its own parts and events: `tbody`
|
|
7911
|
+
// contains it, so every deep lookup and delegated handler checks `mine`.
|
|
7912
|
+
const mine = (el) => el.closest("[data-ui='table']") === root;
|
|
7776
7913
|
const table = root.querySelector("[data-part='table']");
|
|
7777
7914
|
const thead = root.querySelector("[data-part='thead']");
|
|
7778
7915
|
const tbody = root.querySelector("[data-part='tbody']");
|
|
7779
|
-
const tfoot = root.
|
|
7916
|
+
const tfoot = [...root.querySelectorAll("[data-part='tfoot']")].find(mine);
|
|
7780
7917
|
const headerCheckbox = thead?.querySelector("[data-part='checkbox']");
|
|
7781
7918
|
|
|
7782
7919
|
// ── Options (read once from the root's opt-in attributes) ──
|
|
@@ -7996,23 +8133,14 @@ function createTable(root) {
|
|
|
7996
8133
|
|
|
7997
8134
|
// ── Intl formatting ──
|
|
7998
8135
|
function formatValue(n, fmt) {
|
|
8136
|
+
const o =
|
|
8137
|
+
fmt === "currency"
|
|
8138
|
+
? { style: fmt, currency: opts.currency, currencySign: accounting ? "accounting" : "standard" }
|
|
8139
|
+
: fmt === "percent"
|
|
8140
|
+
? { style: fmt, maximumFractionDigits: 2 }
|
|
8141
|
+
: fmt === "number" && { maximumFractionDigits: 2 };
|
|
7999
8142
|
try {
|
|
8000
|
-
if (fmt === "
|
|
8001
|
-
return new Intl.NumberFormat(opts.locale, {
|
|
8002
|
-
style: "currency",
|
|
8003
|
-
currency: opts.currency,
|
|
8004
|
-
currencySign: accounting ? "accounting" : "standard",
|
|
8005
|
-
}).format(n);
|
|
8006
|
-
}
|
|
8007
|
-
if (fmt === "percent") {
|
|
8008
|
-
return new Intl.NumberFormat(opts.locale, {
|
|
8009
|
-
style: "percent",
|
|
8010
|
-
maximumFractionDigits: 2,
|
|
8011
|
-
}).format(n / 100);
|
|
8012
|
-
}
|
|
8013
|
-
if (fmt === "number") {
|
|
8014
|
-
return new Intl.NumberFormat(opts.locale, { maximumFractionDigits: 2 }).format(n);
|
|
8015
|
-
}
|
|
8143
|
+
if (o) return new Intl.NumberFormat(opts.locale, o).format(fmt === "percent" ? n / 100 : n);
|
|
8016
8144
|
} catch {
|
|
8017
8145
|
/* unknown locale/currency — fall through to raw */
|
|
8018
8146
|
}
|
|
@@ -8253,13 +8381,17 @@ function createTable(root) {
|
|
|
8253
8381
|
refreshStripes();
|
|
8254
8382
|
}
|
|
8255
8383
|
|
|
8384
|
+
function sortChanged() {
|
|
8385
|
+
applySorts();
|
|
8386
|
+
emit("sort", { sorts: sorts.map((s) => ({ column: s.index, direction: s.dir })) });
|
|
8387
|
+
scheduleStateSave();
|
|
8388
|
+
}
|
|
8389
|
+
|
|
8256
8390
|
/** Legacy API: sort one column with an explicit direction. */
|
|
8257
8391
|
function sort(columnIndex, direction) {
|
|
8258
8392
|
if (!tbody || bodyRows().length === 0) return;
|
|
8259
8393
|
sorts = [{ index: columnIndex, dir: direction }];
|
|
8260
|
-
|
|
8261
|
-
emit("sort", { sorts: sorts.map((s) => ({ column: s.index, direction: s.dir })) });
|
|
8262
|
-
scheduleStateSave();
|
|
8394
|
+
sortChanged();
|
|
8263
8395
|
}
|
|
8264
8396
|
|
|
8265
8397
|
/** Multi-sort API: sortBy([{ column, direction }...]). */
|
|
@@ -8271,17 +8403,13 @@ function createTable(root) {
|
|
|
8271
8403
|
dir: s.direction || s.dir || "ascending",
|
|
8272
8404
|
}))
|
|
8273
8405
|
.filter((s) => Number.isInteger(s.index) && s.index >= 0 && s.index < columnCount());
|
|
8274
|
-
|
|
8275
|
-
emit("sort", { sorts: sorts.map((s) => ({ column: s.index, direction: s.dir })) });
|
|
8276
|
-
scheduleStateSave();
|
|
8406
|
+
sortChanged();
|
|
8277
8407
|
}
|
|
8278
8408
|
|
|
8279
8409
|
function clearSort() {
|
|
8280
8410
|
if (!sorts.length) return;
|
|
8281
8411
|
sorts = [];
|
|
8282
|
-
|
|
8283
|
-
emit("sort", { sorts: [] });
|
|
8284
|
-
scheduleStateSave();
|
|
8412
|
+
sortChanged();
|
|
8285
8413
|
}
|
|
8286
8414
|
|
|
8287
8415
|
function handleSortRequest(idx, additive) {
|
|
@@ -8298,9 +8426,7 @@ function createTable(root) {
|
|
|
8298
8426
|
} else {
|
|
8299
8427
|
sorts = dir === null ? [] : [{ index: idx, dir }];
|
|
8300
8428
|
}
|
|
8301
|
-
|
|
8302
|
-
emit("sort", { sorts: sorts.map((s) => ({ column: s.index, direction: s.dir })) });
|
|
8303
|
-
scheduleStateSave();
|
|
8429
|
+
sortChanged();
|
|
8304
8430
|
}
|
|
8305
8431
|
|
|
8306
8432
|
// ── Selection ──
|
|
@@ -8316,8 +8442,7 @@ function createTable(root) {
|
|
|
8316
8442
|
const row = rows[index];
|
|
8317
8443
|
setRowSelected(row, !row.hasAttribute("data-selected"));
|
|
8318
8444
|
lastSelectedIndex = index;
|
|
8319
|
-
|
|
8320
|
-
emit("selection-change", { selected: getSelected() });
|
|
8445
|
+
selectionChanged();
|
|
8321
8446
|
}
|
|
8322
8447
|
|
|
8323
8448
|
function selectAll() {
|
|
@@ -8326,15 +8451,13 @@ function createTable(root) {
|
|
|
8326
8451
|
if (filtered && row.hasAttribute("data-filtered")) continue;
|
|
8327
8452
|
setRowSelected(row, true);
|
|
8328
8453
|
}
|
|
8329
|
-
|
|
8330
|
-
emit("selection-change", { selected: getSelected() });
|
|
8454
|
+
selectionChanged();
|
|
8331
8455
|
}
|
|
8332
8456
|
|
|
8333
8457
|
function deselectAll() {
|
|
8334
8458
|
for (const row of bodyRows()) setRowSelected(row, false);
|
|
8335
8459
|
lastSelectedIndex = -1;
|
|
8336
|
-
|
|
8337
|
-
emit("selection-change", { selected: getSelected() });
|
|
8460
|
+
selectionChanged();
|
|
8338
8461
|
}
|
|
8339
8462
|
|
|
8340
8463
|
function getSelected() {
|
|
@@ -8351,23 +8474,19 @@ function createTable(root) {
|
|
|
8351
8474
|
for (let i = start; i <= end; i++) {
|
|
8352
8475
|
if (i >= 0 && i < rows.length && isRowVisible(rows[i])) setRowSelected(rows[i], true);
|
|
8353
8476
|
}
|
|
8477
|
+
}
|
|
8478
|
+
|
|
8479
|
+
function selectionChanged() {
|
|
8354
8480
|
updateHeaderCheckbox();
|
|
8481
|
+
emit("selection-change", { selected: getSelected() });
|
|
8355
8482
|
}
|
|
8356
8483
|
|
|
8357
8484
|
function updateHeaderCheckbox() {
|
|
8358
8485
|
if (!headerCheckbox) return;
|
|
8359
8486
|
const rows = visibleBodyRows();
|
|
8360
|
-
const
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
headerCheckbox.indeterminate = false;
|
|
8364
|
-
} else if (selected.length === rows.length) {
|
|
8365
|
-
headerCheckbox.checked = true;
|
|
8366
|
-
headerCheckbox.indeterminate = false;
|
|
8367
|
-
} else {
|
|
8368
|
-
headerCheckbox.checked = false;
|
|
8369
|
-
headerCheckbox.indeterminate = true;
|
|
8370
|
-
}
|
|
8487
|
+
const n = rows.filter((r) => r.hasAttribute("data-selected")).length;
|
|
8488
|
+
headerCheckbox.checked = n > 0 && n === rows.length;
|
|
8489
|
+
headerCheckbox.indeterminate = n > 0 && n < rows.length;
|
|
8371
8490
|
}
|
|
8372
8491
|
|
|
8373
8492
|
// ── Filtering ──
|
|
@@ -8456,6 +8575,7 @@ function createTable(root) {
|
|
|
8456
8575
|
refreshStripes();
|
|
8457
8576
|
updateHeaderCheckbox();
|
|
8458
8577
|
computeAggregates();
|
|
8578
|
+
setupNavigability();
|
|
8459
8579
|
emit("filter", { query: globalFilter, visible, total: rows.length });
|
|
8460
8580
|
scheduleStateSave();
|
|
8461
8581
|
}
|
|
@@ -8476,7 +8596,7 @@ function createTable(root) {
|
|
|
8476
8596
|
applyFilters();
|
|
8477
8597
|
}
|
|
8478
8598
|
function syncFilterInputs() {
|
|
8479
|
-
const g = root.
|
|
8599
|
+
const g = [...root.querySelectorAll("[data-part='filter']")].find(mine);
|
|
8480
8600
|
if (g && g.value !== globalFilter) g.value = globalFilter;
|
|
8481
8601
|
const fr = filterRowEl();
|
|
8482
8602
|
if (!fr) return;
|
|
@@ -8499,6 +8619,7 @@ function createTable(root) {
|
|
|
8499
8619
|
for (const m of groupMembers(gh)) toggleAttr(m, "data-collapsed", !expand);
|
|
8500
8620
|
refreshStripes();
|
|
8501
8621
|
updateHeaderCheckbox();
|
|
8622
|
+
setupNavigability();
|
|
8502
8623
|
emit("group-toggle", { group: gh, expanded: expand });
|
|
8503
8624
|
}
|
|
8504
8625
|
|
|
@@ -8527,6 +8648,7 @@ function createTable(root) {
|
|
|
8527
8648
|
}
|
|
8528
8649
|
refreshStripes();
|
|
8529
8650
|
updateHeaderCheckbox();
|
|
8651
|
+
setupNavigability();
|
|
8530
8652
|
emit("tree-toggle", { row, expanded: expand });
|
|
8531
8653
|
}
|
|
8532
8654
|
|
|
@@ -8541,20 +8663,15 @@ function createTable(root) {
|
|
|
8541
8663
|
emit("row-expand", { row, detail, expanded: expand });
|
|
8542
8664
|
}
|
|
8543
8665
|
|
|
8544
|
-
function
|
|
8666
|
+
function setAllExpanded(expand) {
|
|
8545
8667
|
mutedRun(() => {
|
|
8546
|
-
for (const gh of groupHeaders()) toggleGroup(gh,
|
|
8547
|
-
if (opts.tree) for (const r of bodyRows()) if (isTreeParent(r)) toggleRow(r,
|
|
8548
|
-
});
|
|
8549
|
-
refreshStripes();
|
|
8550
|
-
}
|
|
8551
|
-
function collapseAll() {
|
|
8552
|
-
mutedRun(() => {
|
|
8553
|
-
for (const gh of groupHeaders()) toggleGroup(gh, false);
|
|
8554
|
-
if (opts.tree) for (const r of bodyRows()) if (isTreeParent(r)) toggleRow(r, false);
|
|
8668
|
+
for (const gh of groupHeaders()) toggleGroup(gh, expand);
|
|
8669
|
+
if (opts.tree) for (const r of bodyRows()) if (isTreeParent(r)) toggleRow(r, expand);
|
|
8555
8670
|
});
|
|
8556
8671
|
refreshStripes();
|
|
8557
8672
|
}
|
|
8673
|
+
const expandAll = () => setAllExpanded(true);
|
|
8674
|
+
const collapseAll = () => setAllExpanded(false);
|
|
8558
8675
|
|
|
8559
8676
|
// ── Aggregates (tfoot, group headers, tree parents) ──
|
|
8560
8677
|
function writeAggregate(cell, rows, ownIndex) {
|
|
@@ -8589,7 +8706,7 @@ function createTable(root) {
|
|
|
8589
8706
|
const fmt =
|
|
8590
8707
|
fn === "count"
|
|
8591
8708
|
? cell.getAttribute("data-format")
|
|
8592
|
-
: cell
|
|
8709
|
+
: cellFormatOf(cell, col) || "number";
|
|
8593
8710
|
cell.textContent = fmt ? formatValue(out, fmt) : String(out);
|
|
8594
8711
|
applyNegativeFlag(cell);
|
|
8595
8712
|
}
|
|
@@ -8773,17 +8890,15 @@ function createTable(root) {
|
|
|
8773
8890
|
|
|
8774
8891
|
function onEditorKeydown(e) {
|
|
8775
8892
|
if (!editing) return;
|
|
8893
|
+
e.stopPropagation(); // keep grid navigation away from typing
|
|
8776
8894
|
if (e.key === "Enter") {
|
|
8777
8895
|
e.preventDefault();
|
|
8778
|
-
e.stopPropagation();
|
|
8779
8896
|
commitEdit(true);
|
|
8780
8897
|
} else if (e.key === "Escape") {
|
|
8781
8898
|
e.preventDefault();
|
|
8782
|
-
e.stopPropagation();
|
|
8783
8899
|
cancelEdit(true);
|
|
8784
8900
|
} else if (e.key === "Tab") {
|
|
8785
8901
|
e.preventDefault();
|
|
8786
|
-
e.stopPropagation();
|
|
8787
8902
|
const from = editing.cell;
|
|
8788
8903
|
if (commitEdit()) {
|
|
8789
8904
|
const next = nextEditableCell(from, e.shiftKey ? -1 : 1);
|
|
@@ -8791,7 +8906,6 @@ function createTable(root) {
|
|
|
8791
8906
|
}
|
|
8792
8907
|
} else {
|
|
8793
8908
|
e.target.removeAttribute("data-invalid");
|
|
8794
|
-
e.stopPropagation(); // keep grid navigation away from typing
|
|
8795
8909
|
}
|
|
8796
8910
|
}
|
|
8797
8911
|
function onEditorBlur(e) {
|
|
@@ -8832,6 +8946,7 @@ function createTable(root) {
|
|
|
8832
8946
|
if (!th) return;
|
|
8833
8947
|
forEachColumnCell(i, (cell) => toggleAttr(cell, "data-col-hidden", hidden));
|
|
8834
8948
|
refreshPins();
|
|
8949
|
+
setupNavigability();
|
|
8835
8950
|
emit("col-visibility", { column: i, hidden: !!hidden });
|
|
8836
8951
|
scheduleStateSave();
|
|
8837
8952
|
}
|
|
@@ -8943,14 +9058,11 @@ function createTable(root) {
|
|
|
8943
9058
|
}
|
|
8944
9059
|
|
|
8945
9060
|
function elementAt(e) {
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
return null;
|
|
8951
|
-
}
|
|
9061
|
+
try {
|
|
9062
|
+
return document.elementFromPoint(e.clientX, e.clientY);
|
|
9063
|
+
} catch {
|
|
9064
|
+
return null; // elementFromPoint missing or throwing (headless)
|
|
8952
9065
|
}
|
|
8953
|
-
return null;
|
|
8954
9066
|
}
|
|
8955
9067
|
|
|
8956
9068
|
function resolveRowAt(e) {
|
|
@@ -8959,16 +9071,13 @@ function createTable(root) {
|
|
|
8959
9071
|
return row && tbody?.contains(row) ? row : null;
|
|
8960
9072
|
}
|
|
8961
9073
|
|
|
8962
|
-
|
|
9074
|
+
/** "before"/"after" the pointer's half of el: vertical for rows, horizontal (RTL-aware) for columns. */
|
|
9075
|
+
function dropPos(e, el, horizontal) {
|
|
8963
9076
|
const rect = el.getBoundingClientRect?.();
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
const rect = el.getBoundingClientRect?.();
|
|
8969
|
-
if (!rect || !rect.width) return "after";
|
|
8970
|
-
const before = e.clientX < rect.left + rect.width / 2;
|
|
8971
|
-
return before !== isRTL ? "before" : "after";
|
|
9077
|
+
const size = rect && (horizontal ? rect.width : rect.height);
|
|
9078
|
+
if (!size) return "after";
|
|
9079
|
+
const before = horizontal ? e.clientX < rect.left + size / 2 !== isRTL : e.clientY < rect.top + size / 2;
|
|
9080
|
+
return before ? "before" : "after";
|
|
8972
9081
|
}
|
|
8973
9082
|
|
|
8974
9083
|
function bindDocDrag() {
|
|
@@ -9068,7 +9177,7 @@ function createTable(root) {
|
|
|
9068
9177
|
if (!th) th = e.target?.closest?.("[data-part='th']");
|
|
9069
9178
|
if (!th || th === drag.th || !headerRowEl()?.contains(th)) return;
|
|
9070
9179
|
th.setAttribute("data-drop-target", "");
|
|
9071
|
-
th.setAttribute("data-drop-pos",
|
|
9180
|
+
th.setAttribute("data-drop-pos", dropPos(e, th, true));
|
|
9072
9181
|
}
|
|
9073
9182
|
}
|
|
9074
9183
|
|
|
@@ -9290,15 +9399,11 @@ function createTable(root) {
|
|
|
9290
9399
|
existing.remove();
|
|
9291
9400
|
existing = null;
|
|
9292
9401
|
}
|
|
9293
|
-
|
|
9294
|
-
if (isParent)
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
el.setAttribute("aria-label", "Toggle");
|
|
9299
|
-
} else {
|
|
9300
|
-
el = document.createElement("span");
|
|
9301
|
-
el.setAttribute("data-part", "expander");
|
|
9402
|
+
const el = document.createElement(isParent ? "button" : "span");
|
|
9403
|
+
if (isParent) el.type = "button";
|
|
9404
|
+
el.setAttribute("data-part", "expander");
|
|
9405
|
+
if (isParent) el.setAttribute("aria-label", "Toggle");
|
|
9406
|
+
else {
|
|
9302
9407
|
el.setAttribute("data-leaf", "");
|
|
9303
9408
|
el.setAttribute("aria-hidden", "true");
|
|
9304
9409
|
}
|
|
@@ -9394,14 +9499,35 @@ function createTable(root) {
|
|
|
9394
9499
|
return rows;
|
|
9395
9500
|
}
|
|
9396
9501
|
|
|
9502
|
+
// APG grid: links, buttons and fields inside a navigable grid's cells leave the
|
|
9503
|
+
// Tab order (Enter/F2 reaches them, Escape comes back). The table's own controls
|
|
9504
|
+
// stay focusable. destroy() puts the original tabindex back.
|
|
9505
|
+
const demoted = new WeakMap();
|
|
9506
|
+
const cellControls = (cell) =>
|
|
9507
|
+
[...cell.querySelectorAll("input,select,textarea,button,a[href],[tabindex]")].filter(
|
|
9508
|
+
(el) => mine(el) && !el.closest("[data-part='checkbox'],[data-part='drag-handle'],[data-part='expander'],[data-part='row-toggle']")
|
|
9509
|
+
);
|
|
9510
|
+
|
|
9511
|
+
// Re-run after anything that can take the Tab stop away (refresh, filter,
|
|
9512
|
+
// collapse, column hide): only the Tab stop moves, never focus.
|
|
9397
9513
|
function setupNavigability() {
|
|
9398
9514
|
if (opts.navigable) {
|
|
9515
|
+
const rows = navMatrix();
|
|
9516
|
+
// A stale stop — its row removed, filtered or collapsed, or its column hidden —
|
|
9517
|
+
// is dropped so the first visible cell takes over.
|
|
9518
|
+
if (activeCell && (!rows.includes(activeCell.parentElement) || activeCell.hasAttribute("data-col-hidden"))) {
|
|
9519
|
+
activeCell.setAttribute("tabindex", "-1");
|
|
9520
|
+
activeCell = null;
|
|
9521
|
+
}
|
|
9399
9522
|
let first = null;
|
|
9400
|
-
for (const row of
|
|
9523
|
+
for (const row of rows) {
|
|
9401
9524
|
for (const cell of cellsOf(row)) {
|
|
9402
|
-
if (cell.hasAttribute("data-col-hidden"))
|
|
9403
|
-
if (!first && !activeCell) first = cell;
|
|
9525
|
+
if (!first && !activeCell && !cell.hasAttribute("data-col-hidden")) first = cell;
|
|
9404
9526
|
cell.setAttribute("tabindex", cell === (activeCell || first) ? "0" : "-1");
|
|
9527
|
+
for (const el of cellControls(cell)) {
|
|
9528
|
+
if (!demoted.has(el)) demoted.set(el, el.getAttribute("tabindex"));
|
|
9529
|
+
el.setAttribute("tabindex", "-1");
|
|
9530
|
+
}
|
|
9405
9531
|
}
|
|
9406
9532
|
}
|
|
9407
9533
|
if (!activeCell) activeCell = first;
|
|
@@ -9416,11 +9542,7 @@ function createTable(root) {
|
|
|
9416
9542
|
if (activeCell && activeCell !== cell) activeCell.setAttribute("tabindex", "-1");
|
|
9417
9543
|
activeCell = cell;
|
|
9418
9544
|
cell.setAttribute("tabindex", "0");
|
|
9419
|
-
|
|
9420
|
-
cell.focus();
|
|
9421
|
-
} catch {
|
|
9422
|
-
/* unfocusable */
|
|
9423
|
-
}
|
|
9545
|
+
cell.focus();
|
|
9424
9546
|
}
|
|
9425
9547
|
|
|
9426
9548
|
function moveFocus(rowDelta, colDelta, edge) {
|
|
@@ -9460,7 +9582,7 @@ function createTable(root) {
|
|
|
9460
9582
|
if (!opts.navigable) return;
|
|
9461
9583
|
const cell = e.target.closest("[data-part='td'],[data-part='th']");
|
|
9462
9584
|
if (!cell || cell === activeCell || !root.contains(cell)) return;
|
|
9463
|
-
if (e.target !== cell) return; //
|
|
9585
|
+
if (e.target !== cell && !demoted.has(e.target)) return; // the table's own control, not the cell
|
|
9464
9586
|
if (activeCell) activeCell.setAttribute("tabindex", "-1");
|
|
9465
9587
|
activeCell = cell;
|
|
9466
9588
|
cell.setAttribute("tabindex", "0");
|
|
@@ -9495,13 +9617,12 @@ function createTable(root) {
|
|
|
9495
9617
|
if (e.shiftKey && lastSelectedIndex >= 0) {
|
|
9496
9618
|
e.preventDefault();
|
|
9497
9619
|
selectRange(lastSelectedIndex, index);
|
|
9498
|
-
|
|
9620
|
+
selectionChanged();
|
|
9499
9621
|
return;
|
|
9500
9622
|
}
|
|
9501
9623
|
toggleAttr(row, "data-selected", checkbox.checked);
|
|
9502
9624
|
lastSelectedIndex = index;
|
|
9503
|
-
|
|
9504
|
-
emit("selection-change", { selected: getSelected() });
|
|
9625
|
+
selectionChanged();
|
|
9505
9626
|
}
|
|
9506
9627
|
|
|
9507
9628
|
function onRootClick(e) {
|
|
@@ -9541,7 +9662,7 @@ function createTable(root) {
|
|
|
9541
9662
|
if (index < 0) return;
|
|
9542
9663
|
if (e.shiftKey && lastSelectedIndex >= 0) {
|
|
9543
9664
|
selectRange(lastSelectedIndex, index);
|
|
9544
|
-
|
|
9665
|
+
selectionChanged();
|
|
9545
9666
|
return;
|
|
9546
9667
|
}
|
|
9547
9668
|
if (opts.selectable != null) {
|
|
@@ -9553,8 +9674,7 @@ function createTable(root) {
|
|
|
9553
9674
|
setRowSelected(row, !row.hasAttribute("data-selected"));
|
|
9554
9675
|
}
|
|
9555
9676
|
lastSelectedIndex = index;
|
|
9556
|
-
|
|
9557
|
-
emit("selection-change", { selected: getSelected() });
|
|
9677
|
+
selectionChanged();
|
|
9558
9678
|
}
|
|
9559
9679
|
}
|
|
9560
9680
|
|
|
@@ -9614,10 +9734,18 @@ function createTable(root) {
|
|
|
9614
9734
|
return;
|
|
9615
9735
|
}
|
|
9616
9736
|
|
|
9737
|
+
const cell = target.closest?.("[data-part='td'],[data-part='th']");
|
|
9738
|
+
|
|
9739
|
+
// Escape in a cell's control returns to the cell.
|
|
9740
|
+
if (e.key === "Escape" && demoted.has(target)) {
|
|
9741
|
+
e.preventDefault();
|
|
9742
|
+
focusCell(cell);
|
|
9743
|
+
return;
|
|
9744
|
+
}
|
|
9745
|
+
|
|
9617
9746
|
// Never hijack typing or native control activation (buttons own Enter/Space via click).
|
|
9618
9747
|
if (target.closest?.("input,select,textarea,button,a")) return;
|
|
9619
9748
|
|
|
9620
|
-
const cell = target.closest?.("[data-part='td'],[data-part='th']");
|
|
9621
9749
|
const inHeader = cell && thead?.contains(cell);
|
|
9622
9750
|
|
|
9623
9751
|
// Sortable header keyboard activation.
|
|
@@ -9708,6 +9836,12 @@ function createTable(root) {
|
|
|
9708
9836
|
if (isCellEditable(cell)) {
|
|
9709
9837
|
e.preventDefault();
|
|
9710
9838
|
startEdit(cell);
|
|
9839
|
+
return;
|
|
9840
|
+
}
|
|
9841
|
+
const ctl = cellControls(cell)[0];
|
|
9842
|
+
if (ctl) {
|
|
9843
|
+
e.preventDefault();
|
|
9844
|
+
ctl.focus();
|
|
9711
9845
|
}
|
|
9712
9846
|
return;
|
|
9713
9847
|
}
|
|
@@ -9755,7 +9889,7 @@ function createTable(root) {
|
|
|
9755
9889
|
|
|
9756
9890
|
function measureSticky() {
|
|
9757
9891
|
const needsHeaderVar =
|
|
9758
|
-
root.hasAttribute("data-sticky-header") ||
|
|
9892
|
+
root.hasAttribute("data-sticky-header") || bodyRows().some((r) => r.dataset.pin === "top");
|
|
9759
9893
|
if (needsHeaderVar && thead && thead.offsetHeight) {
|
|
9760
9894
|
root.style.setProperty("--table-thead-h", thead.offsetHeight + "px");
|
|
9761
9895
|
}
|
|
@@ -9794,6 +9928,12 @@ function createTable(root) {
|
|
|
9794
9928
|
cleanups.length = 0;
|
|
9795
9929
|
observer?.disconnect();
|
|
9796
9930
|
observer = null;
|
|
9931
|
+
for (const el of root.querySelectorAll("[tabindex]")) {
|
|
9932
|
+
if (demoted.has(el)) {
|
|
9933
|
+
const t = demoted.get(el);
|
|
9934
|
+
t == null ? el.removeAttribute("tabindex") : el.setAttribute("tabindex", t);
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9797
9937
|
for (const t of timers) clearTimeout(t);
|
|
9798
9938
|
timers.clear();
|
|
9799
9939
|
clearTimeout(saveTimer);
|
|
@@ -9803,13 +9943,14 @@ function createTable(root) {
|
|
|
9803
9943
|
// ── Wire up ──
|
|
9804
9944
|
if (thead) on(thead, "click", onHeaderClick);
|
|
9805
9945
|
if (headerCheckbox) on(headerCheckbox, "change", onHeaderCheckboxChange);
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9946
|
+
const onOwn = (target, ev, fn) => on(target, ev, (e) => mine(e.target) && fn(e));
|
|
9947
|
+
if (tbody) onOwn(tbody, "change", onRowCheckboxChange);
|
|
9948
|
+
onOwn(root, "click", onRootClick);
|
|
9949
|
+
onOwn(root, "dblclick", onDblClick);
|
|
9950
|
+
onOwn(root, "keydown", onKeydown);
|
|
9951
|
+
onOwn(root, "input", onFilterInput);
|
|
9952
|
+
onOwn(root, "pointerdown", onPointerDown);
|
|
9953
|
+
onOwn(root, "focusin", onFocusIn);
|
|
9813
9954
|
if (typeof window !== "undefined") {
|
|
9814
9955
|
let resizeTimer = null;
|
|
9815
9956
|
const onWinResize = () => {
|
|
@@ -9894,9 +10035,14 @@ function createTabs(root) {
|
|
|
9894
10035
|
// Prevent double-init
|
|
9895
10036
|
if (root._faqirTabs) return root._faqirTabs;
|
|
9896
10037
|
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
10038
|
+
// Direct children only: a component nested in a panel (a collapsible's
|
|
10039
|
+
// summary, another tabs) has parts of the same names, and a deep query took
|
|
10040
|
+
// them for this component's tabs and shifted the trigger↔panel pairing.
|
|
10041
|
+
const list = root.querySelector(":scope > [data-part='list']");
|
|
10042
|
+
const triggers = () => [
|
|
10043
|
+
...root.querySelectorAll(":scope > [data-part='list'] > [data-part='trigger']"),
|
|
10044
|
+
];
|
|
10045
|
+
const panels = () => [...root.querySelectorAll(":scope > [data-part='panel']")];
|
|
9900
10046
|
|
|
9901
10047
|
function activate(index) {
|
|
9902
10048
|
const allTriggers = triggers();
|
|
@@ -10565,8 +10711,11 @@ function createToastContainer(root) {
|
|
|
10565
10711
|
* exclusivity when the controls are used standalone (no `l-model`), the
|
|
10566
10712
|
* `[data-state]` styling hook, and a `faqir:change` notification.
|
|
10567
10713
|
*
|
|
10568
|
-
* Every
|
|
10714
|
+
* Every user mutation funnels through a native `change` event on the affected
|
|
10569
10715
|
* input, so the engine's `l-model` listener and this controller stay in lockstep.
|
|
10716
|
+
* Store-driven `l-model` writes arrive as the engine's `faqir:model` event (and
|
|
10717
|
+
* a form `reset`); both re-sync state without a `faqir:change`. A plain script
|
|
10718
|
+
* write (`input.checked = x`) is silent and is not observed — use `setValue()`.
|
|
10570
10719
|
*/
|
|
10571
10720
|
function createToggleGroup(root) {
|
|
10572
10721
|
// Prevent double-init.
|
|
@@ -10727,13 +10876,23 @@ function createToggleGroup(root) {
|
|
|
10727
10876
|
if (el) select(el);
|
|
10728
10877
|
}
|
|
10729
10878
|
|
|
10879
|
+
// A store-driven `l-model` write sets `.checked` silently and announces it
|
|
10880
|
+
// with `faqir:model`; follow it without re-emitting `faqir:change` (the user
|
|
10881
|
+
// did not act). A form reset restores defaults silently the same way.
|
|
10882
|
+
const form = root.closest("form");
|
|
10883
|
+
const onReset = () => setTimeout(syncState, 0);
|
|
10884
|
+
|
|
10730
10885
|
root.addEventListener("change", onChange);
|
|
10886
|
+
root.addEventListener("faqir:model", syncState);
|
|
10731
10887
|
root.addEventListener("keydown", onKeyDown);
|
|
10888
|
+
if (form) form.addEventListener("reset", onReset);
|
|
10732
10889
|
syncState();
|
|
10733
10890
|
|
|
10734
10891
|
function destroy() {
|
|
10735
10892
|
root.removeEventListener("change", onChange);
|
|
10893
|
+
root.removeEventListener("faqir:model", syncState);
|
|
10736
10894
|
root.removeEventListener("keydown", onKeyDown);
|
|
10895
|
+
if (form) form.removeEventListener("reset", onReset);
|
|
10737
10896
|
delete root._faqirToggleGroup;
|
|
10738
10897
|
}
|
|
10739
10898
|
|
|
@@ -10754,7 +10913,8 @@ function createTooltip(root) {
|
|
|
10754
10913
|
if (root._faqirTooltip) return root._faqirTooltip;
|
|
10755
10914
|
|
|
10756
10915
|
const trigger = root.querySelector("[data-part='trigger']");
|
|
10757
|
-
|
|
10916
|
+
// Not the content of a collapsible or popover used as the trigger.
|
|
10917
|
+
const [content] = ownParts(root, "content");
|
|
10758
10918
|
|
|
10759
10919
|
let showTimer = null;
|
|
10760
10920
|
let hideTimer = null;
|
|
@@ -11352,7 +11512,9 @@ function createTreeView(root) {
|
|
|
11352
11512
|
/** One value, deep-copied into something plain, finite and printable. */
|
|
11353
11513
|
function snapshotValue(value, stack, depth) {
|
|
11354
11514
|
if (typeof value === 'function') {
|
|
11355
|
-
|
|
11515
|
+
// Scope methods are bound [1.1F-05]; report the name the author wrote.
|
|
11516
|
+
var name = String(value.name || '').replace(/^bound /, '');
|
|
11517
|
+
return '[Function' + (name ? ' ' + name : '') + ']';
|
|
11356
11518
|
}
|
|
11357
11519
|
if (value === null || typeof value !== 'object') return value;
|
|
11358
11520
|
if (value.nodeType === 1 && value.tagName) {
|
|
@@ -11596,22 +11758,35 @@ function createTreeView(root) {
|
|
|
11596
11758
|
}
|
|
11597
11759
|
}
|
|
11598
11760
|
|
|
11761
|
+
// Every controller inside `root` (a document or an element), `root` included.
|
|
11762
|
+
// Controllers are double-init guarded, so a second call is a no-op.
|
|
11763
|
+
function startControllers(root) {
|
|
11764
|
+
var own = root.getAttribute && root.getAttribute('data-ui');
|
|
11765
|
+
if (own && controllerRegistry[own]) initController(own, root);
|
|
11766
|
+
var names = Object.keys(controllerRegistry);
|
|
11767
|
+
for (var n = 0; n < names.length; n++) {
|
|
11768
|
+
var els = root.querySelectorAll('[data-ui="' + names[n] + '"]');
|
|
11769
|
+
for (var e = 0; e < els.length; e++) initController(names[n], els[e]);
|
|
11770
|
+
}
|
|
11771
|
+
}
|
|
11772
|
+
|
|
11773
|
+
// `Faqir.initTree` — the same order bootstrap uses: a connected subtree's
|
|
11774
|
+
// controllers start first, so its `l-init` / first `l-effect` reach them
|
|
11775
|
+
// through `$ui`. A detached subtree's controllers wait for the
|
|
11776
|
+
// MutationObserver, as before. [1.1F-03]
|
|
11777
|
+
function publicInitTree(root, parentScope) {
|
|
11778
|
+
if (root.isConnected) startControllers(root);
|
|
11779
|
+
initTree(root, parentScope);
|
|
11780
|
+
}
|
|
11781
|
+
|
|
11599
11782
|
// The document's one MutationObserver, or null before the first bootstrap,
|
|
11600
11783
|
// and the <body> it is currently watching.
|
|
11601
11784
|
var mutationObserver = null;
|
|
11602
11785
|
var observedBody = null;
|
|
11603
11786
|
|
|
11604
11787
|
function bootstrap() {
|
|
11605
|
-
injectCloakStyle();
|
|
11606
|
-
|
|
11607
11788
|
// Auto-init controllers for all [data-ui] elements
|
|
11608
|
-
|
|
11609
|
-
for (var n = 0; n < names.length; n++) {
|
|
11610
|
-
var els = document.querySelectorAll('[data-ui="' + names[n] + '"]');
|
|
11611
|
-
for (var e = 0; e < els.length; e++) {
|
|
11612
|
-
initController(names[n], els[e]);
|
|
11613
|
-
}
|
|
11614
|
-
}
|
|
11789
|
+
startControllers(document);
|
|
11615
11790
|
|
|
11616
11791
|
// Find all scope roots.
|
|
11617
11792
|
// l-data elements always create a scope.
|
|
@@ -11684,12 +11859,7 @@ function createTreeView(root) {
|
|
|
11684
11859
|
// after the first sweep ran and before the MutationObserver below starts.
|
|
11685
11860
|
// Controllers are double-init guarded, so re-invoking is a no-op for
|
|
11686
11861
|
// elements initialized by the first sweep.
|
|
11687
|
-
|
|
11688
|
-
var els2 = document.querySelectorAll('[data-ui="' + names[n2] + '"]');
|
|
11689
|
-
for (var e2 = 0; e2 < els2.length; e2++) {
|
|
11690
|
-
initController(names[n2], els2[e2]);
|
|
11691
|
-
}
|
|
11692
|
-
}
|
|
11862
|
+
startControllers(document);
|
|
11693
11863
|
|
|
11694
11864
|
removeCloaks();
|
|
11695
11865
|
|
|
@@ -11715,14 +11885,14 @@ function createTreeView(root) {
|
|
|
11715
11885
|
if (node.nodeType !== 1) continue;
|
|
11716
11886
|
|
|
11717
11887
|
// Content that arrives after bootstrap has already bound by the time
|
|
11718
|
-
// it is here; leaving `l-cloak` on it means the
|
|
11719
|
-
//
|
|
11888
|
+
// it is here; leaving `l-cloak` on it means the cloak rule hides it
|
|
11889
|
+
// forever. [W3-1]
|
|
11720
11890
|
removeCloaks(node);
|
|
11721
11891
|
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11892
|
+
// Controllers first — the node's own and every one inside it — so
|
|
11893
|
+
// the `l-init` / first `l-effect` that `initTree` runs below reach
|
|
11894
|
+
// them through `$ui`. They used to start after it. [1.1F-03]
|
|
11895
|
+
startControllers(node);
|
|
11726
11896
|
|
|
11727
11897
|
// Already initialized — by `Faqir.start()` / `Faqir.initTree()` in the
|
|
11728
11898
|
// same task that inserted it (the record is only delivered on the
|
|
@@ -11739,11 +11909,6 @@ function createTreeView(root) {
|
|
|
11739
11909
|
}
|
|
11740
11910
|
|
|
11741
11911
|
if (node.querySelectorAll) {
|
|
11742
|
-
var cNames = Object.keys(controllerRegistry);
|
|
11743
|
-
for (var cn = 0; cn < cNames.length; cn++) {
|
|
11744
|
-
var cEls = node.querySelectorAll('[data-ui="' + cNames[cn] + '"]');
|
|
11745
|
-
for (var ce = 0; ce < cEls.length; ce++) initController(cNames[cn], cEls[ce]);
|
|
11746
|
-
}
|
|
11747
11912
|
var scopeEls = node.querySelectorAll('[l-data]');
|
|
11748
11913
|
for (var se = 0; se < scopeEls.length; se++) {
|
|
11749
11914
|
if (!scopeEls[se].__faqirScope) {
|
|
@@ -11814,7 +11979,7 @@ function createTreeView(root) {
|
|
|
11814
11979
|
// one time it was hand-maintained (0.1.0 in a package versioned 0.2.4), so
|
|
11815
11980
|
// the built artifact is now the only place the number is authoritative and
|
|
11816
11981
|
// the build fails if this marker goes missing.
|
|
11817
|
-
version: '1.1.
|
|
11982
|
+
version: '1.1.2', // @faqir:version
|
|
11818
11983
|
reactive: reactive,
|
|
11819
11984
|
effect: effect,
|
|
11820
11985
|
batch: batch,
|
|
@@ -11825,11 +11990,14 @@ function createTreeView(root) {
|
|
|
11825
11990
|
data: function(name, factory) { dataRegistry.set(name, factory); },
|
|
11826
11991
|
store: function(name, obj) { globalStores[name] = reactive(obj); },
|
|
11827
11992
|
directive: function(name, handler) { customDirectives.set(name, handler); },
|
|
11828
|
-
magic: function(name, callback) {
|
|
11993
|
+
magic: function(name, callback) {
|
|
11994
|
+
if (name === 'this') throw new Error('[Faqir] "this" is reserved: $this is the directive element');
|
|
11995
|
+
customMagics.set(name, callback);
|
|
11996
|
+
},
|
|
11829
11997
|
plugin: function(fn) { fn(Faqir); },
|
|
11830
11998
|
controller: function(name, factory) { controllerRegistry[name] = factory; },
|
|
11831
11999
|
start: bootstrap,
|
|
11832
|
-
initTree:
|
|
12000
|
+
initTree: publicInitTree,
|
|
11833
12001
|
// Snapshot of Faqir's view of one element — scope, directives, controller,
|
|
11834
12002
|
// protocol attributes. [0.7-12]
|
|
11835
12003
|
inspect: inspect,
|