@wcstack/state 1.10.4 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +244 -0
- package/README.md +75 -0
- package/dist/index.esm.js +443 -209
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -59,7 +59,7 @@ function setConfig(partialConfig) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
var version$1 = "1.
|
|
62
|
+
var version$1 = "1.11.1";
|
|
63
63
|
var pkg = {
|
|
64
64
|
version: version$1};
|
|
65
65
|
|
|
@@ -110,58 +110,6 @@ function replaceToReplaceNode(bindingInfo) {
|
|
|
110
110
|
node.parentNode.replaceChild(replaceNode, node);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function resolveNodePath(root, path) {
|
|
114
|
-
let currentNode = root;
|
|
115
|
-
if (path.length === 0)
|
|
116
|
-
return currentNode;
|
|
117
|
-
// path.reduce()だと途中でnullになる可能性があるので、
|
|
118
|
-
for (let i = 0; i < path.length; i++) {
|
|
119
|
-
currentNode = currentNode?.childNodes[path[i]] ?? null;
|
|
120
|
-
if (currentNode === null)
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
return currentNode;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function getBindingInfos(node, parseBindingTextResults) {
|
|
127
|
-
const bindingInfos = [];
|
|
128
|
-
for (const parseBindingTextResult of parseBindingTextResults) {
|
|
129
|
-
if (parseBindingTextResult.bindingType !== 'text') {
|
|
130
|
-
bindingInfos.push({
|
|
131
|
-
...parseBindingTextResult,
|
|
132
|
-
node: node,
|
|
133
|
-
replaceNode: node,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
const replaceNode = document.createTextNode('');
|
|
138
|
-
bindingInfos.push({
|
|
139
|
-
...parseBindingTextResult,
|
|
140
|
-
node: node,
|
|
141
|
-
replaceNode: replaceNode,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return bindingInfos;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const bindingsByNode = new WeakMap();
|
|
149
|
-
function getBindingsByNode(node) {
|
|
150
|
-
return bindingsByNode.get(node) || null;
|
|
151
|
-
}
|
|
152
|
-
function setBindingsByNode(node, bindings) {
|
|
153
|
-
bindingsByNode.set(node, bindings);
|
|
154
|
-
}
|
|
155
|
-
function addBindingByNode(node, binding) {
|
|
156
|
-
const bindings = getBindingsByNode(node);
|
|
157
|
-
if (bindings === null) {
|
|
158
|
-
setBindingsByNode(node, [binding]);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
bindings.push(binding);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
113
|
const DELIMITER = '.';
|
|
166
114
|
const WILDCARD = '*';
|
|
167
115
|
const MAX_WILDCARD_DEPTH = 128;
|
|
@@ -294,6 +242,237 @@ class PathInfo {
|
|
|
294
242
|
}
|
|
295
243
|
}
|
|
296
244
|
|
|
245
|
+
const cache = new WeakMap();
|
|
246
|
+
function getCustomElement(node) {
|
|
247
|
+
const cached = cache.get(node);
|
|
248
|
+
if (cached !== undefined) {
|
|
249
|
+
return cached;
|
|
250
|
+
}
|
|
251
|
+
let value = null;
|
|
252
|
+
try {
|
|
253
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
const element = node;
|
|
257
|
+
const tagName = element.tagName.toLowerCase();
|
|
258
|
+
if (tagName.includes("-")) {
|
|
259
|
+
return value = tagName;
|
|
260
|
+
}
|
|
261
|
+
if (element.hasAttribute("is")) {
|
|
262
|
+
const is = element.getAttribute("is");
|
|
263
|
+
if (is.includes("-")) {
|
|
264
|
+
return value = is;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
cache.set(node, value);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isValidWcBindable(value) {
|
|
275
|
+
if (typeof value !== "object" || value === null)
|
|
276
|
+
return false;
|
|
277
|
+
const v = value;
|
|
278
|
+
return v.protocol === "wc-bindable"
|
|
279
|
+
&& v.version === 1
|
|
280
|
+
&& Array.isArray(v.properties);
|
|
281
|
+
}
|
|
282
|
+
function makeExpandedEntry(name, base, stateName) {
|
|
283
|
+
// Dot-relative spread keeps the loop item root (`.`) without producing `..foo`.
|
|
284
|
+
const expandedPath = base === "." ? `.${name}` : `${base}.${name}`;
|
|
285
|
+
return {
|
|
286
|
+
propName: name,
|
|
287
|
+
propSegments: [name],
|
|
288
|
+
propModifiers: [],
|
|
289
|
+
statePathName: expandedPath,
|
|
290
|
+
statePathInfo: getPathInfo(expandedPath),
|
|
291
|
+
stateName,
|
|
292
|
+
inFilters: [],
|
|
293
|
+
outFilters: [],
|
|
294
|
+
bindingType: 'prop',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function dedupKey(r) {
|
|
298
|
+
switch (r.bindingType) {
|
|
299
|
+
case 'prop':
|
|
300
|
+
case 'event':
|
|
301
|
+
case 'radio':
|
|
302
|
+
case 'checkbox':
|
|
303
|
+
return `${r.bindingType}::${r.propName}`;
|
|
304
|
+
case 'spread':
|
|
305
|
+
return null;
|
|
306
|
+
default:
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Expand spread bind-text entries (`...: target`) into per-prop entries
|
|
312
|
+
* by enumerating wcBindable.properties + inputs of the element's class.
|
|
313
|
+
*
|
|
314
|
+
* Behavior:
|
|
315
|
+
* - With `allowDeferred: true` (default): if class is not yet defined, the
|
|
316
|
+
* spread entry stays so the caller can wait via customElements.whenDefined.
|
|
317
|
+
* - With `allowDeferred: false`: raises if class is not defined.
|
|
318
|
+
* - Duplicate propName: last-wins (explicit binding overrides spread).
|
|
319
|
+
* - When config.debug, console.debug logs each override.
|
|
320
|
+
* - Mid-`*` in target path is allowed (e.g. `...: stores.*.fetch`).
|
|
321
|
+
*
|
|
322
|
+
* Composite Profile (COMPOSITE.md / SPEC-extensions § 4) support:
|
|
323
|
+
* - A composite shell exposes its synthesized declaration via the standard
|
|
324
|
+
* `target.constructor.wcBindable` surface (§ 1 Discovery), so this function
|
|
325
|
+
* handles it without any composite-specific code path.
|
|
326
|
+
* - Composed property names use the `<sourceId>.<sourceName>` pattern
|
|
327
|
+
* (e.g. "s3.progress"); we keep the dotted name as a single segment so
|
|
328
|
+
* element member access stays flat (element["s3.progress"], not nested).
|
|
329
|
+
* - The expanded state path becomes `targetBase.s3.progress`, which resolves
|
|
330
|
+
* as nested state access — author state as `{ s3: { progress: 0 } }` to
|
|
331
|
+
* mirror the composed structure.
|
|
332
|
+
* - Tier claim (Symbol.for("wc-bindable.composite.tiers")) is not read here;
|
|
333
|
+
* spread covers observation (T1) and writable inputs (T2) transparently
|
|
334
|
+
* through normal property assignment, and commands stay out of spread by
|
|
335
|
+
* design regardless of tier.
|
|
336
|
+
*/
|
|
337
|
+
function expandSpread(node, results, options = {}) {
|
|
338
|
+
const allowDeferred = options.allowDeferred ?? true;
|
|
339
|
+
if (!results.some(r => r.bindingType === 'spread')) {
|
|
340
|
+
return results;
|
|
341
|
+
}
|
|
342
|
+
const expanded = [];
|
|
343
|
+
const spreadOrigin = new WeakSet();
|
|
344
|
+
for (const result of results) {
|
|
345
|
+
if (result.bindingType !== 'spread') {
|
|
346
|
+
expanded.push(result);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
350
|
+
raiseError(`Spread binding requires an element node.`);
|
|
351
|
+
}
|
|
352
|
+
const element = node;
|
|
353
|
+
const tagName = getCustomElement(element);
|
|
354
|
+
if (tagName === null) {
|
|
355
|
+
raiseError(`Spread binding "${result.statePathName}" requires a custom element with wcBindable, but <${element.tagName.toLowerCase()}> is not a custom element.`);
|
|
356
|
+
}
|
|
357
|
+
const customClass = customElements.get(tagName);
|
|
358
|
+
if (typeof customClass === "undefined") {
|
|
359
|
+
if (!allowDeferred) {
|
|
360
|
+
raiseError(`Spread binding "${result.statePathName}" requires <${tagName}> to be registered. Define the custom element before initializing this binding.`);
|
|
361
|
+
}
|
|
362
|
+
// Deferred: keep spread entry intact; caller retries via whenDefined.
|
|
363
|
+
expanded.push(result);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const bindable = customClass.wcBindable;
|
|
367
|
+
if (!isValidWcBindable(bindable)) {
|
|
368
|
+
raiseError(`Spread binding "${result.statePathName}" requires <${tagName}> to declare wcBindable (protocol="wc-bindable", version=1).`);
|
|
369
|
+
}
|
|
370
|
+
const targetBase = result.statePathName;
|
|
371
|
+
const stateName = result.stateName;
|
|
372
|
+
const seen = new Set();
|
|
373
|
+
for (const prop of bindable.properties) {
|
|
374
|
+
if (seen.has(prop.name))
|
|
375
|
+
continue;
|
|
376
|
+
seen.add(prop.name);
|
|
377
|
+
const entry = makeExpandedEntry(prop.name, targetBase, stateName);
|
|
378
|
+
spreadOrigin.add(entry);
|
|
379
|
+
expanded.push(entry);
|
|
380
|
+
}
|
|
381
|
+
// properties win over inputs when the name overlaps because they carry the
|
|
382
|
+
// full property contract (for example change events).
|
|
383
|
+
for (const input of (bindable.inputs ?? [])) {
|
|
384
|
+
if (seen.has(input.name))
|
|
385
|
+
continue;
|
|
386
|
+
seen.add(input.name);
|
|
387
|
+
const entry = makeExpandedEntry(input.name, targetBase, stateName);
|
|
388
|
+
spreadOrigin.add(entry);
|
|
389
|
+
expanded.push(entry);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Last-wins de-duplication
|
|
393
|
+
const lastIndexByKey = new Map();
|
|
394
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
395
|
+
const key = dedupKey(expanded[i]);
|
|
396
|
+
if (key !== null) {
|
|
397
|
+
lastIndexByKey.set(key, i);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const final = [];
|
|
401
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
402
|
+
const key = dedupKey(expanded[i]);
|
|
403
|
+
if (key === null) {
|
|
404
|
+
final.push(expanded[i]);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (lastIndexByKey.get(key) === i) {
|
|
408
|
+
final.push(expanded[i]);
|
|
409
|
+
}
|
|
410
|
+
else if (config.debug && spreadOrigin.has(expanded[i])) {
|
|
411
|
+
const overrider = expanded[lastIndexByKey.get(key)];
|
|
412
|
+
const tagText = node.nodeType === Node.ELEMENT_NODE
|
|
413
|
+
? `<${node.tagName.toLowerCase()}>`
|
|
414
|
+
: 'node';
|
|
415
|
+
console.debug(`[@wcstack/state] spread: prop "${expanded[i].propName}" of ${tagText} overridden by explicit binding (statePath: "${overrider.statePathName}").`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return final;
|
|
419
|
+
}
|
|
420
|
+
function hasUnresolvedSpread(results) {
|
|
421
|
+
return results.some(r => r.bindingType === 'spread');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function resolveNodePath(root, path) {
|
|
425
|
+
let currentNode = root;
|
|
426
|
+
if (path.length === 0)
|
|
427
|
+
return currentNode;
|
|
428
|
+
// path.reduce()だと途中でnullになる可能性があるので、
|
|
429
|
+
for (let i = 0; i < path.length; i++) {
|
|
430
|
+
currentNode = currentNode?.childNodes[path[i]] ?? null;
|
|
431
|
+
if (currentNode === null)
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
return currentNode;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function getBindingInfos(node, parseBindingTextResults) {
|
|
438
|
+
const bindingInfos = [];
|
|
439
|
+
for (const parseBindingTextResult of parseBindingTextResults) {
|
|
440
|
+
if (parseBindingTextResult.bindingType !== 'text') {
|
|
441
|
+
bindingInfos.push({
|
|
442
|
+
...parseBindingTextResult,
|
|
443
|
+
node: node,
|
|
444
|
+
replaceNode: node,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
const replaceNode = document.createTextNode('');
|
|
449
|
+
bindingInfos.push({
|
|
450
|
+
...parseBindingTextResult,
|
|
451
|
+
node: node,
|
|
452
|
+
replaceNode: replaceNode,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return bindingInfos;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const bindingsByNode = new WeakMap();
|
|
460
|
+
function getBindingsByNode(node) {
|
|
461
|
+
return bindingsByNode.get(node) || null;
|
|
462
|
+
}
|
|
463
|
+
function setBindingsByNode(node, bindings) {
|
|
464
|
+
bindingsByNode.set(node, bindings);
|
|
465
|
+
}
|
|
466
|
+
function addBindingByNode(node, binding) {
|
|
467
|
+
const bindings = getBindingsByNode(node);
|
|
468
|
+
if (bindings === null) {
|
|
469
|
+
setBindingsByNode(node, [binding]);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
bindings.push(binding);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
297
476
|
const STRUCTURAL_BINDING_TYPE_SET = new Set([
|
|
298
477
|
"if",
|
|
299
478
|
"elseif",
|
|
@@ -1247,6 +1426,7 @@ function parseStatePart(statePart) {
|
|
|
1247
1426
|
// elseif: statePart only (single binding for conditional rendering)
|
|
1248
1427
|
// for: statePart only (single binding for loop rendering)
|
|
1249
1428
|
// onclick: statePart, onchange: statePart etc. (event listeners)
|
|
1429
|
+
// ...: statePart (spread — expand wcBindable properties+inputs of target object)
|
|
1250
1430
|
function parseBindTextsForElement(bindText) {
|
|
1251
1431
|
const [...bindTexts] = bindText.split(';').map(trimFn).filter(s => s.length > 0);
|
|
1252
1432
|
const results = bindTexts.map((bindText) => {
|
|
@@ -1270,6 +1450,23 @@ function parseBindTextsForElement(bindText) {
|
|
|
1270
1450
|
bindingType: 'else',
|
|
1271
1451
|
};
|
|
1272
1452
|
}
|
|
1453
|
+
else if (propPart === '...') {
|
|
1454
|
+
const stateResult = parseStatePart(statePart);
|
|
1455
|
+
if (stateResult.outFilters.length > 0) {
|
|
1456
|
+
raiseError(`Invalid spread binding "${bindText}": filters are not allowed on spread targets.`);
|
|
1457
|
+
}
|
|
1458
|
+
if (stateResult.statePathName.length === 0) {
|
|
1459
|
+
raiseError(`Invalid spread binding "${bindText}": spread target path is required.`);
|
|
1460
|
+
}
|
|
1461
|
+
return {
|
|
1462
|
+
propName: '...',
|
|
1463
|
+
propSegments: ['...'],
|
|
1464
|
+
propModifiers: [],
|
|
1465
|
+
inFilters: [],
|
|
1466
|
+
...stateResult,
|
|
1467
|
+
bindingType: 'spread',
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1273
1470
|
else if (propPart === 'if'
|
|
1274
1471
|
|| propPart === 'elseif'
|
|
1275
1472
|
|| propPart === 'for'
|
|
@@ -1430,20 +1627,39 @@ function getSubscriberNodes(root) {
|
|
|
1430
1627
|
}
|
|
1431
1628
|
|
|
1432
1629
|
const registeredNodeSet = new WeakSet();
|
|
1630
|
+
function processParseResultsForNode(node, parseResults, options) {
|
|
1631
|
+
const expanded = expandSpread(node, parseResults, { allowDeferred: options.allowDeferred });
|
|
1632
|
+
if (hasUnresolvedSpread(expanded)) {
|
|
1633
|
+
const tagName = node.nodeType === Node.ELEMENT_NODE
|
|
1634
|
+
? getCustomElement(node)
|
|
1635
|
+
: null;
|
|
1636
|
+
if (tagName === null) {
|
|
1637
|
+
raiseError(`Spread binding deferred but element is not a custom element.`);
|
|
1638
|
+
}
|
|
1639
|
+
return { bindings: [], deferred: { node, tagName, parseResults } };
|
|
1640
|
+
}
|
|
1641
|
+
registeredNodeSet.add(node);
|
|
1642
|
+
const bindings = getBindingInfos(node, expanded);
|
|
1643
|
+
setBindingsByNode(node, bindings);
|
|
1644
|
+
resolveInitializedBinding(node);
|
|
1645
|
+
return { bindings, deferred: null };
|
|
1646
|
+
}
|
|
1433
1647
|
function collectNodesAndBindingInfos(root) {
|
|
1434
1648
|
const subscriberNodes = getSubscriberNodes(root);
|
|
1435
1649
|
const allBindings = [];
|
|
1650
|
+
const deferredSpreads = [];
|
|
1436
1651
|
for (const node of subscriberNodes) {
|
|
1437
|
-
if (
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1652
|
+
if (registeredNodeSet.has(node))
|
|
1653
|
+
continue;
|
|
1654
|
+
const parseResults = getParseBindTextResults(node);
|
|
1655
|
+
const result = processParseResultsForNode(node, parseResults, { allowDeferred: true });
|
|
1656
|
+
if (result.deferred !== null) {
|
|
1657
|
+
deferredSpreads.push(result.deferred);
|
|
1658
|
+
continue;
|
|
1444
1659
|
}
|
|
1660
|
+
allBindings.push(...result.bindings);
|
|
1445
1661
|
}
|
|
1446
|
-
return [subscriberNodes, allBindings];
|
|
1662
|
+
return [subscriberNodes, allBindings, deferredSpreads];
|
|
1447
1663
|
}
|
|
1448
1664
|
function collectNodesAndBindingInfosByFragment(root, nodeInfos) {
|
|
1449
1665
|
const nodes = [];
|
|
@@ -1453,17 +1669,119 @@ function collectNodesAndBindingInfosByFragment(root, nodeInfos) {
|
|
|
1453
1669
|
if (node === null) {
|
|
1454
1670
|
raiseError(`Node not found by path [${nodeInfo.nodePath.join(', ')}] in fragment.`);
|
|
1455
1671
|
}
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
nodes.push(node);
|
|
1463
|
-
}
|
|
1672
|
+
if (registeredNodeSet.has(node))
|
|
1673
|
+
continue;
|
|
1674
|
+
const result = processParseResultsForNode(node, nodeInfo.parseBindTextResults, { allowDeferred: false });
|
|
1675
|
+
// deferred is impossible when allowDeferred=false (expandSpread raises instead)
|
|
1676
|
+
allBindings.push(...result.bindings);
|
|
1677
|
+
nodes.push(node);
|
|
1464
1678
|
}
|
|
1465
1679
|
return [nodes, allBindings];
|
|
1466
1680
|
}
|
|
1681
|
+
function unregisterNode(node) {
|
|
1682
|
+
registeredNodeSet.delete(node);
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Re-process a deferred spread entry once the custom element class is
|
|
1686
|
+
* registered. Expands the captured parseResults, installs bindings, and
|
|
1687
|
+
* returns them so the caller can attach handlers and apply state values.
|
|
1688
|
+
*/
|
|
1689
|
+
function processDeferredNode(entry) {
|
|
1690
|
+
const { node, parseResults } = entry;
|
|
1691
|
+
unregisterNode(node);
|
|
1692
|
+
const result = processParseResultsForNode(node, parseResults, { allowDeferred: false });
|
|
1693
|
+
return result.bindings;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const _cache$3 = new WeakMap();
|
|
1697
|
+
const _cacheNullListIndex$1 = new WeakMap();
|
|
1698
|
+
class StateAddress {
|
|
1699
|
+
pathInfo;
|
|
1700
|
+
listIndex;
|
|
1701
|
+
_parentAddress;
|
|
1702
|
+
constructor(pathInfo, listIndex) {
|
|
1703
|
+
this.pathInfo = pathInfo;
|
|
1704
|
+
this.listIndex = listIndex;
|
|
1705
|
+
}
|
|
1706
|
+
get parentAddress() {
|
|
1707
|
+
if (typeof this._parentAddress !== 'undefined') {
|
|
1708
|
+
return this._parentAddress;
|
|
1709
|
+
}
|
|
1710
|
+
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
1711
|
+
if (parentPathInfo === null) {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
1715
|
+
let parentListIndex = null;
|
|
1716
|
+
if (lastSegment === WILDCARD) {
|
|
1717
|
+
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
parentListIndex = this.listIndex;
|
|
1721
|
+
}
|
|
1722
|
+
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function createStateAddress(pathInfo, listIndex) {
|
|
1726
|
+
if (listIndex === null) {
|
|
1727
|
+
let cached = _cacheNullListIndex$1.get(pathInfo);
|
|
1728
|
+
if (typeof cached !== "undefined") {
|
|
1729
|
+
return cached;
|
|
1730
|
+
}
|
|
1731
|
+
cached = new StateAddress(pathInfo, null);
|
|
1732
|
+
_cacheNullListIndex$1.set(pathInfo, cached);
|
|
1733
|
+
return cached;
|
|
1734
|
+
}
|
|
1735
|
+
else {
|
|
1736
|
+
let cacheByPathInfo = _cache$3.get(listIndex);
|
|
1737
|
+
if (typeof cacheByPathInfo === "undefined") {
|
|
1738
|
+
cacheByPathInfo = new WeakMap();
|
|
1739
|
+
_cache$3.set(listIndex, cacheByPathInfo);
|
|
1740
|
+
}
|
|
1741
|
+
let cached = cacheByPathInfo.get(pathInfo);
|
|
1742
|
+
if (typeof cached !== "undefined") {
|
|
1743
|
+
return cached;
|
|
1744
|
+
}
|
|
1745
|
+
cached = new StateAddress(pathInfo, listIndex);
|
|
1746
|
+
cacheByPathInfo.set(pathInfo, cached);
|
|
1747
|
+
return cached;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// _subscribers は Set のため挿入順を保持する。
|
|
1752
|
+
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
1753
|
+
class CommandToken {
|
|
1754
|
+
_name;
|
|
1755
|
+
_subscribers = new Set();
|
|
1756
|
+
constructor(name) {
|
|
1757
|
+
this._name = name;
|
|
1758
|
+
}
|
|
1759
|
+
get name() {
|
|
1760
|
+
return this._name;
|
|
1761
|
+
}
|
|
1762
|
+
get size() {
|
|
1763
|
+
return this._subscribers.size;
|
|
1764
|
+
}
|
|
1765
|
+
subscribe(fn) {
|
|
1766
|
+
this._subscribers.add(fn);
|
|
1767
|
+
return () => {
|
|
1768
|
+
this._subscribers.delete(fn);
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
unsubscribe(fn) {
|
|
1772
|
+
return this._subscribers.delete(fn);
|
|
1773
|
+
}
|
|
1774
|
+
emit(...args) {
|
|
1775
|
+
const results = [];
|
|
1776
|
+
for (const fn of this._subscribers) {
|
|
1777
|
+
results.push(fn(...args));
|
|
1778
|
+
}
|
|
1779
|
+
return results;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
function isCommandToken(value) {
|
|
1783
|
+
return value instanceof CommandToken;
|
|
1784
|
+
}
|
|
1467
1785
|
|
|
1468
1786
|
const loopContextByNode = new WeakMap();
|
|
1469
1787
|
function getLoopContextByNode(node) {
|
|
@@ -1493,13 +1811,18 @@ const connectedCallbackSymbol = Symbol("$$connectedCallback");
|
|
|
1493
1811
|
const disconnectedCallbackSymbol = Symbol("$$disconnectedCallback");
|
|
1494
1812
|
const updatedCallbackSymbol = Symbol("$$updatedCallback");
|
|
1495
1813
|
|
|
1814
|
+
// onclick: $command.<name> のように、DOM イベントから command token を直接 emit する形式かを判定する。
|
|
1815
|
+
// 右辺が $command 名前空間配下のパス($command.<token>)のときに true。
|
|
1816
|
+
function isCommandTokenPath(statePathName) {
|
|
1817
|
+
return statePathName.startsWith(STATE_COMMAND_NAMESPACE_NAME + ".");
|
|
1818
|
+
}
|
|
1496
1819
|
const handlerByHandlerKey$3 = new Map();
|
|
1497
1820
|
const bindingSetByHandlerKey$3 = new Map();
|
|
1498
1821
|
function getHandlerKey$3(binding) {
|
|
1499
1822
|
const modifierKey = binding.propModifiers.filter(m => m === 'prevent' || m === 'stop').sort().join(',');
|
|
1500
1823
|
return `${binding.stateName}::${binding.statePathName}::${modifierKey}`;
|
|
1501
1824
|
}
|
|
1502
|
-
const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event) => {
|
|
1825
|
+
const stateEventHandlerFunction = (stateName, handlerName, modifiers, statePathInfo) => (event) => {
|
|
1503
1826
|
if (modifiers.includes('prevent'))
|
|
1504
1827
|
event.preventDefault();
|
|
1505
1828
|
if (modifiers.includes('stop'))
|
|
@@ -1511,13 +1834,23 @@ const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event)
|
|
|
1511
1834
|
raiseError(`State element with name "${stateName}" not found for event handler.`);
|
|
1512
1835
|
}
|
|
1513
1836
|
const loopContext = getLoopContextByNode(node);
|
|
1837
|
+
const isCommand = isCommandTokenPath(handlerName);
|
|
1514
1838
|
stateElement.createStateAsync("writable", async (state) => {
|
|
1515
1839
|
state[setLoopContextSymbol](loopContext, () => {
|
|
1840
|
+
const indexes = loopContext?.listIndex.indexes ?? [];
|
|
1841
|
+
if (isCommand) {
|
|
1842
|
+
// command token を解決して emit。引数はハンドラ呼び出しと同じく (event, ...listIndexes) を透過する。
|
|
1843
|
+
const token = state[getByAddressSymbol](createStateAddress(statePathInfo, null));
|
|
1844
|
+
if (!isCommandToken(token)) {
|
|
1845
|
+
raiseError(`Event binding "${handlerName}" did not resolve to a CommandToken. Declare the name in $commandTokens and reference it as $command.<name>.`);
|
|
1846
|
+
}
|
|
1847
|
+
return token.emit(event, ...indexes);
|
|
1848
|
+
}
|
|
1516
1849
|
const handler = state[handlerName];
|
|
1517
1850
|
if (typeof handler !== "function") {
|
|
1518
1851
|
raiseError(`Handler "${handlerName}" is not a function on state "${stateName}".`);
|
|
1519
1852
|
}
|
|
1520
|
-
return Reflect.apply(handler, state, [event, ...
|
|
1853
|
+
return Reflect.apply(handler, state, [event, ...indexes]);
|
|
1521
1854
|
});
|
|
1522
1855
|
});
|
|
1523
1856
|
};
|
|
@@ -1528,7 +1861,7 @@ function attachEventHandler(binding) {
|
|
|
1528
1861
|
const key = getHandlerKey$3(binding);
|
|
1529
1862
|
let stateEventHandler = handlerByHandlerKey$3.get(key);
|
|
1530
1863
|
if (typeof stateEventHandler === "undefined") {
|
|
1531
|
-
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers);
|
|
1864
|
+
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers, binding.statePathInfo);
|
|
1532
1865
|
handlerByHandlerKey$3.set(key, stateEventHandler);
|
|
1533
1866
|
}
|
|
1534
1867
|
const eventName = binding.propName.slice(2);
|
|
@@ -1544,35 +1877,6 @@ function attachEventHandler(binding) {
|
|
|
1544
1877
|
return true;
|
|
1545
1878
|
}
|
|
1546
1879
|
|
|
1547
|
-
const cache = new WeakMap();
|
|
1548
|
-
function getCustomElement(node) {
|
|
1549
|
-
const cached = cache.get(node);
|
|
1550
|
-
if (cached !== undefined) {
|
|
1551
|
-
return cached;
|
|
1552
|
-
}
|
|
1553
|
-
let value = null;
|
|
1554
|
-
try {
|
|
1555
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
1556
|
-
return value;
|
|
1557
|
-
}
|
|
1558
|
-
const element = node;
|
|
1559
|
-
const tagName = element.tagName.toLowerCase();
|
|
1560
|
-
if (tagName.includes("-")) {
|
|
1561
|
-
return value = tagName;
|
|
1562
|
-
}
|
|
1563
|
-
if (element.hasAttribute("is")) {
|
|
1564
|
-
const is = element.getAttribute("is");
|
|
1565
|
-
if (is.includes("-")) {
|
|
1566
|
-
return value = is;
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
return value;
|
|
1570
|
-
}
|
|
1571
|
-
finally {
|
|
1572
|
-
cache.set(node, value);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
1880
|
const CHECK_TYPES = new Set(['radio', 'checkbox']);
|
|
1577
1881
|
const DEFAULT_VALUE_PROP_NAMES = new Set(['value', 'valueAsNumber', 'valueAsDate']);
|
|
1578
1882
|
function isPossibleTwoWay(node, propName) {
|
|
@@ -1738,19 +2042,19 @@ function setLastListValueByAbsoluteStateAddress(address, value) {
|
|
|
1738
2042
|
lastListValueByAbsoluteStateAddress.set(address, value);
|
|
1739
2043
|
}
|
|
1740
2044
|
|
|
1741
|
-
const _cache$
|
|
2045
|
+
const _cache$2 = new WeakMap();
|
|
1742
2046
|
function getAbsolutePathInfo(stateElement, pathInfo) {
|
|
1743
|
-
if (_cache$
|
|
1744
|
-
const pathMap = _cache$
|
|
2047
|
+
if (_cache$2.has(stateElement)) {
|
|
2048
|
+
const pathMap = _cache$2.get(stateElement);
|
|
1745
2049
|
if (pathMap.has(pathInfo)) {
|
|
1746
2050
|
return pathMap.get(pathInfo);
|
|
1747
2051
|
}
|
|
1748
2052
|
}
|
|
1749
2053
|
else {
|
|
1750
|
-
_cache$
|
|
2054
|
+
_cache$2.set(stateElement, new WeakMap());
|
|
1751
2055
|
}
|
|
1752
2056
|
const absolutePathInfo = Object.freeze(new AbsolutePathInfo(stateElement, pathInfo));
|
|
1753
|
-
_cache$
|
|
2057
|
+
_cache$2.get(stateElement).set(pathInfo, absolutePathInfo);
|
|
1754
2058
|
return absolutePathInfo;
|
|
1755
2059
|
}
|
|
1756
2060
|
class AbsolutePathInfo {
|
|
@@ -1771,8 +2075,8 @@ class AbsolutePathInfo {
|
|
|
1771
2075
|
}
|
|
1772
2076
|
}
|
|
1773
2077
|
|
|
1774
|
-
const _cache$
|
|
1775
|
-
const _cacheNullListIndex
|
|
2078
|
+
const _cache$1 = new WeakMap();
|
|
2079
|
+
const _cacheNullListIndex = new WeakMap();
|
|
1776
2080
|
class AbsoluteStateAddress {
|
|
1777
2081
|
absolutePathInfo;
|
|
1778
2082
|
listIndex;
|
|
@@ -1802,19 +2106,19 @@ class AbsoluteStateAddress {
|
|
|
1802
2106
|
}
|
|
1803
2107
|
function createAbsoluteStateAddress(absolutePathInfo, listIndex) {
|
|
1804
2108
|
if (listIndex === null) {
|
|
1805
|
-
let cached = _cacheNullListIndex
|
|
2109
|
+
let cached = _cacheNullListIndex.get(absolutePathInfo);
|
|
1806
2110
|
if (typeof cached !== "undefined") {
|
|
1807
2111
|
return cached;
|
|
1808
2112
|
}
|
|
1809
2113
|
cached = new AbsoluteStateAddress(absolutePathInfo, null);
|
|
1810
|
-
_cacheNullListIndex
|
|
2114
|
+
_cacheNullListIndex.set(absolutePathInfo, cached);
|
|
1811
2115
|
return cached;
|
|
1812
2116
|
}
|
|
1813
2117
|
else {
|
|
1814
|
-
let cacheByAbsolutePathInfo = _cache$
|
|
2118
|
+
let cacheByAbsolutePathInfo = _cache$1.get(listIndex);
|
|
1815
2119
|
if (typeof cacheByAbsolutePathInfo === "undefined") {
|
|
1816
2120
|
cacheByAbsolutePathInfo = new WeakMap();
|
|
1817
|
-
_cache$
|
|
2121
|
+
_cache$1.set(listIndex, cacheByAbsolutePathInfo);
|
|
1818
2122
|
}
|
|
1819
2123
|
let cached = cacheByAbsolutePathInfo.get(absolutePathInfo);
|
|
1820
2124
|
if (typeof cached !== "undefined") {
|
|
@@ -1990,41 +2294,6 @@ function applyChangeToClass(binding, _context, newValue) {
|
|
|
1990
2294
|
element.classList.toggle(className, newValue);
|
|
1991
2295
|
}
|
|
1992
2296
|
|
|
1993
|
-
// _subscribers は Set のため挿入順を保持する。
|
|
1994
|
-
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
1995
|
-
class CommandToken {
|
|
1996
|
-
_name;
|
|
1997
|
-
_subscribers = new Set();
|
|
1998
|
-
constructor(name) {
|
|
1999
|
-
this._name = name;
|
|
2000
|
-
}
|
|
2001
|
-
get name() {
|
|
2002
|
-
return this._name;
|
|
2003
|
-
}
|
|
2004
|
-
get size() {
|
|
2005
|
-
return this._subscribers.size;
|
|
2006
|
-
}
|
|
2007
|
-
subscribe(fn) {
|
|
2008
|
-
this._subscribers.add(fn);
|
|
2009
|
-
return () => {
|
|
2010
|
-
this._subscribers.delete(fn);
|
|
2011
|
-
};
|
|
2012
|
-
}
|
|
2013
|
-
unsubscribe(fn) {
|
|
2014
|
-
return this._subscribers.delete(fn);
|
|
2015
|
-
}
|
|
2016
|
-
emit(...args) {
|
|
2017
|
-
const results = [];
|
|
2018
|
-
for (const fn of this._subscribers) {
|
|
2019
|
-
results.push(fn(...args));
|
|
2020
|
-
}
|
|
2021
|
-
return results;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
function isCommandToken(value) {
|
|
2025
|
-
return value instanceof CommandToken;
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
2297
|
/**
|
|
2029
2298
|
* command.<methodName>: <commandToken-path> バインディングの適用ハンドラ。
|
|
2030
2299
|
*
|
|
@@ -2107,61 +2376,6 @@ function applyChangeToCommand(binding, _context, newValue) {
|
|
|
2107
2376
|
subscribedBindings.set(binding, { token, unsubscribe, elementRef });
|
|
2108
2377
|
}
|
|
2109
2378
|
|
|
2110
|
-
const _cache$1 = new WeakMap();
|
|
2111
|
-
const _cacheNullListIndex = new WeakMap();
|
|
2112
|
-
class StateAddress {
|
|
2113
|
-
pathInfo;
|
|
2114
|
-
listIndex;
|
|
2115
|
-
_parentAddress;
|
|
2116
|
-
constructor(pathInfo, listIndex) {
|
|
2117
|
-
this.pathInfo = pathInfo;
|
|
2118
|
-
this.listIndex = listIndex;
|
|
2119
|
-
}
|
|
2120
|
-
get parentAddress() {
|
|
2121
|
-
if (typeof this._parentAddress !== 'undefined') {
|
|
2122
|
-
return this._parentAddress;
|
|
2123
|
-
}
|
|
2124
|
-
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
2125
|
-
if (parentPathInfo === null) {
|
|
2126
|
-
return null;
|
|
2127
|
-
}
|
|
2128
|
-
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
2129
|
-
let parentListIndex = null;
|
|
2130
|
-
if (lastSegment === WILDCARD) {
|
|
2131
|
-
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
2132
|
-
}
|
|
2133
|
-
else {
|
|
2134
|
-
parentListIndex = this.listIndex;
|
|
2135
|
-
}
|
|
2136
|
-
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
function createStateAddress(pathInfo, listIndex) {
|
|
2140
|
-
if (listIndex === null) {
|
|
2141
|
-
let cached = _cacheNullListIndex.get(pathInfo);
|
|
2142
|
-
if (typeof cached !== "undefined") {
|
|
2143
|
-
return cached;
|
|
2144
|
-
}
|
|
2145
|
-
cached = new StateAddress(pathInfo, null);
|
|
2146
|
-
_cacheNullListIndex.set(pathInfo, cached);
|
|
2147
|
-
return cached;
|
|
2148
|
-
}
|
|
2149
|
-
else {
|
|
2150
|
-
let cacheByPathInfo = _cache$1.get(listIndex);
|
|
2151
|
-
if (typeof cacheByPathInfo === "undefined") {
|
|
2152
|
-
cacheByPathInfo = new WeakMap();
|
|
2153
|
-
_cache$1.set(listIndex, cacheByPathInfo);
|
|
2154
|
-
}
|
|
2155
|
-
let cached = cacheByPathInfo.get(pathInfo);
|
|
2156
|
-
if (typeof cached !== "undefined") {
|
|
2157
|
-
return cached;
|
|
2158
|
-
}
|
|
2159
|
-
cached = new StateAddress(pathInfo, listIndex);
|
|
2160
|
-
cacheByPathInfo.set(pathInfo, cached);
|
|
2161
|
-
return cached;
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
2379
|
const indexBindingsByContent = new WeakMap();
|
|
2166
2380
|
function getIndexBindingsByContent(content) {
|
|
2167
2381
|
return indexBindingsByContent.get(content) ?? [];
|
|
@@ -3665,13 +3879,7 @@ function _initializeBindings(allBindings) {
|
|
|
3665
3879
|
attachCheckboxEventHandler(binding);
|
|
3666
3880
|
}
|
|
3667
3881
|
}
|
|
3668
|
-
function
|
|
3669
|
-
const [subscriberNodes, allBindings] = collectNodesAndBindingInfos(root);
|
|
3670
|
-
for (const node of subscriberNodes) {
|
|
3671
|
-
setLoopContextByNode(node, parentLoopContext);
|
|
3672
|
-
}
|
|
3673
|
-
_initializeBindings(allBindings);
|
|
3674
|
-
// create absolute state address and register binding infos
|
|
3882
|
+
function _registerAbsoluteAddresses(allBindings) {
|
|
3675
3883
|
for (const binding of allBindings) {
|
|
3676
3884
|
const absoluteStateAddress = getAbsoluteStateAddressByBinding(binding);
|
|
3677
3885
|
addBindingByAbsoluteStateAddress(absoluteStateAddress, binding);
|
|
@@ -3684,8 +3892,34 @@ function initializeBindings(root, parentLoopContext) {
|
|
|
3684
3892
|
stateElement.setPathInfo(binding.statePathName, binding.bindingType);
|
|
3685
3893
|
}
|
|
3686
3894
|
}
|
|
3895
|
+
}
|
|
3896
|
+
function _scheduleDeferredSpreads(deferredSpreads, parentLoopContext) {
|
|
3897
|
+
for (const entry of deferredSpreads) {
|
|
3898
|
+
customElements.whenDefined(entry.tagName).then(() => {
|
|
3899
|
+
if (!entry.node.isConnected)
|
|
3900
|
+
return; // node was removed before class became ready
|
|
3901
|
+
const bindings = processDeferredNode(entry);
|
|
3902
|
+
if (bindings.length === 0)
|
|
3903
|
+
return;
|
|
3904
|
+
setLoopContextByNode(entry.node, parentLoopContext);
|
|
3905
|
+
_initializeBindings(bindings);
|
|
3906
|
+
_registerAbsoluteAddresses(bindings);
|
|
3907
|
+
applyChangeFromBindings(bindings);
|
|
3908
|
+
}).catch((error) => {
|
|
3909
|
+
console.error(`[@wcstack/state] deferred spread failed for <${entry.tagName}>.`, error);
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
function initializeBindings(root, parentLoopContext) {
|
|
3914
|
+
const [subscriberNodes, allBindings, deferredSpreads] = collectNodesAndBindingInfos(root);
|
|
3915
|
+
for (const node of subscriberNodes) {
|
|
3916
|
+
setLoopContextByNode(node, parentLoopContext);
|
|
3917
|
+
}
|
|
3918
|
+
_initializeBindings(allBindings);
|
|
3919
|
+
_registerAbsoluteAddresses(allBindings);
|
|
3687
3920
|
// apply all at once
|
|
3688
3921
|
applyChangeFromBindings(allBindings);
|
|
3922
|
+
_scheduleDeferredSpreads(deferredSpreads, parentLoopContext);
|
|
3689
3923
|
}
|
|
3690
3924
|
function initializeBindingsByFragment(root, nodeInfos) {
|
|
3691
3925
|
const [subscriberNodes, allBindings] = collectNodesAndBindingInfosByFragment(root, nodeInfos);
|