@panphora/sapjs 0.2.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/dist/sap.js ADDED
@@ -0,0 +1,2490 @@
1
+ var Sap = (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/sap.js
21
+ var sap_exports = {};
22
+ __export(sap_exports, {
23
+ Sap: () => Sap,
24
+ default: () => sap_default,
25
+ mount: () => mount,
26
+ mountAll: () => mountAll,
27
+ runtime: () => runtime
28
+ });
29
+
30
+ // src/scheduler.js
31
+ var MAX_PASSES = 50;
32
+ function createScheduler(runPass2) {
33
+ let queued = false;
34
+ const dirty = /* @__PURE__ */ new Set();
35
+ const ring = [];
36
+ function note(trigger) {
37
+ if (!trigger) return;
38
+ ring.push(trigger);
39
+ if (ring.length > 8) ring.shift();
40
+ }
41
+ function schedule(app, trigger) {
42
+ note(trigger);
43
+ if (app._broken) return;
44
+ dirty.add(app);
45
+ if (queued) return;
46
+ queued = true;
47
+ queueMicrotask(drain);
48
+ }
49
+ function drain() {
50
+ queued = false;
51
+ const apps = [...dirty];
52
+ dirty.clear();
53
+ for (const app of apps) runWithBreaker(app);
54
+ }
55
+ function runWithBreaker(app) {
56
+ if (app._broken) return;
57
+ app._passes = (app._passes || 0) + 1;
58
+ if (app._passes > MAX_PASSES) {
59
+ app._broken = true;
60
+ app._breakerRing = ring.slice();
61
+ runPass2(app, { breaker: true });
62
+ return;
63
+ }
64
+ if (!app._resetArmed) {
65
+ app._resetArmed = true;
66
+ setTimeout(() => {
67
+ app._passes = 0;
68
+ app._resetArmed = false;
69
+ }, 0);
70
+ }
71
+ runPass2(app);
72
+ }
73
+ function runNow(app, trigger) {
74
+ note(trigger);
75
+ runWithBreaker(app);
76
+ }
77
+ function rearm(app) {
78
+ app._broken = false;
79
+ app._passes = 0;
80
+ }
81
+ return { schedule, runNow, rearm, ring };
82
+ }
83
+
84
+ // src/dom.js
85
+ var idSeq = 0;
86
+ function ensureId(el, prefix = "sap") {
87
+ if (!el.id) el.id = `${prefix}-${(idSeq++).toString(36)}`;
88
+ return el.id;
89
+ }
90
+ function isScopeBoundary(el) {
91
+ return el.hasAttribute("scope") || el.hasAttribute("items") || el.hasAttribute("detail");
92
+ }
93
+ function isInert(el) {
94
+ return el.hasAttribute("sap-ignore") || el.hasAttribute("template");
95
+ }
96
+ function walkOwned(scopeEl, visit) {
97
+ for (const child of scopeEl.children) descend(child);
98
+ function descend(el) {
99
+ if (el.nodeType !== 1) return;
100
+ if (isInert(el)) return;
101
+ const boundary = isScopeBoundary(el);
102
+ visit(el, boundary);
103
+ if (!boundary) for (const c of el.children) descend(c);
104
+ }
105
+ }
106
+ function nearestScopeEl(el) {
107
+ let cur = el;
108
+ while (cur) {
109
+ if (cur.hasAttribute("sap") || cur.hasAttribute("scope") || cur.hasAttribute("item") || cur.hasAttribute("detail")) {
110
+ return cur;
111
+ }
112
+ cur = cur.parentElement;
113
+ }
114
+ return null;
115
+ }
116
+ function nearestItemEl(el) {
117
+ let cur = el;
118
+ while (cur) {
119
+ if (cur.hasAttribute("detail")) return cur;
120
+ if (cur.hasAttribute("item")) return cur;
121
+ cur = cur.parentElement;
122
+ }
123
+ return null;
124
+ }
125
+ function isInsideDetail(el) {
126
+ let cur = el;
127
+ while (cur) {
128
+ if (cur.hasAttribute("detail")) return cur;
129
+ cur = cur.parentElement;
130
+ }
131
+ return null;
132
+ }
133
+ function ownedBind(scopeEl, field) {
134
+ let found = null;
135
+ walkOwned(scopeEl, (el) => {
136
+ if (!found && el.getAttribute("bind") === field) found = el;
137
+ });
138
+ return found;
139
+ }
140
+ function ownedItems(scopeEl, name) {
141
+ let found = null;
142
+ walkOwned(scopeEl, (el) => {
143
+ if (!found && el.getAttribute("items") === name) found = el;
144
+ });
145
+ return found;
146
+ }
147
+ function rowsOf(listEl) {
148
+ return [...listEl.children].filter((c) => c.hasAttribute("item") && !c.hasAttribute("template"));
149
+ }
150
+ function templateOf(listEl) {
151
+ return [...listEl.children].find((c) => c.hasAttribute("item") && c.hasAttribute("template")) || null;
152
+ }
153
+ function parseStateDecl(str) {
154
+ const out = [];
155
+ if (!str) return out;
156
+ for (const raw of str.trim().split(/\s+/)) {
157
+ if (!raw) continue;
158
+ let token = raw;
159
+ let def;
160
+ const eq = token.indexOf("=");
161
+ if (eq >= 0) {
162
+ def = token.slice(eq + 1);
163
+ token = token.slice(0, eq);
164
+ }
165
+ const parts = token.split(":");
166
+ const name = parts[0];
167
+ let type = "string";
168
+ let transient = false;
169
+ for (const p of parts.slice(1)) {
170
+ if (p === "num") type = "num";
171
+ else if (p === "bool") type = "bool";
172
+ else if (p === "transient") transient = true;
173
+ }
174
+ out.push({ name, type, default: def, transient });
175
+ }
176
+ return out;
177
+ }
178
+ function parseTyped(value, type) {
179
+ if (type === "num") {
180
+ if (value == null || value === "") return 0;
181
+ const n2 = Number(value);
182
+ return Number.isNaN(n2) ? 0 : n2;
183
+ }
184
+ if (type === "bool") return value === "true" || value === "" || value === "1";
185
+ return value == null ? "" : value;
186
+ }
187
+ function serializeTyped(value, type) {
188
+ if (type === "bool") return value ? "true" : "false";
189
+ return value == null ? "" : String(value);
190
+ }
191
+ function readTransient(el, decl) {
192
+ const store = el._sapTransient || (el._sapTransient = {});
193
+ if (!(decl.name in store)) {
194
+ let raw = el.getAttribute(decl.name);
195
+ if (raw == null) raw = decl.default != null ? decl.default : decl.type === "num" ? "0" : decl.type === "bool" ? "false" : "";
196
+ store[decl.name] = parseTyped(raw, decl.type);
197
+ }
198
+ if (el.hasAttribute(decl.name)) el.removeAttribute(decl.name);
199
+ return store[decl.name];
200
+ }
201
+ function writeTransient(el, decl, value) {
202
+ (el._sapTransient || (el._sapTransient = {}))[decl.name] = value;
203
+ if (el.hasAttribute(decl.name)) el.removeAttribute(decl.name);
204
+ }
205
+ function resolveListEl(appRoot, fromEl, path) {
206
+ const segs = String(path).split(".");
207
+ const listName = segs[segs.length - 1];
208
+ if (segs.length === 1) {
209
+ const up = findItemsUp(fromEl, listName);
210
+ if (up) return up;
211
+ if (appRoot.getAttribute("items") === listName) return appRoot;
212
+ return appRoot.querySelector(`[items="${listName}"]`);
213
+ }
214
+ const scopeName = segs[segs.length - 2];
215
+ const scopeEl = [...appRoot.querySelectorAll(`[scope="${scopeName}"]`)].find(
216
+ (s) => ownedItems(s, listName)
217
+ );
218
+ return scopeEl ? ownedItems(scopeEl, listName) : null;
219
+ }
220
+ function findItemsUp(fromEl, name) {
221
+ let scope = nearestScopeEl(fromEl);
222
+ while (scope) {
223
+ if (scope.getAttribute("items") === name) return scope;
224
+ const found = ownedItems(scope, name);
225
+ if (found) return found;
226
+ scope = nearestScopeEl(scope.parentElement);
227
+ }
228
+ return null;
229
+ }
230
+
231
+ // src/helpers.js
232
+ function num(v) {
233
+ if (typeof v === "number") return v;
234
+ if (v == null || v === "") return 0;
235
+ const n2 = Number(typeof v === "string" ? v.replace(/[$,%\s]/g, "") : v);
236
+ return Number.isNaN(n2) ? 0 : n2;
237
+ }
238
+ function values(rows, k) {
239
+ if (!Array.isArray(rows)) return [];
240
+ if (k == null) return rows;
241
+ return rows.map((r) => typeof k === "function" ? k(r) : r == null ? void 0 : r[k]);
242
+ }
243
+ function sum(rows, k) {
244
+ return values(rows, k).reduce((a, v) => a + num(v), 0);
245
+ }
246
+ function count(rows, pred) {
247
+ if (!Array.isArray(rows)) return 0;
248
+ if (pred == null) return rows.length;
249
+ return rows.reduce((a, r) => a + (pred(r) ? 1 : 0), 0);
250
+ }
251
+ function avg(rows, k) {
252
+ const v = values(rows, k);
253
+ if (!v.length) return 0;
254
+ return sum(rows, k) / v.length;
255
+ }
256
+ function min(rows, k) {
257
+ const v = values(rows, k).filter((x) => x != null && x !== "");
258
+ if (!v.length) return 0;
259
+ if (v.every((x) => typeof x === "number" || !Number.isNaN(Number(x)))) {
260
+ return Math.min(...v.map(Number));
261
+ }
262
+ return v.reduce((a, b) => String(a) <= String(b) ? a : b);
263
+ }
264
+ function max(rows, k) {
265
+ const v = values(rows, k).filter((x) => x != null && x !== "");
266
+ if (!v.length) return 0;
267
+ if (v.every((x) => typeof x === "number" || !Number.isNaN(Number(x)))) {
268
+ return Math.max(...v.map(Number));
269
+ }
270
+ return v.reduce((a, b) => String(a) >= String(b) ? a : b);
271
+ }
272
+ function plural(n2, singular, pluralForm) {
273
+ const word = num(n2) === 1 ? singular : pluralForm != null ? pluralForm : singular + "s";
274
+ return `${n2} ${word}`;
275
+ }
276
+ function days(a, b) {
277
+ const da = new Date(a);
278
+ const db = new Date(b);
279
+ if (Number.isNaN(+da) || Number.isNaN(+db)) return 0;
280
+ return Math.round((db - da) / 864e5);
281
+ }
282
+ function n(v) {
283
+ return typeof v === "number" ? v : Number(v);
284
+ }
285
+ var formats = {
286
+ usd: (v) => "$" + n(v).toLocaleString("en-US", { maximumFractionDigits: 0 }),
287
+ usd2: (v) => "$" + n(v).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
288
+ pct: (v) => Math.round(n(v)) + "%",
289
+ pct1: (v) => n(v).toFixed(1) + "%",
290
+ int: (v) => Math.round(n(v)).toLocaleString("en-US"),
291
+ num: (v) => n(v).toLocaleString("en-US"),
292
+ num2: (v) => n(v).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
293
+ compact: (v) => n(v).toLocaleString("en-US", { notation: "compact" }),
294
+ date: (v) => {
295
+ const d = new Date(v);
296
+ return Number.isNaN(+d) ? String(v) : d.toLocaleDateString("en-US");
297
+ },
298
+ clock: (v) => {
299
+ const ms = n(v);
300
+ const total = Math.floor(ms / 100);
301
+ const tenths = total % 10;
302
+ const secs = Math.floor(total / 10) % 60;
303
+ const mins = Math.floor(total / 600);
304
+ return `${mins}:${String(secs).padStart(2, "0")}.${tenths}`;
305
+ }
306
+ };
307
+ var NUMERIC = /* @__PURE__ */ new Set(["usd", "usd2", "pct", "pct1", "int", "num", "num2", "compact", "clock"]);
308
+ function applyFormat(name, v) {
309
+ if (!name) return v == null ? "" : String(v);
310
+ const f = formats[name];
311
+ if (typeof f !== "function") {
312
+ const e = new Error(`unknown format "${name}"`);
313
+ e.sapCode = "E22";
314
+ throw e;
315
+ }
316
+ if (NUMERIC.has(name)) {
317
+ const x = typeof v === "number" ? v : Number(v);
318
+ if (!Number.isFinite(x)) {
319
+ const e = new Error(`format "${name}" received ${typeof v === "string" ? `"${v}"` : v}`);
320
+ e.sapCode = "E22";
321
+ throw e;
322
+ }
323
+ }
324
+ return f(v);
325
+ }
326
+ function helperBundle() {
327
+ return [formats, num, sum, count, avg, min, max, plural, days];
328
+ }
329
+
330
+ // src/compile.js
331
+ var ARGS = ["state", "item", "el", "root", "fmt", "num", "sum", "count", "avg", "min", "max", "plural", "days"];
332
+ var cache = /* @__PURE__ */ new Map();
333
+ var STATE_REF = /\bstate\.([a-z][a-z0-9_]*)/g;
334
+ var ITEM_REF = /\bitem\.([a-z][a-z0-9_]*)/g;
335
+ function extractDeps(src) {
336
+ const state = /* @__PURE__ */ new Set();
337
+ const item = /* @__PURE__ */ new Set();
338
+ let m;
339
+ STATE_REF.lastIndex = 0;
340
+ while (m = STATE_REF.exec(src)) state.add(m[1]);
341
+ ITEM_REF.lastIndex = 0;
342
+ while (m = ITEM_REF.exec(src)) item.add(m[1]);
343
+ return { state, item };
344
+ }
345
+ function compile(src, statement = false) {
346
+ const key = (statement ? "!" : "") + src;
347
+ let entry = cache.get(key);
348
+ if (entry) return entry;
349
+ if (cache.size > 512) cache.clear();
350
+ try {
351
+ const body = statement ? `"use strict";
352
+ ${src}` : `"use strict"; return (${src});`;
353
+ const fn = new Function(...ARGS, body);
354
+ entry = { fn, src, statement, deps: extractDeps(src), error: null };
355
+ } catch (err) {
356
+ entry = { fn: null, src, statement, deps: { state: /* @__PURE__ */ new Set(), item: /* @__PURE__ */ new Set() }, error: err };
357
+ }
358
+ cache.set(key, entry);
359
+ return entry;
360
+ }
361
+ function run(entry, ctx) {
362
+ if (entry.error) throw entry.error;
363
+ return entry.fn(ctx.state, ctx.item, ctx.el ?? null, ctx.root, ...helperBundle());
364
+ }
365
+ function topoSort(calcs) {
366
+ const byName = /* @__PURE__ */ new Map();
367
+ for (const c of calcs) byName.set(c.name, c);
368
+ const out = [];
369
+ const state = /* @__PURE__ */ new Map();
370
+ const stack = [];
371
+ function visit(c) {
372
+ const s = state.get(c.name);
373
+ if (s === 1) return;
374
+ if (s === 0) {
375
+ const cycle = [...stack.slice(stack.indexOf(c.name)), c.name].join(" -> ");
376
+ const err = new Error(`calc: cycle: ${cycle}`);
377
+ err.sapCode = "E07";
378
+ err.cycle = cycle;
379
+ throw err;
380
+ }
381
+ state.set(c.name, 0);
382
+ stack.push(c.name);
383
+ for (const dep of c.entry.deps.state) {
384
+ const d = byName.get(dep);
385
+ if (d && d !== c) visit(d);
386
+ }
387
+ for (const dep of c.entry.deps.item) {
388
+ const d = byName.get(dep);
389
+ if (d && d !== c) visit(d);
390
+ }
391
+ stack.pop();
392
+ state.set(c.name, 1);
393
+ out.push(c);
394
+ }
395
+ for (const c of calcs) visit(c);
396
+ return out;
397
+ }
398
+
399
+ // src/control-serialize.js
400
+ function inputType(el) {
401
+ return (el.getAttribute("type") || "text").toLowerCase();
402
+ }
403
+ function serializeControlToAttributes(el) {
404
+ const tag = el.tagName;
405
+ if (tag === "INPUT") {
406
+ const type = inputType(el);
407
+ if (type === "checkbox" || type === "radio") {
408
+ if (el.checked) el.setAttribute("checked", "");
409
+ else el.removeAttribute("checked");
410
+ } else {
411
+ el.setAttribute("value", el.value);
412
+ }
413
+ return;
414
+ }
415
+ if (tag === "TEXTAREA") {
416
+ el.setAttribute("data-value", el.value);
417
+ return;
418
+ }
419
+ if (tag === "SELECT") {
420
+ const opts = el.options;
421
+ for (let i = 0; i < opts.length; i++) {
422
+ if (opts[i].selected) opts[i].setAttribute("selected", "");
423
+ else opts[i].removeAttribute("selected");
424
+ }
425
+ }
426
+ }
427
+ function finalizeControlForSave(target, source = target) {
428
+ const tag = target.tagName;
429
+ if (tag === "INPUT") {
430
+ const type = inputType(target);
431
+ if (type === "checkbox" || type === "radio") {
432
+ if (source.checked) target.setAttribute("checked", "");
433
+ else target.removeAttribute("checked");
434
+ } else {
435
+ target.setAttribute("value", source.value);
436
+ }
437
+ return;
438
+ }
439
+ if (tag === "TEXTAREA") {
440
+ target.textContent = source.value;
441
+ target.removeAttribute("data-value");
442
+ return;
443
+ }
444
+ if (tag === "SELECT") {
445
+ const tOpts = target.options;
446
+ const sOpts = source.options;
447
+ for (let i = 0; i < tOpts.length; i++) {
448
+ if (sOpts[i] && sOpts[i].selected) tOpts[i].setAttribute("selected", "");
449
+ else tOpts[i].removeAttribute("selected");
450
+ }
451
+ }
452
+ }
453
+ function rehydrateControlFromAttributes(el) {
454
+ if (el.tagName !== "TEXTAREA" || !el.hasAttribute("data-value")) return;
455
+ if (el === el.ownerDocument.activeElement) return;
456
+ el.textContent = el.getAttribute("data-value");
457
+ el.removeAttribute("data-value");
458
+ }
459
+
460
+ // src/platform.js
461
+ function regionSkipsSave(el) {
462
+ const region = typeof window !== "undefined" && window.hyperclay && window.hyperclay.region;
463
+ if (!region || typeof region.resolveRegionPolicy !== "function" || typeof region.skipForPolicy !== "function") {
464
+ return false;
465
+ }
466
+ return region.skipForPolicy(region.resolveRegionPolicy(el), void 0, ["no-save", "freeze"]);
467
+ }
468
+ function platformConsent() {
469
+ const h = typeof window !== "undefined" && window.hyperclay;
470
+ return h && typeof h.consent === "function" ? h.consent : null;
471
+ }
472
+ function batch(label, fn) {
473
+ if (typeof fn !== "function") throw new Error("Sap.batch(label, fn): fn must be a function");
474
+ const u = typeof window !== "undefined" && window.hyperclay && window.hyperclay.undo;
475
+ if (u && u.flush) u.flush();
476
+ const r = fn();
477
+ if (r && typeof r.then === "function") throw new Error("Sap.batch fn must be synchronous (it returned a promise)");
478
+ if (u && u.commitCaptured) u.commitCaptured(label);
479
+ return r;
480
+ }
481
+ function installBridges(runtime2) {
482
+ if (typeof window === "undefined" || typeof document === "undefined") return;
483
+ const refreshConnected = () => {
484
+ let disconnected = false;
485
+ for (const app of runtime2.apps()) {
486
+ if (app.root.isConnected) runtime2.runNow(app, "platform");
487
+ else disconnected = true;
488
+ }
489
+ if (disconnected) {
490
+ runtime2.pruneDisconnected();
491
+ runtime2.remountIfPresent();
492
+ }
493
+ };
494
+ document.addEventListener("hyperclay:livesync-applied", refreshConnected);
495
+ const undo = window.hyperclay && window.hyperclay.undo;
496
+ if (undo && typeof undo.on === "function") {
497
+ undo.on("undo", refreshConnected);
498
+ undo.on("redo", refreshConnected);
499
+ } else {
500
+ document.addEventListener("hyperclay:mutation-ready", () => {
501
+ const u = window.hyperclay && window.hyperclay.undo;
502
+ if (u && typeof u.on === "function") {
503
+ u.on("undo", refreshConnected);
504
+ u.on("redo", refreshConnected);
505
+ }
506
+ });
507
+ }
508
+ }
509
+
510
+ // src/carrier.js
511
+ function kindOf(el) {
512
+ const tag = el.tagName;
513
+ if (tag === "INPUT") {
514
+ const t = (el.getAttribute("type") || "text").toLowerCase();
515
+ if (t === "checkbox") return "checkbox";
516
+ if (t === "radio") return "radio";
517
+ if (t === "number" || t === "range") return "number";
518
+ if (t === "hidden") return "hidden";
519
+ return "text";
520
+ }
521
+ if (tag === "TEXTAREA") return "text";
522
+ if (tag === "SELECT") return el.multiple ? "select-multiple" : "select-one";
523
+ return "leaf";
524
+ }
525
+ function readNumber(el) {
526
+ if (el.validity && el.validity.badInput) {
527
+ if (el.hasAttribute("value")) return Number(el.getAttribute("value")) || 0;
528
+ return el._sapLastGood ?? 0;
529
+ }
530
+ if (el.value === "") return 0;
531
+ const n2 = Number(el.value);
532
+ if (Number.isNaN(n2)) return el._sapLastGood ?? 0;
533
+ el._sapLastGood = n2;
534
+ return n2;
535
+ }
536
+ function readBy(el, kind) {
537
+ switch (kind) {
538
+ case "checkbox":
539
+ return el.checked;
540
+ case "radio":
541
+ return el.checked ? el.value : void 0;
542
+ case "number":
543
+ return readNumber(el);
544
+ case "select-one":
545
+ return el.value;
546
+ case "select-multiple":
547
+ return [...el.selectedOptions].map((o) => o.value);
548
+ case "hidden":
549
+ case "text":
550
+ return el.value;
551
+ case "leaf":
552
+ return el.textContent;
553
+ default:
554
+ return el.value;
555
+ }
556
+ }
557
+ function writeBy(el, kind, v) {
558
+ switch (kind) {
559
+ case "checkbox":
560
+ el.checked = !!v;
561
+ break;
562
+ case "radio":
563
+ el.checked = String(el.value) === String(v);
564
+ break;
565
+ case "number":
566
+ case "text":
567
+ case "hidden":
568
+ el.value = v == null ? "" : String(v);
569
+ break;
570
+ case "select-one":
571
+ el.value = v == null ? "" : String(v);
572
+ break;
573
+ case "select-multiple": {
574
+ const set = new Set((Array.isArray(v) ? v : [v]).map(String));
575
+ for (const opt of el.options) opt.selected = set.has(opt.value);
576
+ break;
577
+ }
578
+ case "leaf":
579
+ el.textContent = v == null ? "" : String(v);
580
+ break;
581
+ default:
582
+ el.value = v == null ? "" : String(v);
583
+ }
584
+ }
585
+ function mirror(el) {
586
+ if (el.hasAttribute("transient")) {
587
+ el.removeAttribute("value");
588
+ el.removeAttribute("checked");
589
+ el.removeAttribute("data-value");
590
+ return;
591
+ }
592
+ if (regionSkipsSave(el)) return;
593
+ serializeControlToAttributes(el);
594
+ }
595
+ function fire(el) {
596
+ el.dispatchEvent(new Event("input", { bubbles: true }));
597
+ el.dispatchEvent(new Event("change", { bubbles: true }));
598
+ }
599
+ function carrierFor(el) {
600
+ const kind = kindOf(el);
601
+ return {
602
+ el,
603
+ kind,
604
+ read() {
605
+ return readBy(el, kind);
606
+ },
607
+ write(v, opts = {}) {
608
+ writeBy(el, kind, v);
609
+ if (!opts.noMirror) mirror(el);
610
+ if (!opts.silent) fire(el);
611
+ },
612
+ mirror() {
613
+ mirror(el);
614
+ }
615
+ };
616
+ }
617
+
618
+ // src/errors.js
619
+ var REGISTRY = {
620
+ E01: { slug: "foreign-dialect", behavior: "HALT" },
621
+ E02: { slug: "moustaches-in-text", behavior: "HALT" },
622
+ E04: { slug: "duplicate-declaration", behavior: "HALT" },
623
+ E05: { slug: "reserved-name", behavior: "HALT" },
624
+ E06: { slug: "global-html-name", behavior: "HALT" },
625
+ E07: { slug: "calc-cycle", behavior: "ERR" },
626
+ E08: { slug: "expression-syntax", behavior: "HALT" },
627
+ E10: { slug: "orphan-item", behavior: "HALT" },
628
+ E12: { slug: "unknown-state-key", behavior: "ERR" },
629
+ E15: { slug: "write-to-undeclared-key", behavior: "ERR" },
630
+ E16: { slug: "write-to-computed-field", behavior: "ERR" },
631
+ E17: { slug: "unresolvable-action-target", behavior: "HALT" },
632
+ E18: { slug: "paint-on-form-control", behavior: "HALT" },
633
+ E20: { slug: "bind-on-non-control", behavior: "HALT" },
634
+ E22: { slug: "format-type", behavior: "ERR" },
635
+ E24: { slug: "runtime-expression-throw", behavior: "ERR" },
636
+ E26: { slug: "circuit-breaker", behavior: "HALT" },
637
+ E30: { slug: "attr-on-bound-control", behavior: "HALT" },
638
+ E31: { slug: "password-without-transient", behavior: "HALT" },
639
+ E32: { slug: "file-bind", behavior: "HALT" },
640
+ E33: { slug: "dialog-state-open", behavior: "HALT" },
641
+ E34: { slug: "transient-persist-collision", behavior: "HALT" },
642
+ W03: { slug: "unknown-colon-attribute", behavior: "WARN" },
643
+ W04: { slug: "expression-in-when", behavior: "WARN" },
644
+ W30: { slug: "nonzero-mount-writes", behavior: "WARN" }
645
+ };
646
+ var NATIVE_BOOLEANS = [
647
+ "disabled",
648
+ "readonly",
649
+ "required",
650
+ "checked",
651
+ "selected",
652
+ "multiple",
653
+ "open",
654
+ "inert",
655
+ "autofocus",
656
+ "novalidate",
657
+ "hidden"
658
+ ];
659
+ var HTML_GLOBALS = /* @__PURE__ */ new Set([
660
+ "title",
661
+ "style",
662
+ "hidden",
663
+ "lang",
664
+ "dir",
665
+ "translate",
666
+ "class",
667
+ "id",
668
+ "nonce",
669
+ "autofocus",
670
+ "tabindex",
671
+ "contenteditable",
672
+ "spellcheck",
673
+ "draggable",
674
+ "role",
675
+ "slot",
676
+ "part",
677
+ "name",
678
+ "value",
679
+ "type",
680
+ "itemscope",
681
+ "itemtype",
682
+ "itemid",
683
+ "itemprop",
684
+ "itemref"
685
+ ]);
686
+ var RESERVED = /* @__PURE__ */ new Set([
687
+ "sap",
688
+ "scope",
689
+ "items",
690
+ "item",
691
+ "template",
692
+ "bind",
693
+ "calc",
694
+ "text",
695
+ "show",
696
+ "attr",
697
+ "set",
698
+ "trigger",
699
+ "move",
700
+ "sort",
701
+ "detail",
702
+ "effect",
703
+ "invalid",
704
+ "confirm",
705
+ "transient",
706
+ "default",
707
+ "persist",
708
+ "sortable",
709
+ // editmode is a hyperclayjs platform prefix (editmode:onclick), reserved so a
710
+ // co-loaded admin attribute never collides with a Sap state field name.
711
+ "editmode"
712
+ ]);
713
+ function cssPath(el) {
714
+ if (!el || el.nodeType !== 1) return "?";
715
+ if (el.id) return `${el.tagName.toLowerCase()}#${el.id}`;
716
+ const parts = [];
717
+ let cur = el;
718
+ let depth2 = 0;
719
+ while (cur && cur.nodeType === 1 && depth2 < 4) {
720
+ let seg = cur.tagName.toLowerCase();
721
+ if (cur.hasAttribute("sap")) seg += "[sap]";
722
+ else if (cur.parentElement) {
723
+ const sibs = [...cur.parentElement.children].filter((c) => c.tagName === cur.tagName);
724
+ if (sibs.length > 1) seg += `:nth-of-type(${sibs.indexOf(cur) + 1})`;
725
+ }
726
+ parts.unshift(seg);
727
+ if (cur.hasAttribute("sap")) break;
728
+ cur = cur.parentElement;
729
+ depth2++;
730
+ }
731
+ return parts.join(" > ");
732
+ }
733
+ function teachForAttr(name) {
734
+ const n2 = name.toLowerCase();
735
+ if (/^x-text$|^x-html$/.test(n2)) return `use text="state.x" (Sap paints text only)`;
736
+ if (/^x-show$|^v-show$|^v-if$/.test(n2)) return `use show="expr" (toggles native hidden)`;
737
+ if (/^x-model$|^v-model$/.test(n2)) return `use bind="field"`;
738
+ if (/^@|^x-on:|^v-on:|^on:/.test(n2)) return `use native onclick + Sap(this) (no event directive)`;
739
+ if (/^:|^x-bind:class$|^v-bind:class$/.test(n2)) return `use class:NAME="expr" or attr:NAME="expr"`;
740
+ if (/^x-for$|^v-for$/.test(n2)) return `use items="name" on the container and item template on the row`;
741
+ if (/^x-data$/.test(n2)) return `declare state with state= / bind / items (no JS state object)`;
742
+ if (/^x-init$/.test(n2)) return `use a <script> or onclick + Sap(this)`;
743
+ if (/^bind:/.test(n2)) return `use bind="field"; one-way paint is attr:NAME="expr"`;
744
+ if (/^x-/.test(n2) || /^v-/.test(n2)) return `this is a foreign-dialect attribute; Sap has no x-/v- attributes`;
745
+ return null;
746
+ }
747
+ var Diagnostics = class {
748
+ constructor(appName) {
749
+ this.appName = appName;
750
+ this.errors = [];
751
+ this.warnings = [];
752
+ this.halted = false;
753
+ this.haltReason = null;
754
+ }
755
+ block(code, el, info = {}) {
756
+ const slug = (REGISTRY[code] || {}).slug || "error";
757
+ const rec = {
758
+ code,
759
+ slug,
760
+ el: cssPath(el),
761
+ attr: info.attr || null,
762
+ expr: info.expr || null,
763
+ key: info.key || null,
764
+ problem: info.problem || "",
765
+ didYouMean: info.didYouMean || null,
766
+ fix: info.fix || null,
767
+ phase: info.phase || "runtime"
768
+ };
769
+ const out = ["\n" + (code[0] === "W" ? "sap \u26A0 " : "sap \u2717 ") + code + " " + slug];
770
+ out.push(` at ${rec.el}${rec.attr ? ` \u2014 ${rec.attr}="${rec.expr ?? ""}"` : ""}`);
771
+ if (rec.problem) out.push(` problem: ${rec.problem}`);
772
+ if (rec.didYouMean) out.push(` did you mean: ${rec.didYouMean}`);
773
+ if (rec.fix) out.push(` fix: ${rec.fix}`);
774
+ console.error(out.join("\n"));
775
+ return rec;
776
+ }
777
+ error(code, el, info = {}) {
778
+ const rec = this.block(code, el, info);
779
+ this.errors.push(rec);
780
+ return rec;
781
+ }
782
+ warn(code, el, info = {}) {
783
+ const rec = this.block(code, el, info);
784
+ this.warnings.push(rec);
785
+ return rec;
786
+ }
787
+ report() {
788
+ return {
789
+ ok: this.errors.length === 0 && !this.halted,
790
+ app: this.appName,
791
+ halted: this.halted,
792
+ haltReason: this.haltReason,
793
+ errors: this.errors.slice(),
794
+ warnings: this.warnings.slice()
795
+ };
796
+ }
797
+ };
798
+ function setBeacon(el, code) {
799
+ if (el && el.getAttribute("sap-error") !== code) el.setAttribute("sap-error", code);
800
+ }
801
+ function clearBeacon(el) {
802
+ if (el && el.hasAttribute("sap-error")) el.removeAttribute("sap-error");
803
+ }
804
+ function didYouMean(name, candidates) {
805
+ let best = null;
806
+ let bestD = 3;
807
+ for (const c of candidates) {
808
+ const d = lev(name, c);
809
+ if (d < bestD) {
810
+ bestD = d;
811
+ best = c;
812
+ }
813
+ }
814
+ return best;
815
+ }
816
+ function lev(a, b) {
817
+ const m = a.length;
818
+ const n2 = b.length;
819
+ const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...new Array(n2).fill(0)]);
820
+ for (let j = 0; j <= n2; j++) dp[0][j] = j;
821
+ for (let i = 1; i <= m; i++)
822
+ for (let j = 1; j <= n2; j++)
823
+ dp[i][j] = Math.min(
824
+ dp[i - 1][j] + 1,
825
+ dp[i][j - 1] + 1,
826
+ dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
827
+ );
828
+ return dp[m][n2];
829
+ }
830
+
831
+ // src/mutation-bridge.js
832
+ var activeSource = null;
833
+ var installed = false;
834
+ var lateHubListener = null;
835
+ function hubSource(M) {
836
+ let unsub = null;
837
+ return {
838
+ kind: "hub",
839
+ subscribe(onChanges) {
840
+ const ret = M.onAnyChange({ debounce: 0, require: "observed" }, onChanges);
841
+ if (typeof ret === "function") unsub = ret;
842
+ },
843
+ suppress(fn) {
844
+ M.pause();
845
+ try {
846
+ return fn();
847
+ } finally {
848
+ M.resume();
849
+ }
850
+ },
851
+ teardown() {
852
+ if (unsub) {
853
+ try {
854
+ unsub();
855
+ } catch {
856
+ }
857
+ unsub = null;
858
+ }
859
+ }
860
+ };
861
+ }
862
+ function nativeSource() {
863
+ let observer = null;
864
+ return {
865
+ kind: "native",
866
+ subscribe(onChanges) {
867
+ observer = new MutationObserver((records) => onChanges(changesFromRecords(records)));
868
+ const target = typeof document !== "undefined" && document.body || typeof document !== "undefined" && document.documentElement;
869
+ if (target) {
870
+ observer.observe(target, { childList: true, subtree: true, attributes: true, characterData: true });
871
+ }
872
+ },
873
+ suppress(fn) {
874
+ const u = typeof window !== "undefined" && window.hyperclay && window.hyperclay.undo;
875
+ if (u && u.pause) u.pause();
876
+ try {
877
+ return fn();
878
+ } finally {
879
+ if (observer) observer.takeRecords();
880
+ if (u && u.resume) u.resume();
881
+ }
882
+ },
883
+ teardown() {
884
+ if (observer) {
885
+ observer.disconnect();
886
+ observer = null;
887
+ }
888
+ }
889
+ };
890
+ }
891
+ function changesFromRecords(records) {
892
+ const out = [];
893
+ for (const rec of records) {
894
+ if (rec.type === "attributes") {
895
+ const el = rec.target;
896
+ if (el && el.nodeType === 1 && !inertSubtree(el)) out.push({ type: "attribute", element: el });
897
+ } else if (rec.type === "characterData") {
898
+ const el = rec.target && rec.target.parentElement;
899
+ if (el && !inertSubtree(el)) out.push({ type: "characterData", element: el });
900
+ } else if (rec.type === "childList") {
901
+ for (const node of rec.addedNodes) {
902
+ if (node.nodeType !== 1 || inertSubtree(node)) continue;
903
+ out.push({ type: "add", element: node });
904
+ if (node.querySelectorAll) {
905
+ for (const desc of node.querySelectorAll("*")) {
906
+ if (!inertSubtree(desc)) out.push({ type: "add", element: desc });
907
+ }
908
+ }
909
+ }
910
+ for (const node of rec.removedNodes) {
911
+ if (node.nodeType !== 1) continue;
912
+ out.push({ type: "remove", element: node, parent: rec.target });
913
+ }
914
+ }
915
+ }
916
+ return out;
917
+ }
918
+ function inertSubtree(el) {
919
+ return !!(el.closest && el.closest("[sap-ignore],[template]"));
920
+ }
921
+ function scheduleAffectedApps(runtime2, changes) {
922
+ runtime2.pruneDisconnected();
923
+ const affected = /* @__PURE__ */ new Set();
924
+ let needRemount = false;
925
+ for (const c of changes) {
926
+ const target = c.type === "remove" ? c.parent : c.element;
927
+ const app = target && runtime2.appFor(target);
928
+ if (app) affected.add(app);
929
+ if (c.type === "add" && c.element && c.element.nodeType === 1 && c.element.hasAttribute && c.element.hasAttribute("sap") && !(runtime2.isRegistered && runtime2.isRegistered(c.element))) {
930
+ needRemount = true;
931
+ }
932
+ }
933
+ if (needRemount) runtime2.remountIfPresent();
934
+ for (const app of affected) if (app.root.isConnected) runtime2.schedule(app, "mutation");
935
+ }
936
+ function withDomMutationPaused(fn) {
937
+ if (activeSource) return activeSource.suppress(fn);
938
+ const u = typeof window !== "undefined" && window.hyperclay && window.hyperclay.undo;
939
+ if (u && u.pause && u.resume) {
940
+ u.pause();
941
+ try {
942
+ return fn();
943
+ } finally {
944
+ u.resume();
945
+ }
946
+ }
947
+ return fn();
948
+ }
949
+ function activate(source, runtime2) {
950
+ installed = true;
951
+ activeSource = source;
952
+ source.subscribe((changes) => scheduleAffectedApps(runtime2, changes));
953
+ }
954
+ function installMutationBridge(runtime2) {
955
+ if (installed) return;
956
+ if (typeof window === "undefined" || typeof document === "undefined") return;
957
+ const w = window;
958
+ const M = w.hyperclay && w.hyperclay.Mutation;
959
+ if (M && typeof M.onAnyChange === "function") {
960
+ activate(hubSource(M), runtime2);
961
+ return;
962
+ }
963
+ if (typeof MutationObserver === "function") {
964
+ activate(nativeSource(), runtime2);
965
+ lateHubListener = function onLateHub() {
966
+ const M2 = w.hyperclay && w.hyperclay.Mutation;
967
+ if (!M2) return;
968
+ document.removeEventListener("hyperclay:mutation-ready", lateHubListener);
969
+ lateHubListener = null;
970
+ if (activeSource) activeSource.teardown();
971
+ installed = false;
972
+ activate(hubSource(M2), runtime2);
973
+ };
974
+ document.addEventListener("hyperclay:mutation-ready", lateHubListener);
975
+ }
976
+ }
977
+ function resetMutationBridge() {
978
+ if (activeSource) {
979
+ try {
980
+ activeSource.teardown();
981
+ } catch {
982
+ }
983
+ }
984
+ if (lateHubListener && typeof document !== "undefined") {
985
+ document.removeEventListener("hyperclay:mutation-ready", lateHubListener);
986
+ }
987
+ activeSource = null;
988
+ installed = false;
989
+ lateHubListener = null;
990
+ }
991
+
992
+ // src/pass.js
993
+ var NATIVE_BOOLEAN_SET = new Set(NATIVE_BOOLEANS);
994
+ function runPass(app, opts = {}) {
995
+ if (opts.breaker) {
996
+ app.diag.error("E26", app.root, {
997
+ problem: "recompute loop halted after 50 passes; recent triggers: " + (app._breakerRing || []).join(", ")
998
+ });
999
+ setBeacon(app.root, "loop");
1000
+ return;
1001
+ }
1002
+ withDomMutationPaused(() => runPassBody(app, opts));
1003
+ }
1004
+ function runPassBody(app, opts) {
1005
+ const root = app.root;
1006
+ const rootObj = {};
1007
+ const calcs = [];
1008
+ const paints = [];
1009
+ const effects = [];
1010
+ const invalids = [];
1011
+ const pending = [];
1012
+ const whenFields = /* @__PURE__ */ new Set();
1013
+ app.diag.errors = [];
1014
+ const rootCtx = { root: rootObj, state: rootObj, item: null, ownerEl: root, projection: false };
1015
+ function resolveDefault(el, d) {
1016
+ let raw = el.getAttribute(d.name);
1017
+ if (raw == null) {
1018
+ raw = d.default != null ? d.default : d.type === "num" ? "0" : d.type === "bool" ? "false" : "";
1019
+ pending.push({ el, name: d.name, value: serializeTyped(parseTyped(raw, d.type), d.type) });
1020
+ }
1021
+ return parseTyped(raw, d.type);
1022
+ }
1023
+ function readState(el, obj) {
1024
+ const decls = parseStateDecl(el.getAttribute("state") || "");
1025
+ el._sapStateDecls = decls;
1026
+ for (const d of decls) {
1027
+ if (d.type === "string" && NATIVE_BOOLEAN_SET.has(d.name) && typeof el[d.name] === "boolean") {
1028
+ obj[d.name] = el.hasAttribute(d.name);
1029
+ continue;
1030
+ }
1031
+ if (d.transient) {
1032
+ obj[d.name] = readTransient(el, d);
1033
+ continue;
1034
+ }
1035
+ obj[d.name] = resolveDefault(el, d);
1036
+ }
1037
+ }
1038
+ function readBindInto(el, ctx) {
1039
+ if (!el.hasAttribute("bind")) return;
1040
+ const field = el.getAttribute("bind");
1041
+ const owner = ctx.item || ctx.state;
1042
+ const c = carrierFor(el);
1043
+ if (c.kind === "radio") {
1044
+ if (!(field in owner)) owner[field] = "";
1045
+ if (el.checked) owner[field] = el.value;
1046
+ } else {
1047
+ owner[field] = c.read();
1048
+ }
1049
+ if (el.hasAttribute("transient")) {
1050
+ el.removeAttribute("value");
1051
+ el.removeAttribute("checked");
1052
+ }
1053
+ }
1054
+ function collectEl(el, ctx) {
1055
+ for (const attr of el.attributes) {
1056
+ const name = attr.name;
1057
+ const val = attr.value;
1058
+ if (name.startsWith("calc:")) {
1059
+ calcs.push({ owner: ctx.item || ctx.state, ownerEl: ctx.ownerEl, name: name.slice(5), entry: compile(val), el, ctx });
1060
+ } else if (name === "text" || name.startsWith("text:")) {
1061
+ paints.push({ el, kind: "text", fmt: name.includes(":") ? name.slice(5) : null, entry: compile(val), ctx });
1062
+ } else if (name === "show") {
1063
+ paints.push({ el, kind: "show", entry: compile(val), ctx });
1064
+ } else if (name.startsWith("attr:")) {
1065
+ paints.push({ el, kind: "attr", arg: name.slice(5), entry: compile(val), ctx });
1066
+ } else if (name.startsWith("class:")) {
1067
+ paints.push({ el, kind: "class", arg: name.slice(6), entry: compile(val), ctx });
1068
+ } else if (name.startsWith("css:")) {
1069
+ paints.push({ el, kind: "css", arg: name.slice(4), entry: compile(val), ctx });
1070
+ } else if (name === "effect") {
1071
+ effects.push({ el, entry: compile(val, true), ctx });
1072
+ } else if (name === "invalid") {
1073
+ invalids.push({ el, entry: compile(val), ctx });
1074
+ } else if (name.startsWith("show-when:") || name.startsWith("option:")) {
1075
+ const field = name.slice(name.indexOf(":") + 1);
1076
+ whenFields.add(field);
1077
+ paints.push({ el, kind: "when", match: "show", field, values: val.split("|"), ctx });
1078
+ } else if (name.startsWith("hide-when:")) {
1079
+ const field = name.slice(name.indexOf(":") + 1);
1080
+ whenFields.add(field);
1081
+ paints.push({ el, kind: "when", match: "hide", field, values: val.split("|"), ctx });
1082
+ } else if (name.startsWith("option-not:")) {
1083
+ const field = name.slice(name.indexOf(":") + 1);
1084
+ whenFields.add(field);
1085
+ paints.push({ el, kind: "when", match: "show-not", field, values: val.split("|"), ctx });
1086
+ }
1087
+ }
1088
+ if (ctx.projection && el.hasAttribute("bind")) {
1089
+ paints.push({ el, kind: "projection", field: el.getAttribute("bind"), ctx });
1090
+ }
1091
+ }
1092
+ function defineRowMeta(rowObj, rowEl, i) {
1093
+ ensureId(rowEl, "row");
1094
+ Object.defineProperty(rowObj, "$key", { value: rowEl.id, enumerable: false });
1095
+ Object.defineProperty(rowObj, "$index", { value: i, enumerable: false });
1096
+ Object.defineProperty(rowObj, "$el", { value: rowEl, enumerable: false });
1097
+ }
1098
+ function enterScope(el, obj, ctx) {
1099
+ readState(el, obj);
1100
+ el._sapScope = obj;
1101
+ if (!ctx.projection && el.hasAttribute("bind")) readBindInto(el, ctx);
1102
+ collectEl(el, ctx);
1103
+ if (el.hasAttribute("items")) handleItems(el, obj, ctx);
1104
+ else walkScope(el, obj, ctx);
1105
+ }
1106
+ function walkScope(scopeEl, scopeObj, ctx) {
1107
+ walkOwned(scopeEl, (el, boundary) => {
1108
+ if (boundary) {
1109
+ if (el.hasAttribute("scope")) handleScope(el, scopeObj, ctx);
1110
+ else if (el.hasAttribute("items")) {
1111
+ collectEl(el, ctx);
1112
+ handleItems(el, scopeObj, ctx);
1113
+ } else if (el.hasAttribute("detail")) handleDetail(el, scopeObj, ctx);
1114
+ } else {
1115
+ if (!ctx.projection) readBindInto(el, ctx);
1116
+ collectEl(el, ctx);
1117
+ }
1118
+ });
1119
+ }
1120
+ function handleScope(el, parentObj, ctx) {
1121
+ const name = el.getAttribute("scope");
1122
+ const childObj = {};
1123
+ parentObj[name] = childObj;
1124
+ const cctx = { root: ctx.root, state: childObj, item: null, ownerEl: el, projection: ctx.projection };
1125
+ enterScope(el, childObj, cctx);
1126
+ }
1127
+ function handleItems(listEl, parentObj, ctx) {
1128
+ const name = listEl.getAttribute("items");
1129
+ const arr = [];
1130
+ parentObj[name] = arr;
1131
+ listEl._sapList = arr;
1132
+ rowsOf(listEl).forEach((rowEl, i) => {
1133
+ const rowObj = {};
1134
+ defineRowMeta(rowObj, rowEl, i);
1135
+ const rctx = { root: ctx.root, state: ctx.state, item: rowObj, ownerEl: rowEl, projection: ctx.projection };
1136
+ enterScope(rowEl, rowObj, rctx);
1137
+ arr.push(rowObj);
1138
+ });
1139
+ }
1140
+ function resolvePathValue(path, ctx) {
1141
+ const segs = String(path).split(".");
1142
+ let o = ctx.state;
1143
+ for (const s of segs) o = o == null ? void 0 : o[s];
1144
+ if (o === void 0) {
1145
+ o = ctx.root;
1146
+ for (const s of segs) o = o == null ? void 0 : o[s];
1147
+ }
1148
+ return o;
1149
+ }
1150
+ function handleDetail(el, parentObj, ctx) {
1151
+ for (const d of parseStateDecl(el.getAttribute("state") || "")) {
1152
+ if (!(d.name in parentObj)) parentObj[d.name] = resolveDefault(el, d);
1153
+ }
1154
+ el._sapScope = parentObj;
1155
+ const spec = el.getAttribute("detail") || "";
1156
+ const m = /^([\w.]+)\s+by\s+(.+)$/.exec(spec);
1157
+ if (!m) {
1158
+ el.hidden = true;
1159
+ el._sapDetailRow = null;
1160
+ return;
1161
+ }
1162
+ const listPath = m[1];
1163
+ const keyExpr = m[2].trim();
1164
+ const arr = resolvePathValue(listPath, ctx);
1165
+ let keyVal;
1166
+ try {
1167
+ keyVal = run(compile(keyExpr), { state: parentObj, item: ctx.item, el, root: ctx.root });
1168
+ } catch {
1169
+ keyVal = void 0;
1170
+ }
1171
+ const row = Array.isArray(arr) ? arr.find((r) => r && String(r.$key) === String(keyVal)) : null;
1172
+ el._sapDetailRow = row ? row.$el : null;
1173
+ if (!row) {
1174
+ if (!el.hidden) el.hidden = true;
1175
+ return;
1176
+ }
1177
+ if (el.hidden) el.hidden = false;
1178
+ const dctx = { root: ctx.root, state: parentObj, item: row, ownerEl: row.$el, projection: true };
1179
+ collectEl(el, dctx);
1180
+ walkScope(el, row, dctx);
1181
+ }
1182
+ enterScope(root, rootObj, rootCtx);
1183
+ if (pending.length && whenFields.size) {
1184
+ for (const w of pending) {
1185
+ if (whenFields.has(w.name) && w.el.getAttribute(w.name) == null) {
1186
+ w.el.setAttribute(w.name, w.value);
1187
+ }
1188
+ }
1189
+ }
1190
+ const groups = /* @__PURE__ */ new Map();
1191
+ for (const c of calcs) {
1192
+ let g = groups.get(c.owner);
1193
+ if (!g) {
1194
+ g = { ownerEl: c.ownerEl, list: [] };
1195
+ groups.set(c.owner, g);
1196
+ }
1197
+ g.list.push(c);
1198
+ }
1199
+ const ordered = [...groups.values()].sort((a, b) => depth(b.ownerEl) - depth(a.ownerEl));
1200
+ for (const g of ordered) {
1201
+ let sorted;
1202
+ try {
1203
+ sorted = topoSort(g.list);
1204
+ } catch (err) {
1205
+ app.diag.error("E07", g.list[0].el, { problem: err.message });
1206
+ sorted = g.list;
1207
+ }
1208
+ for (const c of sorted) {
1209
+ try {
1210
+ c.owner[c.name] = run(c.entry, { state: c.ctx.state, item: c.ctx.item, el: c.el, root: c.ctx.root });
1211
+ clearBeacon(c.el);
1212
+ } catch (err) {
1213
+ c.owner[c.name] = void 0;
1214
+ app.diag.error("E24", c.el, { attr: "calc:" + c.name, expr: c.entry.src, problem: String(err.message) });
1215
+ setBeacon(c.el, err.sapCode || "E24");
1216
+ }
1217
+ }
1218
+ }
1219
+ let writes = 0;
1220
+ for (const p of paints) writes += paintOne(p, app);
1221
+ for (const e of effects) runEffect(e, app);
1222
+ for (const iv of invalids) runInvalid(iv);
1223
+ app._state = rootObj;
1224
+ app._stats = {
1225
+ fields: Object.keys(rootObj).filter((k) => typeof rootObj[k] !== "function").length,
1226
+ calcs: calcs.length,
1227
+ paints: paints.length,
1228
+ lists: groups.size,
1229
+ writes
1230
+ };
1231
+ app._lastPass = { trigger: opts.trigger || "refresh", writes };
1232
+ if (app._debug) {
1233
+ console.log(`sap pass \xB7 trigger: ${app._lastPass.trigger} \xB7 writes ${writes}/${paints.length}`);
1234
+ }
1235
+ }
1236
+ function paintAttrName(p) {
1237
+ if (p.kind === "text") return p.fmt ? "text:" + p.fmt : "text";
1238
+ if (p.kind === "attr") return "attr:" + p.arg;
1239
+ if (p.kind === "class") return "class:" + p.arg;
1240
+ if (p.kind === "css") return "css:" + p.arg;
1241
+ if (p.kind === "when") return (p.match === "hide" ? "hide-when:" : p.match === "show-not" ? "option-not:" : "show-when:") + p.field;
1242
+ return p.kind;
1243
+ }
1244
+ function nearestMode(el, field, values2, notMode) {
1245
+ let cur = el;
1246
+ while (cur && cur.nodeType === 1) {
1247
+ if (cur.hasAttribute && cur.hasAttribute(field)) {
1248
+ const inList = values2.includes(cur.getAttribute(field));
1249
+ if (notMode ? !inList : inList) return true;
1250
+ }
1251
+ cur = cur.parentElement;
1252
+ }
1253
+ return false;
1254
+ }
1255
+ function applyProjection(el, v) {
1256
+ if (el === el.ownerDocument.activeElement) return 0;
1257
+ const c = carrierFor(el);
1258
+ const cur = c.read();
1259
+ const nv = v == null ? "" : v;
1260
+ if (String(cur) !== String(nv)) {
1261
+ c.write(nv, { silent: true });
1262
+ return 1;
1263
+ }
1264
+ return 0;
1265
+ }
1266
+ function paintOne(p, app) {
1267
+ if (p.kind === "projection") {
1268
+ return applyProjection(p.el, p.ctx.item ? p.ctx.item[p.field] : "");
1269
+ }
1270
+ if (p.kind === "when") {
1271
+ return applyPaint(p, null);
1272
+ }
1273
+ const ctx = { state: p.ctx.state, item: p.ctx.item, el: p.el, root: p.ctx.root };
1274
+ try {
1275
+ const value = run(p.entry, ctx);
1276
+ const n2 = applyPaint(p, value);
1277
+ clearBeacon(p.el);
1278
+ return n2;
1279
+ } catch (err) {
1280
+ app.diag.error(err.sapCode === "E22" ? "E22" : "E24", p.el, {
1281
+ attr: paintAttrName(p),
1282
+ expr: p.entry && p.entry.src,
1283
+ problem: String(err.message)
1284
+ });
1285
+ setBeacon(p.el, err.sapCode || "E24");
1286
+ return 0;
1287
+ }
1288
+ }
1289
+ function applyPaint(p, value) {
1290
+ const el = p.el;
1291
+ switch (p.kind) {
1292
+ case "text": {
1293
+ const s = p.fmt != null ? applyFormat(p.fmt, value) : value == null ? "" : String(value);
1294
+ if (el.textContent !== s) {
1295
+ el.textContent = s;
1296
+ return 1;
1297
+ }
1298
+ return 0;
1299
+ }
1300
+ case "show": {
1301
+ const hide = !value;
1302
+ if (el.hidden !== hide) {
1303
+ el.hidden = hide;
1304
+ return 1;
1305
+ }
1306
+ return 0;
1307
+ }
1308
+ case "when": {
1309
+ const ov = typeof window !== "undefined" && window.hyperclay && window.hyperclay.optionVisibility;
1310
+ if (ov && ov._started) {
1311
+ if (el._sapWhen && el.hidden) {
1312
+ el.hidden = false;
1313
+ el._sapWhen = false;
1314
+ return 1;
1315
+ }
1316
+ return 0;
1317
+ }
1318
+ let visible;
1319
+ if (p.match === "hide") visible = !nearestMode(el, p.field, p.values, false);
1320
+ else if (p.match === "show-not") visible = nearestMode(el, p.field, p.values, true);
1321
+ else visible = nearestMode(el, p.field, p.values, false);
1322
+ el._sapWhen = true;
1323
+ const hide = !visible;
1324
+ if (el.hidden !== hide) {
1325
+ el.hidden = hide;
1326
+ return 1;
1327
+ }
1328
+ return 0;
1329
+ }
1330
+ case "attr": {
1331
+ const a = p.arg;
1332
+ if (NATIVE_BOOLEAN_SET.has(a)) {
1333
+ const has = el.hasAttribute(a);
1334
+ if (value && !has) {
1335
+ el.setAttribute(a, "");
1336
+ return 1;
1337
+ }
1338
+ if (!value && has) {
1339
+ el.removeAttribute(a);
1340
+ return 1;
1341
+ }
1342
+ return 0;
1343
+ }
1344
+ const s = typeof value === "boolean" ? String(value) : value == null ? "" : String(value);
1345
+ if (el.getAttribute(a) !== s) {
1346
+ el.setAttribute(a, s);
1347
+ return 1;
1348
+ }
1349
+ return 0;
1350
+ }
1351
+ case "class": {
1352
+ const on = !!value;
1353
+ if (el.classList.contains(p.arg) !== on) {
1354
+ el.classList.toggle(p.arg, on);
1355
+ return 1;
1356
+ }
1357
+ return 0;
1358
+ }
1359
+ case "css": {
1360
+ const s = value == null ? "" : String(value);
1361
+ if (el.style.getPropertyValue("--" + p.arg) !== s) {
1362
+ el.style.setProperty("--" + p.arg, s);
1363
+ return 1;
1364
+ }
1365
+ return 0;
1366
+ }
1367
+ default:
1368
+ return 0;
1369
+ }
1370
+ }
1371
+ function runEffect(e, app) {
1372
+ try {
1373
+ run(e.entry, { state: e.ctx.state, item: e.ctx.item, el: e.el, root: e.ctx.root });
1374
+ clearBeacon(e.el);
1375
+ } catch (err) {
1376
+ app.diag.error("E24", e.el, { attr: "effect", expr: e.entry && e.entry.src, problem: String(err.message) });
1377
+ setBeacon(e.el, "E24");
1378
+ }
1379
+ }
1380
+ function runInvalid(iv) {
1381
+ let msg = "";
1382
+ try {
1383
+ const v = run(iv.entry, { state: iv.ctx.state, item: iv.ctx.item, el: iv.el, root: iv.ctx.root });
1384
+ msg = typeof v === "string" ? v : "";
1385
+ } catch {
1386
+ msg = "";
1387
+ }
1388
+ if (typeof iv.el.setCustomValidity === "function") iv.el.setCustomValidity(msg);
1389
+ }
1390
+ function depth(el) {
1391
+ let d = 0;
1392
+ let c = el;
1393
+ while (c) {
1394
+ if (c.hasAttribute && c.hasAttribute("sap")) break;
1395
+ c = c.parentElement;
1396
+ d++;
1397
+ }
1398
+ return d;
1399
+ }
1400
+
1401
+ // src/lint.js
1402
+ var NATIVE_BOOLEAN_SET2 = new Set(NATIVE_BOOLEANS);
1403
+ var COLON_PREFIXES = /* @__PURE__ */ new Set(["calc", "text", "attr", "class", "css", "set", "move", "sort", "option", "option-not", "show-when", "hide-when", "editmode"]);
1404
+ var WHEN_PREFIXES = /* @__PURE__ */ new Set(["show-when", "hide-when", "option", "option-not"]);
1405
+ var EXPR_SHAPED = /\b(?:state|item|root)\.|===|!==|&&|\|\||[()]|^\s*!/;
1406
+ var KNOWN_BARE = /* @__PURE__ */ new Set([
1407
+ "sap",
1408
+ "scope",
1409
+ "items",
1410
+ "item",
1411
+ "template",
1412
+ "bind",
1413
+ "show",
1414
+ "effect",
1415
+ "invalid",
1416
+ "detail",
1417
+ "state",
1418
+ "transient",
1419
+ "confirm",
1420
+ "default",
1421
+ "persist",
1422
+ "sortable",
1423
+ "trigger-add",
1424
+ "trigger-remove",
1425
+ "trigger-reset",
1426
+ "sap-ignore",
1427
+ "sap-error",
1428
+ "no-save",
1429
+ "no-watch",
1430
+ "no-undo",
1431
+ // hyperclayjs platform region markers (canonical + legacy aliases). sapjs tolerates
1432
+ // them so a co-loaded region attribute inside a [sap] root is never flagged a typo.
1433
+ "no-trigger-autosave",
1434
+ "freeze",
1435
+ "mutations-ignore",
1436
+ "save-remove",
1437
+ "save-ignore",
1438
+ "save-freeze"
1439
+ ]);
1440
+ function isControl(el) {
1441
+ return el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA";
1442
+ }
1443
+ function isBound(el) {
1444
+ return el.hasAttribute("bind");
1445
+ }
1446
+ function walkAll(root, visit) {
1447
+ visit(root);
1448
+ for (const child of root.children) descend(child);
1449
+ function descend(el) {
1450
+ if (el.nodeType !== 1) return;
1451
+ if (el.hasAttribute("sap-ignore") || el.hasAttribute("template")) {
1452
+ if (el.hasAttribute("sap-ignore")) return;
1453
+ }
1454
+ visit(el);
1455
+ for (const c of el.children) descend(c);
1456
+ }
1457
+ }
1458
+ function lintApp(root, diag) {
1459
+ let halted = false;
1460
+ const halt = (code, el, info) => {
1461
+ diag.error(code, el, info);
1462
+ halted = true;
1463
+ };
1464
+ const tw = root.ownerDocument.createTreeWalker(
1465
+ root,
1466
+ 4
1467
+ /* NodeFilter.SHOW_TEXT */
1468
+ );
1469
+ let node;
1470
+ while (node = tw.nextNode()) {
1471
+ if (/\{\{.*\}\}/.test(node.nodeValue || "")) {
1472
+ halt("E02", node.parentElement || root, {
1473
+ problem: "moustaches in text content are not a Sap feature",
1474
+ fix: 'move the expression onto the element: text="state.x"'
1475
+ });
1476
+ break;
1477
+ }
1478
+ }
1479
+ walkAll(root, (el) => {
1480
+ const inDetail = el.closest && el.closest("[detail]");
1481
+ for (const a of [...el.attributes]) {
1482
+ const name = a.name;
1483
+ const teach = teachForAttr(name);
1484
+ if (teach) {
1485
+ halt("E01", el, {
1486
+ attr: name,
1487
+ expr: a.value,
1488
+ problem: `"${name}" is a foreign-dialect attribute; Sap has no ${name.split(/[:=]/)[0]} directive`,
1489
+ fix: teach + " \u2014 or wrap the subtree in sap-ignore if intentional"
1490
+ });
1491
+ continue;
1492
+ }
1493
+ if (name.includes(":")) {
1494
+ const prefix = name.slice(0, name.indexOf(":"));
1495
+ if (!COLON_PREFIXES.has(prefix)) {
1496
+ const dym = didYouMean(prefix, [...COLON_PREFIXES]);
1497
+ diag.warn("W03", el, {
1498
+ attr: name,
1499
+ problem: `unknown "${prefix}:" attribute`,
1500
+ didYouMean: dym ? `${dym}:` : null
1501
+ });
1502
+ } else if (WHEN_PREFIXES.has(prefix) && EXPR_SHAPED.test(a.value)) {
1503
+ const field = name.slice(name.indexOf(":") + 1);
1504
+ diag.warn("W04", el, {
1505
+ attr: name,
1506
+ expr: a.value,
1507
+ problem: `${prefix}: compares the "${field}" attribute against a literal value, not a JS expression`,
1508
+ fix: `use show="${a.value}" for an expression, or a literal like ${prefix}:${field}="overview"`
1509
+ });
1510
+ }
1511
+ } else if (!KNOWN_BARE.has(name) && !HTML_GLOBALS.has(name) && !NATIVE_BOOLEAN_SET2.has(name) && !/^(data-|aria-|on)/.test(name) && !(name in el)) {
1512
+ const dym = didYouMean(name, [...KNOWN_BARE]);
1513
+ if (dym) {
1514
+ diag.warn("W03", el, { attr: name, problem: `unknown "${name}" attribute`, didYouMean: dym });
1515
+ }
1516
+ }
1517
+ if (name === "attr:hidden") {
1518
+ diag.warn("W03", el, { attr: name, problem: "attr:hidden paints the wrong attribute", fix: 'use show="expr"' });
1519
+ }
1520
+ if ((name === "attr:value" || name === "attr:checked" || name === "attr:selected") && isBound(el)) {
1521
+ halt("E30", el, { attr: name, problem: "attr: on a bound control collides with persist ownership", fix: "remove it; bind owns this attribute" });
1522
+ }
1523
+ if (name === "effect" && isBound(el) && /\b(value|checked)\s*=[^=]/.test(a.value)) {
1524
+ halt("E30", el, { attr: name, expr: a.value, problem: "effect writes value/checked on a bound control (writes a stale value with no synthetic event)", fix: "write through onclick + Sap(this) instead" });
1525
+ }
1526
+ if ((name === "text" || name.startsWith("text:") || name === "show") && isControl(el)) {
1527
+ if (name === "show") {
1528
+ } else {
1529
+ halt("E18", el, { attr: name, problem: "paint belongs on output elements, not form controls", fix: "move the paint to an <output> or <span>" });
1530
+ }
1531
+ }
1532
+ }
1533
+ if (el.hasAttribute("state")) {
1534
+ const decls = parseStateDecl(el.getAttribute("state"));
1535
+ const seen = /* @__PURE__ */ new Set();
1536
+ for (const d of decls) {
1537
+ if (d.name.includes("-")) {
1538
+ halt("E08", el, { attr: "state", problem: `field "${d.name}" is not a valid identifier (the hyphen parses as subtraction)`, fix: `rename to ${d.name.replace(/-/g, "")}` });
1539
+ }
1540
+ if (seen.has(d.name)) halt("E04", el, { attr: "state", problem: `field "${d.name}" is declared twice in one scope` });
1541
+ seen.add(d.name);
1542
+ if (RESERVED.has(d.name)) halt("E05", el, { attr: "state", problem: `"${d.name}" is a reserved Sap word` });
1543
+ if (HTML_GLOBALS.has(d.name)) halt("E06", el, { attr: "state", problem: `"${d.name}" is a global HTML attribute name; pick another field name` });
1544
+ if (d.name === "open" && el.tagName === "DIALOG") {
1545
+ halt("E33", el, { attr: "state", problem: "dialog open is transient", fix: "use showModal() \u2014 declare state= only on <details> or popovers" });
1546
+ }
1547
+ }
1548
+ }
1549
+ if (isBound(el)) {
1550
+ if (el.hasAttribute("-")) {
1551
+ }
1552
+ if (el.tagName === "INPUT") {
1553
+ const t = (el.getAttribute("type") || "text").toLowerCase();
1554
+ if (t === "file") halt("E32", el, { attr: "bind", problem: "files never serialize into an HTML file", fix: "use no-save + effect instead of bind" });
1555
+ if (t === "password" && !el.hasAttribute("transient")) {
1556
+ halt("E31", el, { attr: "bind", problem: "a password must never serialize into a world-readable file", fix: "add transient to the password input" });
1557
+ }
1558
+ } else if (el.tagName !== "SELECT" && el.tagName !== "TEXTAREA") {
1559
+ const ce = el.getAttribute("contenteditable");
1560
+ const editable = ce != null && ce !== "false";
1561
+ if (!editable && el.children.length > 0) {
1562
+ halt("E20", el, { attr: "bind", problem: "bind on a container element is not a control; a write would overwrite its children", fix: "bind a control (input/select/textarea), a contenteditable, or an empty text leaf" });
1563
+ }
1564
+ }
1565
+ }
1566
+ if (el.hasAttribute("transient") && el.hasAttribute("persist")) {
1567
+ halt("E34", el, {
1568
+ problem: "transient and persist are opposite: persist forces the live value into the saved bytes, leaking exactly what transient drops",
1569
+ fix: "remove one \u2014 transient to keep it out of the file, or persist to save it"
1570
+ });
1571
+ }
1572
+ if (el.hasAttribute("item") && !el.hasAttribute("template")) {
1573
+ const list = el.parentElement;
1574
+ if (!list || !list.hasAttribute("items")) {
1575
+ halt("E10", el, { problem: "an [item] must be a direct child of an [items] list", fix: 'wrap it in a container with items="name"' });
1576
+ }
1577
+ }
1578
+ if (el.hasAttribute("items") && inDetail) {
1579
+ halt("E17", el, { attr: "items", problem: "nested items inside a detail panel is a v1 mount error (nested collections ship in v1.1)" });
1580
+ }
1581
+ if (el.hasAttribute("items")) {
1582
+ const targeted = root.querySelector(`[trigger-add="${el.getAttribute("items")}"]`);
1583
+ if (!templateOf(el) && rowsOf(el).length === 0 && targeted) {
1584
+ halt("E17", el, { attr: "items", problem: `trigger-add targets items="${el.getAttribute("items")}" but it has no [item template] to clone` });
1585
+ }
1586
+ }
1587
+ });
1588
+ if (halted) {
1589
+ diag.halted = true;
1590
+ diag.haltReason = diag.errors[0] ? diag.errors[0].code : "halt";
1591
+ setBeacon(root, diag.haltReason);
1592
+ }
1593
+ return !halted;
1594
+ }
1595
+
1596
+ // src/mount.js
1597
+ var STYLE_ID = "sap-styles";
1598
+ var STYLE_TEXT = "[item][template]{display:none!important}[hidden]{display:none!important}[sap-error]{outline:2px solid #e5484d;outline-offset:1px}";
1599
+ var styledDocs = /* @__PURE__ */ new WeakSet();
1600
+ function injectStyles(doc) {
1601
+ if (styledDocs.has(doc)) return;
1602
+ const view = doc.defaultView;
1603
+ const SheetCtor = view && view.CSSStyleSheet;
1604
+ if (SheetCtor && "adoptedStyleSheets" in doc) {
1605
+ try {
1606
+ const sheet = new SheetCtor();
1607
+ sheet.replaceSync(STYLE_TEXT);
1608
+ doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, sheet];
1609
+ styledDocs.add(doc);
1610
+ return;
1611
+ } catch {
1612
+ }
1613
+ }
1614
+ if (!doc.getElementById(STYLE_ID)) {
1615
+ const style = doc.createElement("style");
1616
+ style.id = STYLE_ID;
1617
+ style.textContent = STYLE_TEXT;
1618
+ (doc.head || doc.documentElement).appendChild(style);
1619
+ }
1620
+ styledDocs.add(doc);
1621
+ }
1622
+ var appSeq = 0;
1623
+ function now() {
1624
+ return typeof performance !== "undefined" && performance.now ? performance.now() : 0;
1625
+ }
1626
+ function mountApp(root) {
1627
+ const name = root.id || root.getAttribute("sap") || `app-${appSeq++}`;
1628
+ const diag = new Diagnostics(name);
1629
+ const appRec = {
1630
+ root,
1631
+ name,
1632
+ diag,
1633
+ _state: null,
1634
+ _stats: null,
1635
+ _lastPass: null,
1636
+ _passes: 0,
1637
+ _broken: false,
1638
+ _mountMs: 0,
1639
+ _mountWrites: 0
1640
+ };
1641
+ const ok = lintApp(root, diag);
1642
+ if (!ok) {
1643
+ appRec.halted = true;
1644
+ return appRec;
1645
+ }
1646
+ injectStyles(root.ownerDocument);
1647
+ for (const ta of root.querySelectorAll("textarea[data-value]")) rehydrateControlFromAttributes(ta);
1648
+ const t0 = now();
1649
+ runPass(appRec, { trigger: "mount" });
1650
+ appRec._mountMs = now() - t0;
1651
+ appRec._mountWrites = appRec._stats ? appRec._stats.writes : 0;
1652
+ if (appRec._mountWrites > 0) {
1653
+ diag.warn("W30", root, {
1654
+ problem: `${appRec._mountWrites} paint(s) ran at mount; the saved file was out of sync with its declared state`,
1655
+ fix: "re-save once so the file mounts clean (a settled file writes nothing)"
1656
+ });
1657
+ }
1658
+ return appRec;
1659
+ }
1660
+
1661
+ // src/scope.js
1662
+ function ownedScope(scopeEl, name) {
1663
+ let found = null;
1664
+ walkOwned(scopeEl, (el) => {
1665
+ if (!found && el.getAttribute("scope") === name) found = el;
1666
+ });
1667
+ return found;
1668
+ }
1669
+ function declarerOf(fromEl, appRoot, field) {
1670
+ let cur = nearestScopeEl(fromEl) || appRoot;
1671
+ while (cur) {
1672
+ for (const d of parseStateDecl(cur.getAttribute("state") || "")) {
1673
+ if (d.name === field) return { el: cur, decl: d };
1674
+ }
1675
+ if (cur.hasAttribute("sap")) break;
1676
+ cur = nearestScopeEl(cur.parentElement);
1677
+ }
1678
+ return null;
1679
+ }
1680
+ function inferType(v) {
1681
+ if (typeof v === "number") return "num";
1682
+ if (typeof v === "boolean") return "bool";
1683
+ return "string";
1684
+ }
1685
+ function createAccessor(runtime2) {
1686
+ function contextElFor(el) {
1687
+ const appRec = runtime2.appFor(el);
1688
+ if (!appRec) return null;
1689
+ const detailEl = isInsideDetail(el);
1690
+ if (detailEl) {
1691
+ if (detailEl._sapDetailRow) return { ctxEl: detailEl._sapDetailRow, appRec, isRow: true };
1692
+ const sEl = nearestScopeEl(detailEl.parentElement) || appRec.root;
1693
+ return { ctxEl: sEl, appRec, isRow: false };
1694
+ }
1695
+ const itemEl = nearestItemEl(el);
1696
+ if (itemEl && itemEl.hasAttribute("item")) return { ctxEl: itemEl, appRec, isRow: true };
1697
+ const scopeEl = nearestScopeEl(el) || appRec.root;
1698
+ return { ctxEl: scopeEl, appRec, isRow: scopeEl.hasAttribute("item") };
1699
+ }
1700
+ function Sap2(elOrSel) {
1701
+ const el = typeof elOrSel === "string" ? document.querySelector(elOrSel) : elOrSel;
1702
+ if (!el || el.nodeType !== 1) return null;
1703
+ const res = contextElFor(el);
1704
+ if (!res) return null;
1705
+ return makeProxy(res.ctxEl, res.appRec, res.isRow);
1706
+ }
1707
+ function readField(ctxEl, appRec, field) {
1708
+ const control = ownedBind(ctxEl, field);
1709
+ if (control) return carrierFor(control).read();
1710
+ const listEl = ownedItems(ctxEl, field);
1711
+ if (listEl) return rowsOf(listEl).map((r) => makeProxy(r, appRec, true));
1712
+ const sub = ownedScope(ctxEl, field);
1713
+ if (sub) return makeProxy(sub, appRec, false);
1714
+ const decl = declarerOf(ctxEl, appRec.root, field);
1715
+ if (decl) {
1716
+ if (decl.decl.transient) return readTransient(decl.el, decl.decl);
1717
+ return parseTyped(decl.el.getAttribute(field), decl.decl.type);
1718
+ }
1719
+ const snap = ctxEl._sapScope;
1720
+ return snap ? snap[field] : void 0;
1721
+ }
1722
+ function setStateAttr(el, field, value, decl, appRec) {
1723
+ if (decl.transient) writeTransient(el, decl, value);
1724
+ else el.setAttribute(field, serializeTyped(value, decl.type));
1725
+ runtime2.schedule(appRec, "set:" + field);
1726
+ }
1727
+ function knownFieldsFor(ctxEl, appRoot) {
1728
+ const names = /* @__PURE__ */ new Set();
1729
+ let cur = nearestScopeEl(ctxEl) || appRoot;
1730
+ while (cur) {
1731
+ for (const d of parseStateDecl(cur.getAttribute("state") || "")) names.add(d.name);
1732
+ if (cur.hasAttribute("sap")) break;
1733
+ cur = nearestScopeEl(cur.parentElement);
1734
+ }
1735
+ walkOwned(ctxEl, (el) => {
1736
+ const b = el.getAttribute("bind");
1737
+ if (b) names.add(b);
1738
+ });
1739
+ return names;
1740
+ }
1741
+ function writeField(ctxEl, appRec, field, value) {
1742
+ const control = ownedBind(ctxEl, field);
1743
+ if (control) {
1744
+ carrierFor(control).write(value);
1745
+ return;
1746
+ }
1747
+ let probe = ctxEl;
1748
+ while (probe) {
1749
+ for (const attr of probe.attributes) {
1750
+ if (attr.name === "calc:" + field) {
1751
+ appRec.diag.error("E16", probe, { attr: "calc:" + field, problem: `"${field}" is a computed field; computed fields are read-only` });
1752
+ return;
1753
+ }
1754
+ }
1755
+ if (probe.hasAttribute("sap")) break;
1756
+ probe = probe.parentElement;
1757
+ }
1758
+ const decl = declarerOf(ctxEl, appRec.root, field);
1759
+ if (decl) {
1760
+ setStateAttr(decl.el, field, value, decl.decl, appRec);
1761
+ return;
1762
+ }
1763
+ const known = knownFieldsFor(ctxEl, appRec.root);
1764
+ known.delete(field);
1765
+ const guess = field.length >= 4 ? didYouMean(field, [...known]) : null;
1766
+ if (guess) {
1767
+ appRec.diag.error("E12", ctxEl, { key: field, problem: `unknown state key "${field}"`, didYouMean: guess });
1768
+ return;
1769
+ }
1770
+ if (RESERVED.has(field) || HTML_GLOBALS.has(field)) {
1771
+ appRec.diag.error("E15", ctxEl, {
1772
+ key: field,
1773
+ problem: `cannot write undeclared field "${field}" \u2014 it is a reserved/global name and cannot become state`,
1774
+ fix: "pick a different field name, or declare it explicitly in a state= attribute"
1775
+ });
1776
+ return;
1777
+ }
1778
+ const root = appRec.root;
1779
+ const type = inferType(value);
1780
+ const decls = root.getAttribute("state") || "";
1781
+ const token = type === "string" ? field : `${field}:${type}`;
1782
+ root.setAttribute("state", decls ? `${decls} ${token}` : token);
1783
+ setStateAttr(root, field, value, { name: field, type, transient: false }, appRec);
1784
+ }
1785
+ function freshIds(rootClone) {
1786
+ const stamp = (el) => {
1787
+ if (el.id) {
1788
+ el.id = "";
1789
+ ensureId(el, "row");
1790
+ }
1791
+ if (el.hasAttribute("sap-error")) el.removeAttribute("sap-error");
1792
+ };
1793
+ rootClone.id = "";
1794
+ ensureId(rootClone, "row");
1795
+ rootClone.querySelectorAll("[id]").forEach(stamp);
1796
+ }
1797
+ function doAdd(ctxEl, appRec, name) {
1798
+ const listEl = resolveListEl(appRec.root, ctxEl, name);
1799
+ if (!listEl) {
1800
+ appRec.diag.error("E17", ctxEl, { problem: `cannot resolve a list named "${name}" from here` });
1801
+ return null;
1802
+ }
1803
+ const tmpl = templateOf(listEl);
1804
+ if (!tmpl) {
1805
+ appRec.diag.error("E17", listEl, { problem: `items="${name}" has no [item template] to clone` });
1806
+ return null;
1807
+ }
1808
+ const clone = tmpl.cloneNode(true);
1809
+ clone.removeAttribute("template");
1810
+ freshIds(clone);
1811
+ listEl.appendChild(clone);
1812
+ runtime2.schedule(appRec, "trigger-add:" + name);
1813
+ return makeProxy(clone, appRec, true);
1814
+ }
1815
+ function doReset(ctxEl, appRec) {
1816
+ walkOwned(ctxEl, (el) => {
1817
+ if (el.hasAttribute("bind")) {
1818
+ const c = carrierFor(el);
1819
+ const def = el.getAttribute("default");
1820
+ if (c.kind === "checkbox" || c.kind === "radio") {
1821
+ c.write(def != null ? def === "" || def === "true" || el.value === def : el.hasAttribute("checked"));
1822
+ } else {
1823
+ c.write(def != null ? def : el.getAttribute("value") || "");
1824
+ }
1825
+ }
1826
+ });
1827
+ for (const d of parseStateDecl(ctxEl.getAttribute("state") || "")) {
1828
+ const dflt = d.default != null ? d.default : d.type === "num" ? "0" : d.type === "bool" ? "false" : "";
1829
+ if (d.transient) writeTransient(ctxEl, d, parseTyped(dflt, d.type));
1830
+ else ctxEl.setAttribute(d.name, dflt);
1831
+ }
1832
+ runtime2.schedule(appRec, "trigger-reset");
1833
+ }
1834
+ function doRemove(ctxEl, appRec) {
1835
+ if (!ctxEl.isConnected) throw new Error("stale row handle: $remove() on a removed row");
1836
+ ctxEl.remove();
1837
+ runtime2.schedule(appRec, "trigger-remove");
1838
+ }
1839
+ function doMove(ctxEl, appRec, target) {
1840
+ if (!ctxEl.isConnected) throw new Error("stale row handle: $move() on a removed row");
1841
+ let listEl = null;
1842
+ if (target && target.nodeType === 1) listEl = target;
1843
+ else if (typeof target === "string") listEl = resolveListEl(appRec.root, ctxEl, target);
1844
+ if (!listEl) {
1845
+ appRec.diag.error("E17", ctxEl, { problem: `$move() cannot resolve target "${target}"` });
1846
+ return;
1847
+ }
1848
+ runtime2.moveInto(listEl, ctxEl);
1849
+ runtime2.schedule(appRec, "trigger-move");
1850
+ }
1851
+ function rowIndex(ctxEl) {
1852
+ const list = ctxEl.parentElement;
1853
+ if (!list) return -1;
1854
+ return rowsOf(list).indexOf(ctxEl);
1855
+ }
1856
+ function makeProxy(ctxEl, appRec, isRow) {
1857
+ const verbs = {
1858
+ get $el() {
1859
+ return ctxEl;
1860
+ },
1861
+ get $key() {
1862
+ return ensureId(ctxEl, isRow ? "row" : "scope");
1863
+ },
1864
+ get $index() {
1865
+ return rowIndex(ctxEl);
1866
+ },
1867
+ $add(name) {
1868
+ return doAdd(ctxEl, appRec, name);
1869
+ },
1870
+ $reset() {
1871
+ doReset(ctxEl, appRec);
1872
+ return proxy;
1873
+ },
1874
+ $remove() {
1875
+ doRemove(ctxEl, appRec);
1876
+ },
1877
+ $move(target) {
1878
+ doMove(ctxEl, appRec, target);
1879
+ },
1880
+ get $appRec() {
1881
+ return appRec;
1882
+ }
1883
+ };
1884
+ const proxy = new Proxy(verbs, {
1885
+ get(t, prop) {
1886
+ if (typeof prop === "symbol") return t[prop];
1887
+ if (prop[0] === "$") return t[prop];
1888
+ return readField(ctxEl, appRec, prop);
1889
+ },
1890
+ set(t, prop, value) {
1891
+ if (typeof prop === "string" && prop[0] === "$") {
1892
+ t[prop] = value;
1893
+ return true;
1894
+ }
1895
+ writeField(ctxEl, appRec, prop, value);
1896
+ return true;
1897
+ },
1898
+ has(t, prop) {
1899
+ if (typeof prop === "string" && prop[0] === "$") return prop in t;
1900
+ return readField(ctxEl, appRec, prop) !== void 0;
1901
+ }
1902
+ });
1903
+ return proxy;
1904
+ }
1905
+ return { Sap: Sap2, contextElFor, readField, writeField, makeProxy };
1906
+ }
1907
+
1908
+ // src/actions.js
1909
+ var TRIGGER_WORDS = ["trigger-add", "trigger-remove", "trigger-reset"];
1910
+ function hasAction(el) {
1911
+ if (el.nodeType !== 1) return false;
1912
+ for (const a of el.attributes) {
1913
+ const n2 = a.name;
1914
+ if (n2.startsWith("set:") || n2.startsWith("move:") || n2.startsWith("sort:")) return true;
1915
+ if (TRIGGER_WORDS.includes(n2)) return true;
1916
+ }
1917
+ return false;
1918
+ }
1919
+ function findActionEl(target, root) {
1920
+ let el = target;
1921
+ const stop = root.parentElement;
1922
+ while (el && el !== stop) {
1923
+ if (el.nodeType === 1) {
1924
+ if (hasAction(el) && !(el.tagName === "FORM" && el.hasAttribute("trigger-add"))) return el;
1925
+ }
1926
+ el = el.parentElement;
1927
+ }
1928
+ return null;
1929
+ }
1930
+ function createActions(runtime2, accessor2) {
1931
+ const { Sap: Sap2 } = accessor2;
1932
+ function actionCtx(el, appRec) {
1933
+ let item = null;
1934
+ const detailEl = isInsideDetail(el);
1935
+ if (detailEl && detailEl._sapDetailRow) item = detailEl._sapDetailRow._sapScope || null;
1936
+ else {
1937
+ const itemEl = nearestItemEl(el);
1938
+ if (itemEl && itemEl.hasAttribute("item")) item = itemEl._sapScope || null;
1939
+ }
1940
+ const scopeEl = nearestScopeEl(el) || appRec.root;
1941
+ const state = scopeEl._sapScope || appRec._state || {};
1942
+ return { state, item, el, root: appRec._state || {} };
1943
+ }
1944
+ function gateThenRun(el, appRec, proceed) {
1945
+ if (!el.hasAttribute("confirm")) {
1946
+ proceed();
1947
+ return;
1948
+ }
1949
+ const raw = el.getAttribute("confirm");
1950
+ let msg = raw;
1951
+ const entry = compile(raw);
1952
+ if (!entry.error) {
1953
+ try {
1954
+ const v = run(entry, actionCtx(el, appRec));
1955
+ if (typeof v === "string" || typeof v === "number") msg = String(v);
1956
+ } catch {
1957
+ }
1958
+ }
1959
+ const consent = platformConsent();
1960
+ if (!consent) {
1961
+ if (window.confirm(msg)) proceed();
1962
+ return;
1963
+ }
1964
+ if (el._sapGatePending) return;
1965
+ el._sapGatePending = true;
1966
+ const done = () => {
1967
+ el._sapGatePending = false;
1968
+ };
1969
+ consent(msg, () => {
1970
+ done();
1971
+ proceed();
1972
+ }).catch(done);
1973
+ }
1974
+ function runSet(el, appRec) {
1975
+ for (const a of el.attributes) {
1976
+ if (!a.name.startsWith("set:")) continue;
1977
+ const field = a.name.slice(4);
1978
+ const entry = compile(a.value);
1979
+ if (entry.error) {
1980
+ appRec.diag.error("E08", el, { attr: a.name, expr: a.value, problem: String(entry.error.message) });
1981
+ continue;
1982
+ }
1983
+ let value;
1984
+ try {
1985
+ value = run(entry, actionCtx(el, appRec));
1986
+ } catch (err) {
1987
+ appRec.diag.error("E24", el, { attr: a.name, expr: a.value, problem: String(err.message) });
1988
+ continue;
1989
+ }
1990
+ const proxy = Sap2(el);
1991
+ if (proxy) proxy[field] = value;
1992
+ }
1993
+ }
1994
+ function moveStep(el, dir) {
1995
+ const row = el.closest("[item]");
1996
+ if (!row) return;
1997
+ const siblings = rowsOf(row.parentElement);
1998
+ const i = siblings.indexOf(row);
1999
+ if (dir === "up" && i > 0) runtime2.placeBefore(row, siblings[i - 1]);
2000
+ else if (dir === "down" && i < siblings.length - 1) runtime2.placeBefore(siblings[i + 1], row);
2001
+ }
2002
+ function resolveSortList(el, value, appRec) {
2003
+ if (value) return resolveListEl(appRec.root, el, value);
2004
+ let cur = el;
2005
+ while (cur) {
2006
+ if (cur.hasAttribute && cur.hasAttribute("items")) return cur;
2007
+ cur = cur.parentElement;
2008
+ }
2009
+ const scopeEl = nearestScopeEl(el) || appRec.root;
2010
+ const lists = [];
2011
+ walkOwned(scopeEl, (e) => {
2012
+ if (e.hasAttribute("items")) lists.push(e);
2013
+ });
2014
+ return lists[0] || null;
2015
+ }
2016
+ function cmp(a, b) {
2017
+ const ea = a == null || a === "";
2018
+ const eb = b == null || b === "";
2019
+ if (ea && eb) return 0;
2020
+ if (ea) return 1;
2021
+ if (eb) return -1;
2022
+ const na = Number(a), nb = Number(b);
2023
+ if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb;
2024
+ return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
2025
+ }
2026
+ function runSort(el, field, value, appRec) {
2027
+ const listEl = resolveSortList(el, value, appRec);
2028
+ if (!listEl) {
2029
+ appRec.diag.error("E17", el, { attr: "sort:" + field, problem: `no items list resolvable for sort:${field}` });
2030
+ return;
2031
+ }
2032
+ const rows = rowsOf(listEl);
2033
+ const valOf = (r) => r._sapScope ? r._sapScope[field] : void 0;
2034
+ const asc = [...rows].sort((a, b) => cmp(valOf(a), valOf(b)));
2035
+ const isAsc = asc.every((r, i) => r === rows[i]);
2036
+ const ordered = isAsc ? [...asc].reverse() : asc;
2037
+ for (const r of ordered) runtime2.moveInto(listEl, r);
2038
+ runtime2.schedule(appRec, "sort:" + field);
2039
+ }
2040
+ function focusFirstEditable(rowEl) {
2041
+ if (!rowEl || rowEl.hidden) return;
2042
+ const f = rowEl.querySelector("[bind]:not([type=hidden]), [contenteditable]");
2043
+ if (f && typeof f.focus === "function") f.focus();
2044
+ }
2045
+ function runAction(actionEl, appRec) {
2046
+ gateThenRun(actionEl, appRec, () => {
2047
+ const proxy = Sap2(actionEl);
2048
+ for (const a of [...actionEl.attributes]) {
2049
+ const n2 = a.name;
2050
+ if (n2 === "trigger-remove") proxy && proxy.$remove();
2051
+ else if (n2 === "trigger-reset") proxy && proxy.$reset();
2052
+ else if (n2 === "trigger-add") {
2053
+ const row = proxy && proxy.$add(a.value);
2054
+ if (row) focusFirstEditable(row.$el);
2055
+ } else if (n2 === "move:up") {
2056
+ moveStep(actionEl, "up");
2057
+ runtime2.schedule(appRec, "move:up");
2058
+ } else if (n2 === "move:down") {
2059
+ moveStep(actionEl, "down");
2060
+ runtime2.schedule(appRec, "move:down");
2061
+ } else if (n2.startsWith("move:to")) proxy && proxy.$move(a.value);
2062
+ else if (n2.startsWith("sort:")) runSort(actionEl, n2.slice(5), a.value, appRec);
2063
+ }
2064
+ if ([...actionEl.attributes].some((a) => a.name.startsWith("set:"))) runSet(actionEl, appRec);
2065
+ });
2066
+ }
2067
+ function onClick(e) {
2068
+ const t = e.target;
2069
+ if (!t || t.nodeType !== 1) return;
2070
+ const appRec = runtime2.appFor(t);
2071
+ if (!appRec) return;
2072
+ const actionEl = findActionEl(t, appRec.root);
2073
+ if (!actionEl) return;
2074
+ runAction(actionEl, appRec);
2075
+ }
2076
+ function clearControl(c) {
2077
+ const cr = carrierFor(c);
2078
+ const def = c.getAttribute("default");
2079
+ if (cr.kind === "checkbox") cr.write(def === "" || def === "true");
2080
+ else cr.write(def != null ? def : "");
2081
+ }
2082
+ function addFromForm(formEl, appRec, listName) {
2083
+ const proxy = Sap2(formEl);
2084
+ if (!proxy) return;
2085
+ const newRow = proxy.$add(listName);
2086
+ if (!newRow) return;
2087
+ const composers = [];
2088
+ walkOwned(formEl, (el) => {
2089
+ if (el.hasAttribute("bind")) composers.push(el);
2090
+ });
2091
+ for (const c of composers) {
2092
+ const target = ownedBind(newRow.$el, c.getAttribute("bind"));
2093
+ if (target) carrierFor(target).write(carrierFor(c).read());
2094
+ }
2095
+ composers.forEach(clearControl);
2096
+ if (composers.length) {
2097
+ if (typeof composers[0].focus === "function") composers[0].focus();
2098
+ } else {
2099
+ focusFirstEditable(newRow.$el);
2100
+ }
2101
+ }
2102
+ function onSubmit(e) {
2103
+ const form = e.target;
2104
+ if (!form || !form.hasAttribute || !form.hasAttribute("trigger-add")) return;
2105
+ const appRec = runtime2.appFor(form);
2106
+ if (!appRec) return;
2107
+ e.preventDefault();
2108
+ gateThenRun(form, appRec, () => addFromForm(form, appRec, form.getAttribute("trigger-add")));
2109
+ }
2110
+ function onReset(e) {
2111
+ const form = e.target;
2112
+ const appRec = runtime2.appFor(form);
2113
+ if (!appRec) return;
2114
+ setTimeout(() => {
2115
+ form.querySelectorAll("[bind]").forEach((c) => {
2116
+ c.dispatchEvent(new Event("input", { bubbles: true }));
2117
+ c.dispatchEvent(new Event("change", { bubbles: true }));
2118
+ });
2119
+ runtime2.schedule(appRec, "reset");
2120
+ }, 0);
2121
+ }
2122
+ function onToggle(e) {
2123
+ const appRec = runtime2.appFor(e.target);
2124
+ if (appRec) runtime2.schedule(appRec, "toggle");
2125
+ }
2126
+ function onInput(e) {
2127
+ const t = e.target;
2128
+ if (!t || t.nodeType !== 1) return;
2129
+ const appRec = runtime2.appFor(t);
2130
+ if (!appRec) return;
2131
+ if (t.hasAttribute("persist") && !regionSkipsSave(t)) serializeControlToAttributes(t);
2132
+ const detailEl = isInsideDetail(t);
2133
+ if (detailEl && detailEl._sapDetailRow && t.hasAttribute("bind")) {
2134
+ const src = ownedBind(detailEl._sapDetailRow, t.getAttribute("bind"));
2135
+ if (src && src !== t) {
2136
+ carrierFor(src).write(carrierFor(t).read());
2137
+ return;
2138
+ }
2139
+ }
2140
+ runtime2.schedule(appRec, e.type + " " + (t.id || t.tagName.toLowerCase()));
2141
+ }
2142
+ function onSorted(e) {
2143
+ const d = e.detail || {};
2144
+ for (const el of [d.from, d.to, e.target]) {
2145
+ if (!el || el.nodeType !== 1) continue;
2146
+ const appRec = runtime2.appFor(el);
2147
+ if (appRec) runtime2.schedule(appRec, "sorted");
2148
+ }
2149
+ }
2150
+ function onKeydown(e) {
2151
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
2152
+ const t = e.target;
2153
+ if (!t || t.nodeType !== 1 || t.tagName !== "INPUT") return;
2154
+ if (!t.hasAttribute("bind") || !t.hasAttribute("step")) return;
2155
+ const type = (t.getAttribute("type") || "text").toLowerCase();
2156
+ if (type !== "text" && type !== "tel" && type !== "search") return;
2157
+ const appRec = runtime2.appFor(t);
2158
+ if (!appRec) return;
2159
+ e.preventDefault();
2160
+ const step = num(t.getAttribute("step")) || 1;
2161
+ const dir = e.key === "ArrowUp" ? 1 : -1;
2162
+ let next = num(t.value) + step * (e.shiftKey ? 10 : 1) * dir;
2163
+ const min2 = t.getAttribute("min"), max2 = t.getAttribute("max");
2164
+ if (min2 !== null && min2 !== "" && next < num(min2)) next = num(min2);
2165
+ if (max2 !== null && max2 !== "" && next > num(max2)) next = num(max2);
2166
+ carrierFor(t).write(String(Number(next.toFixed(2))));
2167
+ }
2168
+ function install(doc = document) {
2169
+ doc.addEventListener("click", onClick);
2170
+ doc.addEventListener("submit", onSubmit);
2171
+ doc.addEventListener("reset", onReset);
2172
+ doc.addEventListener("toggle", onToggle, true);
2173
+ doc.addEventListener("input", onInput);
2174
+ doc.addEventListener("change", onInput);
2175
+ doc.addEventListener("clay:sorted", onSorted);
2176
+ doc.addEventListener("keydown", onKeydown);
2177
+ }
2178
+ return { install };
2179
+ }
2180
+
2181
+ // src/debug.js
2182
+ var ACTION_RE = /^(set:|move:|sort:|trigger-add$|trigger-remove$|trigger-reset$)/;
2183
+ function rootLabel(root) {
2184
+ return `${root.tagName.toLowerCase()}[sap]`;
2185
+ }
2186
+ function countActions(root) {
2187
+ let n2 = 0;
2188
+ for (const el of root.querySelectorAll("*")) {
2189
+ for (const a of el.attributes) if (ACTION_RE.test(a.name)) {
2190
+ n2++;
2191
+ break;
2192
+ }
2193
+ }
2194
+ for (const a of root.attributes) if (ACTION_RE.test(a.name)) {
2195
+ n2++;
2196
+ break;
2197
+ }
2198
+ return n2;
2199
+ }
2200
+ function exprSources(root) {
2201
+ const out = [];
2202
+ const scan = (el) => {
2203
+ for (const a of el.attributes) {
2204
+ const n2 = a.name;
2205
+ if (n2.startsWith("calc:") || n2 === "text" || n2.startsWith("text:") || n2 === "show" || n2.startsWith("attr:") || n2.startsWith("class:") || n2.startsWith("css:") || n2 === "effect" || n2 === "invalid" || n2.startsWith("set:") || n2 === "detail") out.push({ el, attr: n2, src: a.value });
2206
+ }
2207
+ };
2208
+ scan(root);
2209
+ for (const el of root.querySelectorAll("*")) scan(el);
2210
+ return out;
2211
+ }
2212
+ function createDebug(runtime2) {
2213
+ function appView(app) {
2214
+ const s = app._stats || {};
2215
+ const root = app.root;
2216
+ return {
2217
+ root: rootLabel(root),
2218
+ fields: s.fields || 0,
2219
+ calcs: s.calcs || 0,
2220
+ paints: s.paints || 0,
2221
+ actions: countActions(root),
2222
+ lists: (root.hasAttribute("items") ? 1 : 0) + root.querySelectorAll("[items]").length,
2223
+ rows: root.querySelectorAll("[item]:not([template])").length,
2224
+ warnings: app.diag.warnings.length,
2225
+ errors: app.diag.errors.length,
2226
+ mountWrites: app._mountWrites || 0,
2227
+ ms: Math.round((app._mountMs || 0) * 10) / 10,
2228
+ passes: app._passes || 0,
2229
+ lastPass: app._lastPass || null,
2230
+ ok: app.diag.errors.length === 0 && !app.halted
2231
+ };
2232
+ }
2233
+ function status() {
2234
+ const apps = runtime2.apps().map(appView);
2235
+ return { ok: apps.every((a) => a.ok), apps };
2236
+ }
2237
+ function greenLine(app) {
2238
+ const v = appView(app);
2239
+ if (!v.ok) {
2240
+ return `sap \u2717 ${v.root} \xB7 errors ${v.errors} \xB7 warnings ${v.warnings} \xB7 ${v.ms}ms`;
2241
+ }
2242
+ return `sap \u2713 ${v.root} \xB7 fields ${v.fields} \xB7 calcs ${v.calcs} \xB7 paints ${v.paints} \xB7 actions ${v.actions} \xB7 lists ${v.lists} \xB7 rows ${v.rows} \xB7 warnings ${v.warnings} \xB7 mount writes ${v.mountWrites} \xB7 ${v.ms}ms`;
2243
+ }
2244
+ function printGreenLines() {
2245
+ for (const app of runtime2.apps()) {
2246
+ const line = greenLine(app);
2247
+ if (app.diag.errors.length || app.halted) console.error(line);
2248
+ else console.log(line);
2249
+ }
2250
+ }
2251
+ function report() {
2252
+ const errors = [];
2253
+ const warnings = [];
2254
+ for (const app of runtime2.apps()) {
2255
+ errors.push(...app.diag.errors);
2256
+ warnings.push(...app.diag.warnings);
2257
+ }
2258
+ return { ok: errors.length === 0, errors, warnings, counts: { errors: errors.length, warnings: warnings.length } };
2259
+ }
2260
+ function resolveEl(elOrSel) {
2261
+ if (!elOrSel) return null;
2262
+ if (typeof elOrSel === "string") return document.querySelector(elOrSel);
2263
+ return elOrSel.nodeType === 1 ? elOrSel : null;
2264
+ }
2265
+ function why(elOrSel, field) {
2266
+ const el = resolveEl(elOrSel);
2267
+ if (!el) {
2268
+ console.warn("sap why: element not found");
2269
+ return null;
2270
+ }
2271
+ const lines = [`sap why <${el.tagName.toLowerCase()}> \u2014 ${cssPath(el)}`];
2272
+ const sapAttrs = [...el.attributes].filter((a) => /^(bind|text|show|effect|invalid|detail|calc:|text:|attr:|class:|css:|set:|state|items|scope)/.test(a.name));
2273
+ for (const a of sapAttrs) lines.push(` ${a.name}="${a.value}"`);
2274
+ let scopeEl = el;
2275
+ while (scopeEl && !scopeEl._sapScope) scopeEl = scopeEl.parentElement;
2276
+ if (scopeEl && scopeEl._sapScope) {
2277
+ const snap = scopeEl._sapScope;
2278
+ const keys = field ? [field] : Object.keys(snap);
2279
+ lines.push(" reads:");
2280
+ for (const k of keys) {
2281
+ const v = snap[k];
2282
+ if (typeof v === "function") continue;
2283
+ lines.push(` ${k} = ${JSON.stringify(Array.isArray(v) ? `[${v.length} rows]` : v)}`);
2284
+ }
2285
+ }
2286
+ lines.push(" serialization: textContent and value/checked attributes save with the file; reads live from properties");
2287
+ console.log(lines.join("\n"));
2288
+ return null;
2289
+ }
2290
+ function debug(flag) {
2291
+ const on = flag === void 0 ? true : !!flag;
2292
+ for (const app of runtime2.apps()) app._debug = on;
2293
+ console.log(`sap debug ${on ? "on" : "off"} \u2014 per-pass headers will ${on ? "" : "no longer "}log`);
2294
+ return on;
2295
+ }
2296
+ function doctor() {
2297
+ const findings = [];
2298
+ let checks = 0;
2299
+ for (const app of runtime2.apps()) {
2300
+ const root = app.root;
2301
+ const label = rootLabel(root);
2302
+ checks++;
2303
+ const d2 = new Diagnostics(app.name);
2304
+ lintApp(root, d2);
2305
+ for (const e2 of d2.errors) findings.push({ code: e2.code, severity: "error", message: `${label}: ${e2.code} ${e2.slug} at ${e2.el}` });
2306
+ for (const w2 of d2.warnings) findings.push({ code: w2.code, severity: "warn", message: `${label}: ${w2.code} ${w2.slug} at ${w2.el}` });
2307
+ checks++;
2308
+ runPass(app, { trigger: "doctor-dryrun" });
2309
+ const w = app._stats ? app._stats.writes : 0;
2310
+ if (w > 0) findings.push({ code: "W30", severity: "warn", message: `${label}: dry recompute wrote ${w} cells on a quiet page; an element is stale or non-idempotent` });
2311
+ checks++;
2312
+ const srcs = exprSources(root).map((s) => s.src).join(" \0 ");
2313
+ const decls = [];
2314
+ for (const el of [root, ...root.querySelectorAll("[state]")]) {
2315
+ for (const d of parseStateDecl(el.getAttribute("state") || "")) decls.push(d.name);
2316
+ }
2317
+ for (const name of decls) {
2318
+ const re = new RegExp(`(state|item)\\.${name}\\b`);
2319
+ if (!re.test(srcs)) findings.push({ code: "I33", severity: "info", message: `${label}: field "${name}" is declared but read nowhere (dead state)` });
2320
+ }
2321
+ checks++;
2322
+ const ids = /* @__PURE__ */ new Map();
2323
+ for (const el of root.querySelectorAll("[id]")) ids.set(el.id, (ids.get(el.id) || 0) + 1);
2324
+ for (const [id, n2] of ids) if (n2 > 1) findings.push({ code: "W32", severity: "warn", message: `${label}: duplicate id "${id}" (${n2} elements) \u2014 clone/edit collision` });
2325
+ checks++;
2326
+ for (const s of exprSources(root)) {
2327
+ if (s.src.length > 120) findings.push({ code: "I33", severity: "info", message: `${label}: ${s.attr} expression is ${s.src.length} chars; consider a calc: field` });
2328
+ }
2329
+ checks++;
2330
+ for (const el of root.querySelectorAll("[text], [text\\:num]")) {
2331
+ const t = el.textContent || "";
2332
+ if (/\.\d{5,}/.test(t)) findings.push({ code: "I33", severity: "info", message: `${label}: ${cssPath(el)} paints >4 decimals; consider text:FMT` });
2333
+ }
2334
+ }
2335
+ const e = findings.filter((f) => f.severity === "error").length;
2336
+ const wn = findings.filter((f) => f.severity === "warn").length;
2337
+ const i = findings.filter((f) => f.severity === "info").length;
2338
+ const ms = 0;
2339
+ console.log(`sap doctor \xB7 ${runtime2.apps().map((a) => rootLabel(a.root)).join(", ")} \xB7 errors ${e} \xB7 warnings ${wn} \xB7 info ${i} \xB7 ${checks} checks \xB7 ${ms}ms`);
2340
+ for (const f of findings) {
2341
+ const fn = f.severity === "error" ? console.error : f.severity === "warn" ? console.warn : console.info;
2342
+ fn(` ${f.severity === "error" ? "\u2717" : f.severity === "warn" ? "\u26A0" : "\xB7"} ${f.message}`);
2343
+ }
2344
+ return findings;
2345
+ }
2346
+ return { status, report, why, debug, doctor, greenLine, printGreenLines, REGISTRY };
2347
+ }
2348
+
2349
+ // src/sap.js
2350
+ var VERSION = "0.2.0";
2351
+ var registry = /* @__PURE__ */ new Map();
2352
+ var order = [];
2353
+ var scheduler = createScheduler(runPass);
2354
+ function appFor(el) {
2355
+ let cur = el;
2356
+ while (cur) {
2357
+ if (cur.nodeType === 1 && cur.hasAttribute && cur.hasAttribute("sap")) {
2358
+ return registry.get(cur) || null;
2359
+ }
2360
+ cur = cur.parentNode && cur.parentNode.host ? cur.parentNode.host : cur.parentElement;
2361
+ }
2362
+ return null;
2363
+ }
2364
+ function pruneDisconnected() {
2365
+ for (let i = order.length - 1; i >= 0; i--) {
2366
+ const app = order[i];
2367
+ if (!app.root.isConnected) {
2368
+ registry.delete(app.root);
2369
+ order.splice(i, 1);
2370
+ }
2371
+ }
2372
+ }
2373
+ function moveInto(parent, el) {
2374
+ if (parent.moveBefore) {
2375
+ try {
2376
+ parent.moveBefore(el, null);
2377
+ return;
2378
+ } catch {
2379
+ }
2380
+ }
2381
+ parent.appendChild(el);
2382
+ }
2383
+ function placeBefore(el, ref) {
2384
+ const parent = ref.parentElement;
2385
+ if (!parent) return;
2386
+ if (parent.moveBefore) {
2387
+ try {
2388
+ parent.moveBefore(el, ref);
2389
+ return;
2390
+ } catch {
2391
+ }
2392
+ }
2393
+ parent.insertBefore(el, ref);
2394
+ }
2395
+ var runtime = {
2396
+ apps: () => order.slice(),
2397
+ appFor,
2398
+ isRegistered: (el) => registry.has(el),
2399
+ pruneDisconnected,
2400
+ schedule: (app, trigger) => scheduler.schedule(app, trigger),
2401
+ runNow: (app, trigger) => scheduler.runNow(app, trigger),
2402
+ moveInto,
2403
+ placeBefore,
2404
+ remountIfPresent: () => mountAll()
2405
+ };
2406
+ var accessor = createAccessor(runtime);
2407
+ var actions = createActions(runtime, accessor);
2408
+ var debugApi = createDebug(runtime);
2409
+ var installed2 = false;
2410
+ function installOnce() {
2411
+ if (installed2) return;
2412
+ installed2 = true;
2413
+ actions.install(document);
2414
+ installBridges(runtime);
2415
+ }
2416
+ function mountAll(docRoot = document) {
2417
+ installOnce();
2418
+ installMutationBridge(runtime);
2419
+ const roots = docRoot.querySelectorAll("[sap]");
2420
+ const fresh = [];
2421
+ for (const root of roots) {
2422
+ if (registry.has(root)) continue;
2423
+ const appRec = mountApp(root);
2424
+ registry.set(root, appRec);
2425
+ order.push(appRec);
2426
+ fresh.push(appRec);
2427
+ }
2428
+ for (const app of fresh) {
2429
+ const line = debugApi.greenLine(app);
2430
+ if (app.diag.errors.length || app.halted) console.error(line);
2431
+ else console.log(line);
2432
+ }
2433
+ return fresh;
2434
+ }
2435
+ function mount(rootOrSel) {
2436
+ installOnce();
2437
+ installMutationBridge(runtime);
2438
+ if (!rootOrSel) return mountAll();
2439
+ const root = typeof rootOrSel === "string" ? document.querySelector(rootOrSel) : rootOrSel;
2440
+ if (!root) return null;
2441
+ if (registry.has(root)) return registry.get(root);
2442
+ const appRec = mountApp(root);
2443
+ registry.set(root, appRec);
2444
+ order.push(appRec);
2445
+ const line = debugApi.greenLine(appRec);
2446
+ if (appRec.diag.errors.length || appRec.halted) console.error(line);
2447
+ else console.log(line);
2448
+ return appRec;
2449
+ }
2450
+ var Sap = accessor.Sap;
2451
+ Sap.version = VERSION;
2452
+ Sap.refresh = function refresh() {
2453
+ for (const app of order) if (app.root.isConnected) scheduler.runNow(app, "refresh");
2454
+ };
2455
+ Sap.batch = batch;
2456
+ Sap.status = debugApi.status;
2457
+ Sap.report = debugApi.report;
2458
+ Sap.why = debugApi.why;
2459
+ Sap.debug = debugApi.debug;
2460
+ Sap.doctor = debugApi.doctor;
2461
+ Sap.greenLine = debugApi.greenLine;
2462
+ Sap.formats = formats;
2463
+ Sap.serializeControlToAttributes = serializeControlToAttributes;
2464
+ Sap.finalizeControlForSave = finalizeControlForSave;
2465
+ Sap.rehydrateControlFromAttributes = rehydrateControlFromAttributes;
2466
+ Sap.mount = mount;
2467
+ Sap.config = function config(options = {}) {
2468
+ if (options.formats) Object.assign(formats, options.formats);
2469
+ return Sap;
2470
+ };
2471
+ Sap._registry = registry;
2472
+ Sap._reset = function reset() {
2473
+ registry.clear();
2474
+ order.length = 0;
2475
+ resetMutationBridge();
2476
+ };
2477
+ function autoMount() {
2478
+ if (typeof document === "undefined") return;
2479
+ if (document.readyState === "loading") {
2480
+ document.addEventListener("DOMContentLoaded", () => mountAll());
2481
+ } else {
2482
+ mountAll();
2483
+ }
2484
+ }
2485
+ autoMount();
2486
+ if (typeof window !== "undefined") window.Sap = Sap;
2487
+ var sap_default = Sap;
2488
+ return __toCommonJS(sap_exports);
2489
+ })();
2490
+ if (typeof window !== 'undefined' && Sap && Sap.default) window.Sap = Sap.default;