@faqir-ui/core 1.0.0 → 1.1.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 +45 -5
- package/cdn.json +38 -22
- package/dist/faqir-core.js +89 -52
- package/dist/faqir-core.min.js +3 -3
- package/dist/faqir-core.min.js.map +3 -3
- package/dist/faqir-core.mjs +61 -54
- package/dist/faqir.aurora.css +1209 -507
- package/dist/faqir.brutalist.css +1184 -489
- package/dist/faqir.candy.css +14152 -0
- package/dist/faqir.clinical.css +14152 -0
- package/dist/faqir.contrast.css +1198 -526
- package/dist/faqir.default.css +1261 -588
- package/dist/faqir.document-serif.css +1211 -491
- package/dist/faqir.document.css +1193 -490
- package/dist/faqir.editorial-document.css +14116 -0
- package/dist/faqir.editorial.css +14152 -0
- package/dist/faqir.fintech.css +14152 -0
- package/dist/faqir.glass.css +1214 -517
- package/dist/faqir.ink-document.css +14116 -0
- package/dist/faqir.ink.css +14127 -0
- package/dist/faqir.luxe.css +14211 -0
- package/dist/faqir.midnight.css +1229 -507
- package/dist/faqir.neo.css +14153 -0
- package/dist/faqir.neumorph.css +14151 -0
- package/dist/faqir.nordic.css +14151 -0
- package/dist/faqir.organic.css +14152 -0
- package/dist/faqir.paper.css +1200 -508
- package/dist/faqir.slate.css +1296 -564
- package/dist/faqir.soft.css +1180 -494
- package/dist/faqir.sunset.css +14153 -0
- package/dist/faqir.swiss-document.css +14116 -0
- package/dist/faqir.swiss.css +14153 -0
- package/dist/faqir.terminal.css +1187 -491
- package/dist/plugins/faqir-collapse.js +1 -1
- package/dist/plugins/faqir-intersect.js +2 -0
- package/dist/plugins/faqir-mask.js +27 -17
- package/dist/plugins/faqir-rules.js +63 -0
- package/dist/plugins/faqir-validate.js +471 -58
- package/dist/sri.json +36 -20
- package/faqir-core.d.ts +111 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@ any HTML file and you have a styled, interactive Faqir page. No build step, no i
|
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
14
|
<!-- 1. A theme's full CSS bundle (tokens + theme + base + every component) -->
|
|
15
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@faqir-ui/core@
|
|
15
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/faqir.default.css">
|
|
16
16
|
|
|
17
17
|
<!-- 2. The reactive engine — sets window.Faqir and auto-boots on DOMContentLoaded -->
|
|
18
|
-
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/faqir-core.min.js" defer></script>
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
That's it. Now write Faqir markup:
|
|
@@ -38,10 +38,10 @@ for tamper-proof loads:
|
|
|
38
38
|
|
|
39
39
|
```html
|
|
40
40
|
<link rel="stylesheet"
|
|
41
|
-
href="https://cdn.jsdelivr.net/npm/@faqir-ui/core@
|
|
41
|
+
href="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/faqir.default.css"
|
|
42
42
|
integrity="sha384-…paste from sri.json…"
|
|
43
43
|
crossorigin="anonymous">
|
|
44
|
-
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@
|
|
44
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/faqir-core.min.js"
|
|
45
45
|
integrity="sha384-…paste from sri.json…"
|
|
46
46
|
crossorigin="anonymous" defer></script>
|
|
47
47
|
```
|
|
@@ -54,9 +54,49 @@ for tamper-proof loads:
|
|
|
54
54
|
| `faqir-core.js` | Canonical UMD engine — what `<script src>` and the CDN load. Reachable explicitly at `@faqir-ui/core/faqir-core.js`. |
|
|
55
55
|
| `faqir-core.min.js` (+ `.map`) | Minified classic-script build; a plain `<script src>` sets `window.Faqir`. |
|
|
56
56
|
| `faqir.{theme}.css` | Full CSS bundle per theme: all tokens + theme + base + every component. Self-contained (no `@import`). |
|
|
57
|
-
| `plugins/` | Official self-registering drops
|
|
57
|
+
| `plugins/` | Official self-registering drops — see [Plugins](#plugins) below. |
|
|
58
58
|
| `sri.json` | SHA-384 integrity hashes for every file above. |
|
|
59
59
|
|
|
60
|
+
## Plugins
|
|
61
|
+
|
|
62
|
+
Each file in `dist/plugins/` is a self-registering classic script: load it
|
|
63
|
+
**after** the engine and it installs itself. Every one is dependency-free.
|
|
64
|
+
|
|
65
|
+
| Plugin | Provides | What it does |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `faqir-collapse.js` | `l-collapse` | height auto-animation for a boolean expression |
|
|
68
|
+
| `faqir-intersect.js` | `l-intersect` | declarative IntersectionObserver hooks |
|
|
69
|
+
| `faqir-mask.js` | `l-mask` | caret-safe input masking |
|
|
70
|
+
| `faqir-persist.js` | `l-persist`, `$persist()` | localStorage-backed reactive state |
|
|
71
|
+
| `faqir-rules.js` | `l-rules`, `$rules` | a form's conditional logic, from one JSON definition |
|
|
72
|
+
| `faqir-validate.js` | `l-validate` | declarative + programmatic form validation |
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/faqir-core.min.js" defer></script>
|
|
76
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/plugins/faqir-validate.js" defer></script>
|
|
77
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/plugins/faqir-rules.js" defer></script>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
One pairing to know: `faqir-rules` registers its cross-field and remote checks
|
|
81
|
+
through `Faqir.validate.register`, so a definition carrying `validate` rules
|
|
82
|
+
needs `faqir-validate` on the page too. That is a presence requirement, not a
|
|
83
|
+
script order — every plugin installs before the engine boots — and a page
|
|
84
|
+
missing one is told so rather than quietly skipping the checks.
|
|
85
|
+
|
|
86
|
+
Pin the plugin drops the same way as the engine — their hashes are in
|
|
87
|
+
`dist/sri.json` beside the rest:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<script src="https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1/dist/plugins/faqir-rules.js"
|
|
91
|
+
integrity="sha384-…paste from sri.json…"
|
|
92
|
+
crossorigin="anonymous" defer></script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`faqir-rules` is the browser half of
|
|
96
|
+
[`@faqir-ui/rules`](https://www.npmjs.com/package/@faqir-ui/rules) — the same
|
|
97
|
+
evaluator, bundled in, so the verdict in the page is the verdict on your server
|
|
98
|
+
by construction. Install that package to re-check a submission server-side.
|
|
99
|
+
|
|
60
100
|
## Module usage
|
|
61
101
|
|
|
62
102
|
The package is **ESM-only**. The default export is the engine; every member is
|
package/cdn.json
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@faqir-ui/core",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"base": "https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"base": "https://cdn.jsdelivr.net/npm/@faqir-ui/core@1.1.0/dist/",
|
|
5
5
|
"integrity": {
|
|
6
|
-
"faqir-core.js": "sha384-
|
|
7
|
-
"faqir-core.min.js": "sha384
|
|
8
|
-
"faqir-core.min.js.map": "sha384-
|
|
9
|
-
"faqir-core.mjs": "sha384-
|
|
10
|
-
"faqir.aurora.css": "sha384-
|
|
11
|
-
"faqir.brutalist.css": "sha384-
|
|
12
|
-
"faqir.
|
|
13
|
-
"faqir.
|
|
14
|
-
"faqir.
|
|
15
|
-
"faqir.
|
|
16
|
-
"faqir.
|
|
17
|
-
"faqir.
|
|
18
|
-
"faqir.
|
|
19
|
-
"faqir.
|
|
20
|
-
"faqir.
|
|
21
|
-
"faqir.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
6
|
+
"faqir-core.js": "sha384-Ehw+8Knrsxpt9enram44fdKrZh9cJoKDO8WeN7+gZ4DDJEH/RwHiceK7lgfiGprw",
|
|
7
|
+
"faqir-core.min.js": "sha384-+FqfiMSc4+AeDam5DHWdByfqFpegDaGaYGsKVFeKqrV2YHahZla6egt04qPvvHSc",
|
|
8
|
+
"faqir-core.min.js.map": "sha384-eMfL8gnKtbSNBDSDa8FvuTsbIe5QFjcgaMktueIfzLSGL48HzFBmCA6Wt4mMKapj",
|
|
9
|
+
"faqir-core.mjs": "sha384-GIAWwY5wDRhwHb3/yKRvQ7D8oSmMI4PZKI0IsSMye5K1mnIwtCbgG20zphtOCn2x",
|
|
10
|
+
"faqir.aurora.css": "sha384-TrPBHGXGxwY6sLypt3kbjbhEHz87wsYgqQhzPqi5zghmsiqewgykQx6aS+WP2O0h",
|
|
11
|
+
"faqir.brutalist.css": "sha384-N/VfEAEdOZ2PGFX4xsPzlHEmDkXtjzmkXXW1Ta3PCtBV/RZm1rF4EYsfwLEvwqPZ",
|
|
12
|
+
"faqir.candy.css": "sha384-/lk9HAr46O2pYwsM8vE8MBVkPLB1t2otA3Edfux98b564DvNkWqpsD4DSVdcmHAX",
|
|
13
|
+
"faqir.clinical.css": "sha384-quTFS0lcUB0sz5ysan8vVX75oBDNzY+of7CmKDEoX5fM5OCIbYEttiZ8eLL/vQ4u",
|
|
14
|
+
"faqir.contrast.css": "sha384-Kkc5S9mDWhATTvHHfZhNd+rN06hSCvbKLuqa5y+cwtvlF7jSunMH1bLkfrZsVw5f",
|
|
15
|
+
"faqir.default.css": "sha384-bbc2p6aV7UYxrmv8M6x0buppf4uT7yh8DZ9d///ykZjXQokcZNtOk6xK1UUl6Mnj",
|
|
16
|
+
"faqir.document-serif.css": "sha384-gruBhX1KhlPRKzCVMODRBoOB+IpJM3lgYB+xQgCbu35onXyy+OFvh0MBDThrRFSq",
|
|
17
|
+
"faqir.document.css": "sha384-oC4VnHJi2ez8nAPSBwN4GWOz+BraAWcXXcGjNrQmdKujuA3fVmawKwolH1zIEzT0",
|
|
18
|
+
"faqir.editorial-document.css": "sha384-sKWLbBQ4IvgSvhLYo0nE95l6pqku+T0VTmkkoTwg/YOx5vM9gBAPA7f6CLE8gLNa",
|
|
19
|
+
"faqir.editorial.css": "sha384-N5sYSpAAOB81nDI+eShZ+gTZ5zbTMwaUt9fIPxwiNWBkKdaEJnsvyP3na/2SyNbv",
|
|
20
|
+
"faqir.fintech.css": "sha384-xY6baIhCB0boozwIAPzhPg2FFZ8RhqSf/13CWeda9Bd+9D5bvZbNuiFcO67GGLOL",
|
|
21
|
+
"faqir.glass.css": "sha384-MW9qUMfNSCgUyQjY/UqCpRxsXxuDEoVFvvpLy2htmV1Vc2mtn/z7uCXscj6Yn5zq",
|
|
22
|
+
"faqir.ink-document.css": "sha384-uWhbYEZwSDk3QHPMWe7lzYM0HELolHkW8xyqoowVvXrToqTXnSYVw7zTVET3kTVC",
|
|
23
|
+
"faqir.ink.css": "sha384-57zpUenZ+vVG/PViYuf1yxB0VxdV/5hxhZbKy9vi8XawKTMpbbU7AtnvgZ81ZIvs",
|
|
24
|
+
"faqir.luxe.css": "sha384-AP+OFzUaZ0RNEOGS95dsf09tD8ltts0VslqgCyaJ3CBHHkesKJ+/MT3SA4gZPxbe",
|
|
25
|
+
"faqir.midnight.css": "sha384-xsSMTT7Pf9i0CDFfx5hlzCBkHx5gsfORYS3rZuKnasgMkHumNUsy0fTBFcHBxSo3",
|
|
26
|
+
"faqir.neo.css": "sha384-rNHNMTeReWOmjhAayIam2CSmH3cUpHI3VbCOxg1hhk0GE3pAB2GX6tqzXH8OU9kX",
|
|
27
|
+
"faqir.neumorph.css": "sha384-7N6RHoYWSlnTHeTwVWrCn4ey1KR62fsAS0p1QVyb0gcljIYhcMWADZrmH/hOjhx+",
|
|
28
|
+
"faqir.nordic.css": "sha384-A/O40XHz2mMUs3tKWT7usnwgAxSsdFpb8uXKNMtoIa3OBaR2Ibpqg5mGNov90PaT",
|
|
29
|
+
"faqir.organic.css": "sha384-acTW5ka4Zy5SXHHn1My078WAnPkj6/KbLHKuWzToArB8rWlwZGRqlFxfq8VNAaqY",
|
|
30
|
+
"faqir.paper.css": "sha384-O26GS1gp6lFBZmollq/g21aUGVoWixAfBEsFxhWzPif1X2ZrjeXyIOEmCn25R1Dz",
|
|
31
|
+
"faqir.slate.css": "sha384-lSeSUhJCMUsQxM1oS2AbeqbfdLzquz4zYDYSF1N2bpA4JC0g/7fcJA/lWvkZugyq",
|
|
32
|
+
"faqir.soft.css": "sha384-G7xaO/Rr9RdMdBch66kUiS+atQOluVXhwIb039G7WJg6KvAxDBSbA/7v8y9ouhZH",
|
|
33
|
+
"faqir.sunset.css": "sha384-7FyjK1RJrkGTvOSFLH15WiRI1i8V3bbfpUuv4Gq3stG1NwC56/JkXOH4uPscY95b",
|
|
34
|
+
"faqir.swiss-document.css": "sha384-hQKtiQvaCu9ot5TJnCIlPjZhUJif5cacuE27IvJi1j99iF9HuNOvfGp+epSzJlMT",
|
|
35
|
+
"faqir.swiss.css": "sha384-Ki39mfAWeHD1KRjAB07b4MygOk5swzXSp5sUKcTWldXuiNhpNTWo+vOeponF4yfB",
|
|
36
|
+
"faqir.terminal.css": "sha384-EE2EAqi6sG4GTe8l+7w0p8gqCjZu8A+dkfNFCx90Ue/loSNUSnLIYDqdAsApyOOR",
|
|
37
|
+
"plugins/faqir-collapse.js": "sha384-3l5Z+isXlDkidRevDGkuNOiDp6eJ2ML11RkXA0j4ixQBaepuu8pAhyY4YRnR1qB7",
|
|
38
|
+
"plugins/faqir-intersect.js": "sha384-Q/akZjAcnTnGYPKBP7b8c3iyYbHTIW1IPjBivzOH9dE7Nflr9Cu6adjeDXU2bhk6",
|
|
39
|
+
"plugins/faqir-mask.js": "sha384-7yA/VdjGdg/6vwX+zUbughO6ERyV2rEsVyNYBO5ghqJbAhj9912F9qMPTkLc55B6",
|
|
25
40
|
"plugins/faqir-persist.js": "sha384-o0jfddebC8QZ3S26VXUlOme723BNMlb/N7vqpn+fWpSeEFXzpq+3MoeXhVL/0PSv",
|
|
26
|
-
"plugins/faqir-
|
|
41
|
+
"plugins/faqir-rules.js": "sha384-xE90r5W3AlWKu+idcwx6NAkVUcRWMgM9ZJ1VgJLOguj/0GYP0jfflCojiCWt4FVq",
|
|
42
|
+
"plugins/faqir-validate.js": "sha384-TIyGNL8dHBnFdV56LAhdklvhQLsClQqGuQ7DT0Zgxva8DIzfONXZSJYTXhQxJcN0"
|
|
27
43
|
}
|
|
28
44
|
}
|
package/dist/faqir-core.js
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
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.
|
|
13
|
+
// Package version: 1.1.0
|
|
14
14
|
// ============================================================================
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Faqir Core
|
|
17
|
+
* Faqir Core — the running version is `Faqir.version` (stamped at build time).
|
|
18
18
|
* Alpine-style reactivity for the Faqir UI component system.
|
|
19
19
|
* Zero dependencies. CDN-ready. Agent-native.
|
|
20
20
|
*
|
|
@@ -176,7 +176,11 @@
|
|
|
176
176
|
flushScheduled = false;
|
|
177
177
|
var iterations = 0;
|
|
178
178
|
while (pendingEffects.size > 0) {
|
|
179
|
+
// Effects that keep re-triggering each other (a writes b, b writes a)
|
|
180
|
+
// would loop forever. The cap stops that, but dropping work silently
|
|
181
|
+
// hides the bug, so it is reported in every build.
|
|
179
182
|
if (++iterations > 100) {
|
|
183
|
+
console.warn('[Faqir] Effect loop: dropped ' + pendingEffects.size + ' effect(s) after 100 flushes; effects keep re-triggering each other');
|
|
180
184
|
pendingEffects.clear();
|
|
181
185
|
break;
|
|
182
186
|
}
|
|
@@ -917,6 +921,16 @@
|
|
|
917
921
|
function signalOf(ac) {
|
|
918
922
|
return ac ? ac.signal : undefined;
|
|
919
923
|
}
|
|
924
|
+
// Where a row is NOW — `row` by identity, else the row whose idKey is `id`.
|
|
925
|
+
// Never an index saved before the request: an interleaved load()/remove()
|
|
926
|
+
// shifts or replaces the list, and a stale index writes over a real row.
|
|
927
|
+
function locate(id, row) {
|
|
928
|
+
var items = scope[name];
|
|
929
|
+
for (var i = 0; i < items.length; i++) {
|
|
930
|
+
if (row ? items[i] === row : items[i] && items[i][idKey] === id) return i;
|
|
931
|
+
}
|
|
932
|
+
return -1;
|
|
933
|
+
}
|
|
920
934
|
|
|
921
935
|
// Inject reactive data properties into scope
|
|
922
936
|
scope[name] = [];
|
|
@@ -959,12 +973,12 @@
|
|
|
959
973
|
create: function(payload) {
|
|
960
974
|
if (destroyed) return Promise.resolve(null);
|
|
961
975
|
scope[name + 'Error'] = null;
|
|
962
|
-
var
|
|
976
|
+
var temp = null;
|
|
963
977
|
|
|
964
978
|
if (isOptimistic) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
979
|
+
scope[name].push(Object.assign({}, payload, { _pending: true }));
|
|
980
|
+
// Read back through the list: that is the handle `locate` compares.
|
|
981
|
+
temp = scope[name][scope[name].length - 1];
|
|
968
982
|
}
|
|
969
983
|
|
|
970
984
|
var ac = beginRequest();
|
|
@@ -981,9 +995,12 @@
|
|
|
981
995
|
.then(function(created) {
|
|
982
996
|
endRequest(ac);
|
|
983
997
|
if (destroyed) return null;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
998
|
+
var at = temp ? locate(null, temp) : -1;
|
|
999
|
+
if (at >= 0) {
|
|
1000
|
+
scope[name][at] = created;
|
|
1001
|
+
} else if (!temp || !created || created[idKey] == null || locate(created[idKey]) < 0) {
|
|
1002
|
+
// The temp row is gone when a load() replaced the list meanwhile;
|
|
1003
|
+
// that list may already hold the created row.
|
|
987
1004
|
scope[name].push(created);
|
|
988
1005
|
}
|
|
989
1006
|
return created;
|
|
@@ -992,9 +1009,8 @@
|
|
|
992
1009
|
endRequest(ac);
|
|
993
1010
|
if (destroyed) return null;
|
|
994
1011
|
scope[name + 'Error'] = e.message;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
1012
|
+
var at = temp ? locate(null, temp) : -1;
|
|
1013
|
+
if (at >= 0) scope[name].splice(at, 1);
|
|
998
1014
|
return null;
|
|
999
1015
|
});
|
|
1000
1016
|
},
|
|
@@ -1003,10 +1019,7 @@
|
|
|
1003
1019
|
if (destroyed) return Promise.resolve(null);
|
|
1004
1020
|
scope[name + 'Error'] = null;
|
|
1005
1021
|
var items = scope[name];
|
|
1006
|
-
var idx =
|
|
1007
|
-
for (var i = 0; i < items.length; i++) {
|
|
1008
|
-
if (items[i][idKey] === id) { idx = i; break; }
|
|
1009
|
-
}
|
|
1022
|
+
var idx = locate(id);
|
|
1010
1023
|
var snapshot = null;
|
|
1011
1024
|
|
|
1012
1025
|
if (isOptimistic && idx >= 0) {
|
|
@@ -1015,7 +1028,7 @@
|
|
|
1015
1028
|
}
|
|
1016
1029
|
|
|
1017
1030
|
var ac = beginRequest();
|
|
1018
|
-
return fetch(endpoint + '/' + id, {
|
|
1031
|
+
return fetch(endpoint + '/' + encodeURIComponent(id), {
|
|
1019
1032
|
method: 'PATCH',
|
|
1020
1033
|
headers: { 'Content-Type': 'application/json' },
|
|
1021
1034
|
body: JSON.stringify(payload),
|
|
@@ -1028,16 +1041,16 @@
|
|
|
1028
1041
|
.then(function(updated) {
|
|
1029
1042
|
endRequest(ac);
|
|
1030
1043
|
if (destroyed) return null;
|
|
1031
|
-
|
|
1044
|
+
var at = locate(id);
|
|
1045
|
+
if (at >= 0) scope[name][at] = updated;
|
|
1032
1046
|
return updated;
|
|
1033
1047
|
})
|
|
1034
1048
|
.catch(function(e) {
|
|
1035
1049
|
endRequest(ac);
|
|
1036
1050
|
if (destroyed) return null;
|
|
1037
1051
|
scope[name + 'Error'] = e.message;
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
}
|
|
1052
|
+
var at = snapshot ? locate(id) : -1;
|
|
1053
|
+
if (at >= 0) scope[name][at] = snapshot;
|
|
1041
1054
|
return null;
|
|
1042
1055
|
});
|
|
1043
1056
|
},
|
|
@@ -1046,10 +1059,7 @@
|
|
|
1046
1059
|
if (destroyed) return Promise.resolve();
|
|
1047
1060
|
scope[name + 'Error'] = null;
|
|
1048
1061
|
var items = scope[name];
|
|
1049
|
-
var idx =
|
|
1050
|
-
for (var i = 0; i < items.length; i++) {
|
|
1051
|
-
if (items[i][idKey] === id) { idx = i; break; }
|
|
1052
|
-
}
|
|
1062
|
+
var idx = locate(id);
|
|
1053
1063
|
var snapshot = null;
|
|
1054
1064
|
|
|
1055
1065
|
if (isOptimistic && idx >= 0) {
|
|
@@ -1058,20 +1068,20 @@
|
|
|
1058
1068
|
}
|
|
1059
1069
|
|
|
1060
1070
|
var ac = beginRequest();
|
|
1061
|
-
return fetch(endpoint + '/' + id, { method: 'DELETE', signal: signalOf(ac) })
|
|
1071
|
+
return fetch(endpoint + '/' + encodeURIComponent(id), { method: 'DELETE', signal: signalOf(ac) })
|
|
1062
1072
|
.then(function(res) {
|
|
1063
1073
|
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
|
|
1064
1074
|
endRequest(ac);
|
|
1065
1075
|
if (destroyed) return;
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1076
|
+
var at = isOptimistic ? -1 : locate(id);
|
|
1077
|
+
if (at >= 0) scope[name].splice(at, 1);
|
|
1069
1078
|
})
|
|
1070
1079
|
.catch(function(e) {
|
|
1071
1080
|
endRequest(ac);
|
|
1072
1081
|
if (destroyed) return;
|
|
1073
1082
|
scope[name + 'Error'] = e.message;
|
|
1074
|
-
if
|
|
1083
|
+
// `idx` is only a position hint here; skip if a reload restored it.
|
|
1084
|
+
if (snapshot && locate(id) < 0) {
|
|
1075
1085
|
scope[name].splice(idx, 0, snapshot);
|
|
1076
1086
|
}
|
|
1077
1087
|
});
|
|
@@ -1600,14 +1610,11 @@
|
|
|
1600
1610
|
});
|
|
1601
1611
|
|
|
1602
1612
|
var inputHandler = function() {
|
|
1613
|
+
// Trim first: `.number.trim` must not call trim() on the parsed number.
|
|
1603
1614
|
var value = el.value;
|
|
1604
|
-
if (modifiers.has('number')) value = parseFloat(value) || 0;
|
|
1605
1615
|
if (modifiers.has('trim')) value = value.trim();
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1608
|
-
} else {
|
|
1609
|
-
writeModel(prop, value, scope, el);
|
|
1610
|
-
}
|
|
1616
|
+
if (modifiers.has('number')) value = parseFloat(value) || 0;
|
|
1617
|
+
writeModel(prop, value, scope, el);
|
|
1611
1618
|
};
|
|
1612
1619
|
|
|
1613
1620
|
if (modifiers.has('debounce')) {
|
|
@@ -1904,12 +1911,17 @@
|
|
|
1904
1911
|
// rebinds per-row DOM state to the wrong items. Once per list; keyed lists
|
|
1905
1912
|
// never reach here. Production neither warns nor snapshots the list.
|
|
1906
1913
|
|
|
1907
|
-
// old key ->
|
|
1908
|
-
//
|
|
1909
|
-
//
|
|
1914
|
+
// old key -> entries holding it, in order, consumed as matched; the
|
|
1915
|
+
// leftovers are stale. A list rather than one entry because keys can
|
|
1916
|
+
// repeat: a Map of single entries overwrote the first row of a duplicate
|
|
1917
|
+
// pair, which then was neither reused nor removed and leaked a row per
|
|
1918
|
+
// render. source[i] = reused entry's old position + 1, or 0 for a fresh
|
|
1919
|
+
// entry (the getSequence sentinel).
|
|
1910
1920
|
var oldMap = new Map();
|
|
1911
1921
|
for (var i = 0; i < currentEntries.length; i++) {
|
|
1912
|
-
|
|
1922
|
+
var k = currentEntries[i].key;
|
|
1923
|
+
if (oldMap.has(k)) oldMap.get(k).push(currentEntries[i]);
|
|
1924
|
+
else oldMap.set(k, [currentEntries[i]]);
|
|
1913
1925
|
}
|
|
1914
1926
|
|
|
1915
1927
|
var n = items.length;
|
|
@@ -1917,9 +1929,9 @@
|
|
|
1917
1929
|
var source = new Array(n);
|
|
1918
1930
|
for (var i = 0; i < n; i++) {
|
|
1919
1931
|
var key = keyFor(items[i], i);
|
|
1920
|
-
var
|
|
1932
|
+
var olds = oldMap.get(key);
|
|
1933
|
+
var entry = olds && olds.shift();
|
|
1921
1934
|
if (entry !== undefined) {
|
|
1922
|
-
oldMap.delete(key);
|
|
1923
1935
|
// Reuse in place: one write per slot, re-renders only on change.
|
|
1924
1936
|
entry.scope[itemName] = items[i];
|
|
1925
1937
|
entry.scope[indexName] = i;
|
|
@@ -1932,10 +1944,12 @@
|
|
|
1932
1944
|
}
|
|
1933
1945
|
|
|
1934
1946
|
// Remove stale entries first so the DOM holds only reused nodes.
|
|
1935
|
-
oldMap.forEach(function(
|
|
1936
|
-
for (var
|
|
1937
|
-
|
|
1938
|
-
|
|
1947
|
+
oldMap.forEach(function(olds) {
|
|
1948
|
+
for (var e = 0; e < olds.length; e++) {
|
|
1949
|
+
for (var j = 0; j < olds[e].nodes.length; j++) {
|
|
1950
|
+
destroyScope(olds[e].nodes[j]);
|
|
1951
|
+
olds[e].nodes[j].remove();
|
|
1952
|
+
}
|
|
1939
1953
|
}
|
|
1940
1954
|
});
|
|
1941
1955
|
|
|
@@ -4400,13 +4414,17 @@ function createDialog(root) {
|
|
|
4400
4414
|
let cancelExitWait = null;
|
|
4401
4415
|
|
|
4402
4416
|
function open() {
|
|
4417
|
+
// A second open() (trigger plus an external trigger, say) must not stack
|
|
4418
|
+
// another focus trap or overwrite the focus to restore on close.
|
|
4419
|
+
if (root.dataset.state === "open" && focusCleanup) return;
|
|
4403
4420
|
cancelExitWait?.();
|
|
4404
4421
|
cancelExitWait = null;
|
|
4405
|
-
|
|
4422
|
+
// Re-opened mid-exit: the trap and the focus to restore are still live.
|
|
4423
|
+
if (!focusCleanup) previouslyFocused = document.activeElement;
|
|
4406
4424
|
root.dataset.state = "open";
|
|
4407
4425
|
overlay.hidden = false;
|
|
4408
4426
|
panel.hidden = false;
|
|
4409
|
-
focusCleanup = trapFocus(panel);
|
|
4427
|
+
if (!focusCleanup) focusCleanup = trapFocus(panel);
|
|
4410
4428
|
focusInitial();
|
|
4411
4429
|
}
|
|
4412
4430
|
|
|
@@ -10455,7 +10473,8 @@ function createToastContainer(root) {
|
|
|
10455
10473
|
*/
|
|
10456
10474
|
function dismiss(id) {
|
|
10457
10475
|
const entry = toasts.get(id);
|
|
10458
|
-
|
|
10476
|
+
// Already exiting (close clicked while the timer fires, say): one exit wait.
|
|
10477
|
+
if (!entry || entry.cancelExitWait) return;
|
|
10459
10478
|
|
|
10460
10479
|
const { el, timer, closeBtn, onCloseClick, actionBtn, onActionClick } = entry;
|
|
10461
10480
|
|
|
@@ -11541,7 +11560,18 @@ function createTreeView(root) {
|
|
|
11541
11560
|
* Diagnostics recorded so far, oldest first. Always an array: the
|
|
11542
11561
|
* production engine records nothing, so it is always empty there.
|
|
11543
11562
|
*/
|
|
11544
|
-
warnings: function() { return devHooks ? devHooks.warnings() : []; }
|
|
11563
|
+
warnings: function() { return devHooks ? devHooks.warnings() : []; },
|
|
11564
|
+
/**
|
|
11565
|
+
* Record one diagnostic on a plugin's behalf. The dev build prints it and
|
|
11566
|
+
* keeps it in `warnings()` (deduped like every other); production does
|
|
11567
|
+
* nothing and answers `false`. A plugin is a separate file with no reach
|
|
11568
|
+
* into this seam's other side, so without it a plugin can only be silent
|
|
11569
|
+
* or noisy in a shipped page — and "silent" is what the contract forbids.
|
|
11570
|
+
* [1.1B-03]
|
|
11571
|
+
*/
|
|
11572
|
+
report: function(message, el) {
|
|
11573
|
+
return false;
|
|
11574
|
+
}
|
|
11545
11575
|
};
|
|
11546
11576
|
|
|
11547
11577
|
|
|
@@ -11688,7 +11718,14 @@ function createTreeView(root) {
|
|
|
11688
11718
|
initController(uiName, node);
|
|
11689
11719
|
}
|
|
11690
11720
|
|
|
11691
|
-
|
|
11721
|
+
// Already initialized — by `Faqir.start()` / `Faqir.initTree()` in the
|
|
11722
|
+
// same task that inserted it (the record is only delivered on the
|
|
11723
|
+
// next microtask), or by the l-for / l-if render that stamped it.
|
|
11724
|
+
// Re-running `initTree` would build a second scope and bind every
|
|
11725
|
+
// directive twice — the same guard `bootstrap` applies. [1.1A-23]
|
|
11726
|
+
if (node.__faqirScope) {
|
|
11727
|
+
// fall through to the descendant sweep below
|
|
11728
|
+
} else if (node.hasAttribute && node.hasAttribute('l-data')) {
|
|
11692
11729
|
initTree(node, findParentScope(node));
|
|
11693
11730
|
} else if (node.hasAttribute && node.hasAttribute('data-ui') && !findParentScope(node)) {
|
|
11694
11731
|
// Standalone data-ui (no parent scope) — create its own scope
|
|
@@ -11750,7 +11787,7 @@ function createTreeView(root) {
|
|
|
11750
11787
|
// one time it was hand-maintained (0.1.0 in a package versioned 0.2.4), so
|
|
11751
11788
|
// the built artifact is now the only place the number is authoritative and
|
|
11752
11789
|
// the build fails if this marker goes missing.
|
|
11753
|
-
version: '1.
|
|
11790
|
+
version: '1.1.0', // @faqir:version
|
|
11754
11791
|
reactive: reactive,
|
|
11755
11792
|
effect: effect,
|
|
11756
11793
|
batch: batch,
|