@wcstack/state 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +199 -4
- package/README.md +26 -0
- package/dist/index.esm.js +343 -105
- 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.12.0";
|
|
63
63
|
var pkg = {
|
|
64
64
|
version: version$1};
|
|
65
65
|
|
|
@@ -137,6 +137,8 @@ const WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
|
|
|
137
137
|
const STATE_BINDABLES_NAME = "$bindables";
|
|
138
138
|
const STATE_COMMAND_TOKENS_NAME = "$commandTokens";
|
|
139
139
|
const STATE_COMMAND_NAMESPACE_NAME = "$command";
|
|
140
|
+
const STATE_EVENT_TOKENS_NAME = "$eventTokens";
|
|
141
|
+
const STATE_ON_NAME = "$on";
|
|
140
142
|
const DCC_DEFINITION_ATTRIBUTE = "data-wc-definition";
|
|
141
143
|
|
|
142
144
|
const _cache$4 = new Map();
|
|
@@ -1485,6 +1487,15 @@ function parseBindTextsForElement(bindText) {
|
|
|
1485
1487
|
else {
|
|
1486
1488
|
const stateResult = parseStatePart(statePart);
|
|
1487
1489
|
const propResult = parsePropPart(propPart);
|
|
1490
|
+
// eventToken.<prop>: <name> は要素 dispatch を state へ流す pub/sub 配線。
|
|
1491
|
+
// 値適用ではないため bindingType 'event' として listener attach 経路に乗せる。
|
|
1492
|
+
if (propResult.propSegments[0] === 'eventToken') {
|
|
1493
|
+
return {
|
|
1494
|
+
...propResult,
|
|
1495
|
+
...stateResult,
|
|
1496
|
+
bindingType: 'event',
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1488
1499
|
if (propResult.propSegments[0].startsWith('on')) {
|
|
1489
1500
|
return {
|
|
1490
1501
|
...propResult,
|
|
@@ -1693,6 +1704,106 @@ function processDeferredNode(entry) {
|
|
|
1693
1704
|
return result.bindings;
|
|
1694
1705
|
}
|
|
1695
1706
|
|
|
1707
|
+
const _cache$3 = new WeakMap();
|
|
1708
|
+
const _cacheNullListIndex$1 = new WeakMap();
|
|
1709
|
+
class StateAddress {
|
|
1710
|
+
pathInfo;
|
|
1711
|
+
listIndex;
|
|
1712
|
+
_parentAddress;
|
|
1713
|
+
constructor(pathInfo, listIndex) {
|
|
1714
|
+
this.pathInfo = pathInfo;
|
|
1715
|
+
this.listIndex = listIndex;
|
|
1716
|
+
}
|
|
1717
|
+
get parentAddress() {
|
|
1718
|
+
if (typeof this._parentAddress !== 'undefined') {
|
|
1719
|
+
return this._parentAddress;
|
|
1720
|
+
}
|
|
1721
|
+
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
1722
|
+
if (parentPathInfo === null) {
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
1726
|
+
let parentListIndex = null;
|
|
1727
|
+
if (lastSegment === WILDCARD) {
|
|
1728
|
+
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
parentListIndex = this.listIndex;
|
|
1732
|
+
}
|
|
1733
|
+
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
function createStateAddress(pathInfo, listIndex) {
|
|
1737
|
+
if (listIndex === null) {
|
|
1738
|
+
let cached = _cacheNullListIndex$1.get(pathInfo);
|
|
1739
|
+
if (typeof cached !== "undefined") {
|
|
1740
|
+
return cached;
|
|
1741
|
+
}
|
|
1742
|
+
cached = new StateAddress(pathInfo, null);
|
|
1743
|
+
_cacheNullListIndex$1.set(pathInfo, cached);
|
|
1744
|
+
return cached;
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
let cacheByPathInfo = _cache$3.get(listIndex);
|
|
1748
|
+
if (typeof cacheByPathInfo === "undefined") {
|
|
1749
|
+
cacheByPathInfo = new WeakMap();
|
|
1750
|
+
_cache$3.set(listIndex, cacheByPathInfo);
|
|
1751
|
+
}
|
|
1752
|
+
let cached = cacheByPathInfo.get(pathInfo);
|
|
1753
|
+
if (typeof cached !== "undefined") {
|
|
1754
|
+
return cached;
|
|
1755
|
+
}
|
|
1756
|
+
cached = new StateAddress(pathInfo, listIndex);
|
|
1757
|
+
cacheByPathInfo.set(pathInfo, cached);
|
|
1758
|
+
return cached;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// command-token / event-token が共有する pub/sub プリミティブ。
|
|
1763
|
+
// _subscribers は Set のため挿入順を保持する。
|
|
1764
|
+
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
1765
|
+
//
|
|
1766
|
+
// 「誰が subscribe し誰が emit するか」だけが command / event の違い:
|
|
1767
|
+
// - command-token: element が subscribe / state が emit
|
|
1768
|
+
// - event-token: state(`$on`) が subscribe / element(listener) が emit
|
|
1769
|
+
class Token {
|
|
1770
|
+
_name;
|
|
1771
|
+
_subscribers = new Set();
|
|
1772
|
+
constructor(name) {
|
|
1773
|
+
this._name = name;
|
|
1774
|
+
}
|
|
1775
|
+
get name() {
|
|
1776
|
+
return this._name;
|
|
1777
|
+
}
|
|
1778
|
+
get size() {
|
|
1779
|
+
return this._subscribers.size;
|
|
1780
|
+
}
|
|
1781
|
+
subscribe(fn) {
|
|
1782
|
+
this._subscribers.add(fn);
|
|
1783
|
+
return () => {
|
|
1784
|
+
this._subscribers.delete(fn);
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
unsubscribe(fn) {
|
|
1788
|
+
return this._subscribers.delete(fn);
|
|
1789
|
+
}
|
|
1790
|
+
emit(...args) {
|
|
1791
|
+
const results = [];
|
|
1792
|
+
for (const fn of this._subscribers) {
|
|
1793
|
+
results.push(fn(...args));
|
|
1794
|
+
}
|
|
1795
|
+
return results;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// CommandToken は共有 pub/sub プリミティブ Token の薄い特化。
|
|
1800
|
+
// instanceof による型判別を成立させるため独立クラスとして維持する。
|
|
1801
|
+
class CommandToken extends Token {
|
|
1802
|
+
}
|
|
1803
|
+
function isCommandToken(value) {
|
|
1804
|
+
return value instanceof CommandToken;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1696
1807
|
const loopContextByNode = new WeakMap();
|
|
1697
1808
|
function getLoopContextByNode(node) {
|
|
1698
1809
|
let paramNode = node;
|
|
@@ -1721,13 +1832,18 @@ const connectedCallbackSymbol = Symbol("$$connectedCallback");
|
|
|
1721
1832
|
const disconnectedCallbackSymbol = Symbol("$$disconnectedCallback");
|
|
1722
1833
|
const updatedCallbackSymbol = Symbol("$$updatedCallback");
|
|
1723
1834
|
|
|
1835
|
+
// onclick: $command.<name> のように、DOM イベントから command token を直接 emit する形式かを判定する。
|
|
1836
|
+
// 右辺が $command 名前空間配下のパス($command.<token>)のときに true。
|
|
1837
|
+
function isCommandTokenPath(statePathName) {
|
|
1838
|
+
return statePathName.startsWith(STATE_COMMAND_NAMESPACE_NAME + ".");
|
|
1839
|
+
}
|
|
1724
1840
|
const handlerByHandlerKey$3 = new Map();
|
|
1725
1841
|
const bindingSetByHandlerKey$3 = new Map();
|
|
1726
1842
|
function getHandlerKey$3(binding) {
|
|
1727
1843
|
const modifierKey = binding.propModifiers.filter(m => m === 'prevent' || m === 'stop').sort().join(',');
|
|
1728
1844
|
return `${binding.stateName}::${binding.statePathName}::${modifierKey}`;
|
|
1729
1845
|
}
|
|
1730
|
-
const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event) => {
|
|
1846
|
+
const stateEventHandlerFunction = (stateName, handlerName, modifiers, statePathInfo) => (event) => {
|
|
1731
1847
|
if (modifiers.includes('prevent'))
|
|
1732
1848
|
event.preventDefault();
|
|
1733
1849
|
if (modifiers.includes('stop'))
|
|
@@ -1739,13 +1855,23 @@ const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event)
|
|
|
1739
1855
|
raiseError(`State element with name "${stateName}" not found for event handler.`);
|
|
1740
1856
|
}
|
|
1741
1857
|
const loopContext = getLoopContextByNode(node);
|
|
1858
|
+
const isCommand = isCommandTokenPath(handlerName);
|
|
1742
1859
|
stateElement.createStateAsync("writable", async (state) => {
|
|
1743
1860
|
state[setLoopContextSymbol](loopContext, () => {
|
|
1861
|
+
const indexes = loopContext?.listIndex.indexes ?? [];
|
|
1862
|
+
if (isCommand) {
|
|
1863
|
+
// command token を解決して emit。引数はハンドラ呼び出しと同じく (event, ...listIndexes) を透過する。
|
|
1864
|
+
const token = state[getByAddressSymbol](createStateAddress(statePathInfo, null));
|
|
1865
|
+
if (!isCommandToken(token)) {
|
|
1866
|
+
raiseError(`Event binding "${handlerName}" did not resolve to a CommandToken. Declare the name in $commandTokens and reference it as $command.<name>.`);
|
|
1867
|
+
}
|
|
1868
|
+
return token.emit(event, ...indexes);
|
|
1869
|
+
}
|
|
1744
1870
|
const handler = state[handlerName];
|
|
1745
1871
|
if (typeof handler !== "function") {
|
|
1746
1872
|
raiseError(`Handler "${handlerName}" is not a function on state "${stateName}".`);
|
|
1747
1873
|
}
|
|
1748
|
-
return Reflect.apply(handler, state, [event, ...
|
|
1874
|
+
return Reflect.apply(handler, state, [event, ...indexes]);
|
|
1749
1875
|
});
|
|
1750
1876
|
});
|
|
1751
1877
|
};
|
|
@@ -1756,7 +1882,7 @@ function attachEventHandler(binding) {
|
|
|
1756
1882
|
const key = getHandlerKey$3(binding);
|
|
1757
1883
|
let stateEventHandler = handlerByHandlerKey$3.get(key);
|
|
1758
1884
|
if (typeof stateEventHandler === "undefined") {
|
|
1759
|
-
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers);
|
|
1885
|
+
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers, binding.statePathInfo);
|
|
1760
1886
|
handlerByHandlerKey$3.set(key, stateEventHandler);
|
|
1761
1887
|
}
|
|
1762
1888
|
const eventName = binding.propName.slice(2);
|
|
@@ -1772,6 +1898,129 @@ function attachEventHandler(binding) {
|
|
|
1772
1898
|
return true;
|
|
1773
1899
|
}
|
|
1774
1900
|
|
|
1901
|
+
// EventToken は共有 pub/sub プリミティブ Token の薄い特化(element→state 方向)。
|
|
1902
|
+
// instanceof による型判別を成立させるため独立クラスとして維持する。
|
|
1903
|
+
class EventToken extends Token {
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
const registryByStateElement$1 = new WeakMap();
|
|
1907
|
+
function getOrCreateEventToken(stateElement, name) {
|
|
1908
|
+
let registry = registryByStateElement$1.get(stateElement);
|
|
1909
|
+
if (typeof registry === "undefined") {
|
|
1910
|
+
registry = new Map();
|
|
1911
|
+
registryByStateElement$1.set(stateElement, registry);
|
|
1912
|
+
}
|
|
1913
|
+
let token = registry.get(name);
|
|
1914
|
+
if (typeof token === "undefined") {
|
|
1915
|
+
token = new EventToken(name);
|
|
1916
|
+
registry.set(name, token);
|
|
1917
|
+
}
|
|
1918
|
+
return token;
|
|
1919
|
+
}
|
|
1920
|
+
function clearEventTokenRegistry(stateElement) {
|
|
1921
|
+
registryByStateElement$1.delete(stateElement);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* eventToken.<propertyName>: <eventTokenName> バインディングの attach ハンドラ。
|
|
1926
|
+
*
|
|
1927
|
+
* command-token の双対(element→state)。要素が dispatch する CustomEvent を受けて
|
|
1928
|
+
* event-token を emit し、state 側の `$on` ハンドラ群へ pub/sub で配送する。
|
|
1929
|
+
*
|
|
1930
|
+
* 設計(MVP スコープ: wc-bindable カスタム要素のみ):
|
|
1931
|
+
* - キーは生イベント名ではなく **wcBindable property 名**。実 DOM イベント名は
|
|
1932
|
+
* wcBindable.properties[].event から解決する(command-token が wcBindable.commands で
|
|
1933
|
+
* 検証するのと対称。コロンを含む namespaced event 名と binding 構文の `:` 衝突も回避)。
|
|
1934
|
+
* - <prop> が wcBindable.properties に宣言されていることは attach 時に検証する
|
|
1935
|
+
* (要素クラス参照のみで DOM 接続に非依存。fail-fast / typo 耐性)。
|
|
1936
|
+
* - <eventTokenName> が $eventTokens に宣言されていることは **発火時** に検証する
|
|
1937
|
+
* (state 解決が必要なため。詳細は下記の fire-time 解決の注記を参照)。
|
|
1938
|
+
* - subscriber 引数規約は `(state, event, ...listIndexes)`。
|
|
1939
|
+
* - modifier `#prevent` / `#stop` は既存イベント binding と同等にサポート。
|
|
1940
|
+
*
|
|
1941
|
+
* token はイベント発火ごとに registry から解決する(getOrCreateEventToken)。これにより
|
|
1942
|
+
* state の再 set で registry が作り直されても最新の subscriber 群へ配送できる。
|
|
1943
|
+
*
|
|
1944
|
+
* state element の解決と `$eventTokens` 検証は **発火時** に行う(attach 時ではない)。
|
|
1945
|
+
* 構造ブロック(for/if)や SSR hydration では、binding 初期化時にノードが detached な
|
|
1946
|
+
* DocumentFragment / wrapper 上にあり、その時点では element.getRootNode() から state を
|
|
1947
|
+
* 解決できないため。onclick / two-way ハンドラと同じく fire-time 解決に揃えている。
|
|
1948
|
+
*/
|
|
1949
|
+
const listenerByBinding = new WeakMap();
|
|
1950
|
+
function getWcBindable$1(element) {
|
|
1951
|
+
const customTagName = getCustomElement(element);
|
|
1952
|
+
if (customTagName === null) {
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
// attach 側で未定義要素は whenDefined 後に再試行するため、ここに来る時点で customClass は定義済み。
|
|
1956
|
+
const customClass = customElements.get(customTagName);
|
|
1957
|
+
const bindable = customClass?.wcBindable;
|
|
1958
|
+
if (bindable?.protocol === "wc-bindable" && bindable?.version === 1) {
|
|
1959
|
+
return bindable;
|
|
1960
|
+
}
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
function attachEventTokenHandler(binding) {
|
|
1964
|
+
if (binding.propSegments[0] !== "eventToken") {
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
const element = binding.node;
|
|
1968
|
+
// カスタム要素が未定義なら定義後に再試行(wcBindable が必要なため)。
|
|
1969
|
+
const customTagName = getCustomElement(element);
|
|
1970
|
+
if (customTagName !== null && customElements.get(customTagName) === undefined) {
|
|
1971
|
+
customElements.whenDefined(customTagName).then(() => {
|
|
1972
|
+
attachEventTokenHandler(binding);
|
|
1973
|
+
});
|
|
1974
|
+
return true;
|
|
1975
|
+
}
|
|
1976
|
+
// 再評価で二重 attach しない。
|
|
1977
|
+
if (listenerByBinding.has(binding)) {
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
const propertyName = binding.propSegments[1];
|
|
1981
|
+
if (typeof propertyName !== "string" || propertyName.length === 0) {
|
|
1982
|
+
raiseError(`eventToken binding requires a property name (e.g., "eventToken.error").`);
|
|
1983
|
+
}
|
|
1984
|
+
const bindable = getWcBindable$1(element);
|
|
1985
|
+
if (bindable === null) {
|
|
1986
|
+
raiseError(`eventToken binding requires a wc-bindable custom element. <${element.tagName.toLowerCase()}> is not wc-bindable.`);
|
|
1987
|
+
}
|
|
1988
|
+
const propDesc = bindable.properties.find((p) => p.name === propertyName);
|
|
1989
|
+
if (typeof propDesc === "undefined") {
|
|
1990
|
+
raiseError(`Property "${propertyName}" is not declared in wcBindable.properties of <${element.tagName.toLowerCase()}>.`);
|
|
1991
|
+
}
|
|
1992
|
+
const eventName = propDesc.event;
|
|
1993
|
+
const tokenName = binding.statePathName;
|
|
1994
|
+
const stateName = binding.stateName;
|
|
1995
|
+
const modifiers = binding.propModifiers;
|
|
1996
|
+
const handler = (event) => {
|
|
1997
|
+
if (modifiers.includes("prevent"))
|
|
1998
|
+
event.preventDefault();
|
|
1999
|
+
if (modifiers.includes("stop"))
|
|
2000
|
+
event.stopPropagation();
|
|
2001
|
+
// state は発火時の live root から解決する(attach 時は detached の可能性があるため)。
|
|
2002
|
+
const rootNode = element.getRootNode();
|
|
2003
|
+
const stateElement = getStateElementByName(rootNode, stateName);
|
|
2004
|
+
if (stateElement === null) {
|
|
2005
|
+
raiseError(`State element with name "${stateName}" not found for eventToken handler.`);
|
|
2006
|
+
}
|
|
2007
|
+
if (!stateElement.eventTokenNames.has(tokenName)) {
|
|
2008
|
+
raiseError(`eventToken "${tokenName}" is not declared in $eventTokens of state "${stateName}".`);
|
|
2009
|
+
}
|
|
2010
|
+
const loopContext = getLoopContextByNode(element);
|
|
2011
|
+
stateElement.createStateAsync("writable", async (state) => {
|
|
2012
|
+
state[setLoopContextSymbol](loopContext, () => {
|
|
2013
|
+
const indexes = loopContext?.listIndex.indexes ?? [];
|
|
2014
|
+
const token = getOrCreateEventToken(stateElement, tokenName);
|
|
2015
|
+
return token.emit(state, event, ...indexes);
|
|
2016
|
+
});
|
|
2017
|
+
});
|
|
2018
|
+
};
|
|
2019
|
+
element.addEventListener(eventName, handler);
|
|
2020
|
+
listenerByBinding.set(binding, { eventName, handler });
|
|
2021
|
+
return true;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1775
2024
|
const CHECK_TYPES = new Set(['radio', 'checkbox']);
|
|
1776
2025
|
const DEFAULT_VALUE_PROP_NAMES = new Set(['value', 'valueAsNumber', 'valueAsDate']);
|
|
1777
2026
|
function isPossibleTwoWay(node, propName) {
|
|
@@ -1937,19 +2186,19 @@ function setLastListValueByAbsoluteStateAddress(address, value) {
|
|
|
1937
2186
|
lastListValueByAbsoluteStateAddress.set(address, value);
|
|
1938
2187
|
}
|
|
1939
2188
|
|
|
1940
|
-
const _cache$
|
|
2189
|
+
const _cache$2 = new WeakMap();
|
|
1941
2190
|
function getAbsolutePathInfo(stateElement, pathInfo) {
|
|
1942
|
-
if (_cache$
|
|
1943
|
-
const pathMap = _cache$
|
|
2191
|
+
if (_cache$2.has(stateElement)) {
|
|
2192
|
+
const pathMap = _cache$2.get(stateElement);
|
|
1944
2193
|
if (pathMap.has(pathInfo)) {
|
|
1945
2194
|
return pathMap.get(pathInfo);
|
|
1946
2195
|
}
|
|
1947
2196
|
}
|
|
1948
2197
|
else {
|
|
1949
|
-
_cache$
|
|
2198
|
+
_cache$2.set(stateElement, new WeakMap());
|
|
1950
2199
|
}
|
|
1951
2200
|
const absolutePathInfo = Object.freeze(new AbsolutePathInfo(stateElement, pathInfo));
|
|
1952
|
-
_cache$
|
|
2201
|
+
_cache$2.get(stateElement).set(pathInfo, absolutePathInfo);
|
|
1953
2202
|
return absolutePathInfo;
|
|
1954
2203
|
}
|
|
1955
2204
|
class AbsolutePathInfo {
|
|
@@ -1970,8 +2219,8 @@ class AbsolutePathInfo {
|
|
|
1970
2219
|
}
|
|
1971
2220
|
}
|
|
1972
2221
|
|
|
1973
|
-
const _cache$
|
|
1974
|
-
const _cacheNullListIndex
|
|
2222
|
+
const _cache$1 = new WeakMap();
|
|
2223
|
+
const _cacheNullListIndex = new WeakMap();
|
|
1975
2224
|
class AbsoluteStateAddress {
|
|
1976
2225
|
absolutePathInfo;
|
|
1977
2226
|
listIndex;
|
|
@@ -2001,19 +2250,19 @@ class AbsoluteStateAddress {
|
|
|
2001
2250
|
}
|
|
2002
2251
|
function createAbsoluteStateAddress(absolutePathInfo, listIndex) {
|
|
2003
2252
|
if (listIndex === null) {
|
|
2004
|
-
let cached = _cacheNullListIndex
|
|
2253
|
+
let cached = _cacheNullListIndex.get(absolutePathInfo);
|
|
2005
2254
|
if (typeof cached !== "undefined") {
|
|
2006
2255
|
return cached;
|
|
2007
2256
|
}
|
|
2008
2257
|
cached = new AbsoluteStateAddress(absolutePathInfo, null);
|
|
2009
|
-
_cacheNullListIndex
|
|
2258
|
+
_cacheNullListIndex.set(absolutePathInfo, cached);
|
|
2010
2259
|
return cached;
|
|
2011
2260
|
}
|
|
2012
2261
|
else {
|
|
2013
|
-
let cacheByAbsolutePathInfo = _cache$
|
|
2262
|
+
let cacheByAbsolutePathInfo = _cache$1.get(listIndex);
|
|
2014
2263
|
if (typeof cacheByAbsolutePathInfo === "undefined") {
|
|
2015
2264
|
cacheByAbsolutePathInfo = new WeakMap();
|
|
2016
|
-
_cache$
|
|
2265
|
+
_cache$1.set(listIndex, cacheByAbsolutePathInfo);
|
|
2017
2266
|
}
|
|
2018
2267
|
let cached = cacheByAbsolutePathInfo.get(absolutePathInfo);
|
|
2019
2268
|
if (typeof cached !== "undefined") {
|
|
@@ -2189,41 +2438,6 @@ function applyChangeToClass(binding, _context, newValue) {
|
|
|
2189
2438
|
element.classList.toggle(className, newValue);
|
|
2190
2439
|
}
|
|
2191
2440
|
|
|
2192
|
-
// _subscribers は Set のため挿入順を保持する。
|
|
2193
|
-
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
2194
|
-
class CommandToken {
|
|
2195
|
-
_name;
|
|
2196
|
-
_subscribers = new Set();
|
|
2197
|
-
constructor(name) {
|
|
2198
|
-
this._name = name;
|
|
2199
|
-
}
|
|
2200
|
-
get name() {
|
|
2201
|
-
return this._name;
|
|
2202
|
-
}
|
|
2203
|
-
get size() {
|
|
2204
|
-
return this._subscribers.size;
|
|
2205
|
-
}
|
|
2206
|
-
subscribe(fn) {
|
|
2207
|
-
this._subscribers.add(fn);
|
|
2208
|
-
return () => {
|
|
2209
|
-
this._subscribers.delete(fn);
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
unsubscribe(fn) {
|
|
2213
|
-
return this._subscribers.delete(fn);
|
|
2214
|
-
}
|
|
2215
|
-
emit(...args) {
|
|
2216
|
-
const results = [];
|
|
2217
|
-
for (const fn of this._subscribers) {
|
|
2218
|
-
results.push(fn(...args));
|
|
2219
|
-
}
|
|
2220
|
-
return results;
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
function isCommandToken(value) {
|
|
2224
|
-
return value instanceof CommandToken;
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
2441
|
/**
|
|
2228
2442
|
* command.<methodName>: <commandToken-path> バインディングの適用ハンドラ。
|
|
2229
2443
|
*
|
|
@@ -2306,61 +2520,6 @@ function applyChangeToCommand(binding, _context, newValue) {
|
|
|
2306
2520
|
subscribedBindings.set(binding, { token, unsubscribe, elementRef });
|
|
2307
2521
|
}
|
|
2308
2522
|
|
|
2309
|
-
const _cache$1 = new WeakMap();
|
|
2310
|
-
const _cacheNullListIndex = new WeakMap();
|
|
2311
|
-
class StateAddress {
|
|
2312
|
-
pathInfo;
|
|
2313
|
-
listIndex;
|
|
2314
|
-
_parentAddress;
|
|
2315
|
-
constructor(pathInfo, listIndex) {
|
|
2316
|
-
this.pathInfo = pathInfo;
|
|
2317
|
-
this.listIndex = listIndex;
|
|
2318
|
-
}
|
|
2319
|
-
get parentAddress() {
|
|
2320
|
-
if (typeof this._parentAddress !== 'undefined') {
|
|
2321
|
-
return this._parentAddress;
|
|
2322
|
-
}
|
|
2323
|
-
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
2324
|
-
if (parentPathInfo === null) {
|
|
2325
|
-
return null;
|
|
2326
|
-
}
|
|
2327
|
-
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
2328
|
-
let parentListIndex = null;
|
|
2329
|
-
if (lastSegment === WILDCARD) {
|
|
2330
|
-
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
2331
|
-
}
|
|
2332
|
-
else {
|
|
2333
|
-
parentListIndex = this.listIndex;
|
|
2334
|
-
}
|
|
2335
|
-
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
function createStateAddress(pathInfo, listIndex) {
|
|
2339
|
-
if (listIndex === null) {
|
|
2340
|
-
let cached = _cacheNullListIndex.get(pathInfo);
|
|
2341
|
-
if (typeof cached !== "undefined") {
|
|
2342
|
-
return cached;
|
|
2343
|
-
}
|
|
2344
|
-
cached = new StateAddress(pathInfo, null);
|
|
2345
|
-
_cacheNullListIndex.set(pathInfo, cached);
|
|
2346
|
-
return cached;
|
|
2347
|
-
}
|
|
2348
|
-
else {
|
|
2349
|
-
let cacheByPathInfo = _cache$1.get(listIndex);
|
|
2350
|
-
if (typeof cacheByPathInfo === "undefined") {
|
|
2351
|
-
cacheByPathInfo = new WeakMap();
|
|
2352
|
-
_cache$1.set(listIndex, cacheByPathInfo);
|
|
2353
|
-
}
|
|
2354
|
-
let cached = cacheByPathInfo.get(pathInfo);
|
|
2355
|
-
if (typeof cached !== "undefined") {
|
|
2356
|
-
return cached;
|
|
2357
|
-
}
|
|
2358
|
-
cached = new StateAddress(pathInfo, listIndex);
|
|
2359
|
-
cacheByPathInfo.set(pathInfo, cached);
|
|
2360
|
-
return cached;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
2523
|
const indexBindingsByContent = new WeakMap();
|
|
2365
2524
|
function getIndexBindingsByContent(content) {
|
|
2366
2525
|
return indexBindingsByContent.get(content) ?? [];
|
|
@@ -3856,6 +4015,10 @@ function _initializeBindings(allBindings) {
|
|
|
3856
4015
|
if (attachEventHandler(binding)) {
|
|
3857
4016
|
continue;
|
|
3858
4017
|
}
|
|
4018
|
+
// event token (element → state)
|
|
4019
|
+
if (attachEventTokenHandler(binding)) {
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
3859
4022
|
// two-way binding
|
|
3860
4023
|
attachTwowayEventHandler(binding);
|
|
3861
4024
|
// radio binding
|
|
@@ -4383,6 +4546,8 @@ function collectBindingsFromLiveNodes(nodes) {
|
|
|
4383
4546
|
replaceToReplaceNode(binding);
|
|
4384
4547
|
if (attachEventHandler(binding))
|
|
4385
4548
|
continue;
|
|
4549
|
+
if (attachEventTokenHandler(binding))
|
|
4550
|
+
continue;
|
|
4386
4551
|
attachTwowayEventHandler(binding);
|
|
4387
4552
|
attachRadioEventHandler(binding);
|
|
4388
4553
|
attachCheckboxEventHandler(binding);
|
|
@@ -4622,6 +4787,9 @@ async function hydrateBindings(root) {
|
|
|
4622
4787
|
if (attachEventHandler(binding)) {
|
|
4623
4788
|
continue;
|
|
4624
4789
|
}
|
|
4790
|
+
if (attachEventTokenHandler(binding)) {
|
|
4791
|
+
continue;
|
|
4792
|
+
}
|
|
4625
4793
|
attachTwowayEventHandler(binding);
|
|
4626
4794
|
attachRadioEventHandler(binding);
|
|
4627
4795
|
attachCheckboxEventHandler(binding);
|
|
@@ -5353,6 +5521,67 @@ function clearCommandNamespace(stateElement) {
|
|
|
5353
5521
|
namespaceProxyByStateElement.delete(stateElement);
|
|
5354
5522
|
}
|
|
5355
5523
|
|
|
5524
|
+
/**
|
|
5525
|
+
* `$eventTokens: ["a", "b", ...]` 配列宣言を解析し、宣言された名前群を Set で返す。
|
|
5526
|
+
*
|
|
5527
|
+
* event-token は command-token の双対(element→state 方向)。要素が dispatch する
|
|
5528
|
+
* イベントを `eventToken.<prop>: <name>` で token に流し、state 側は `$on` マップで受ける。
|
|
5529
|
+
* ここで宣言された名前のみが `eventToken.X` / `$on` の有効なチャネル名になる(typo 耐性)。
|
|
5530
|
+
*
|
|
5531
|
+
* 対応している宣言形式は **オブジェクトリテラル** のみ。
|
|
5532
|
+
*/
|
|
5533
|
+
function processEventTokensDeclaration(state) {
|
|
5534
|
+
const names = new Set();
|
|
5535
|
+
const declared = state[STATE_EVENT_TOKENS_NAME];
|
|
5536
|
+
if (typeof declared === "undefined") {
|
|
5537
|
+
return names;
|
|
5538
|
+
}
|
|
5539
|
+
if (!Array.isArray(declared)) {
|
|
5540
|
+
raiseError(`${STATE_EVENT_TOKENS_NAME} must be an array of strings.`);
|
|
5541
|
+
}
|
|
5542
|
+
for (const name of declared) {
|
|
5543
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
5544
|
+
raiseError(`${STATE_EVENT_TOKENS_NAME} entries must be non-empty strings.`);
|
|
5545
|
+
}
|
|
5546
|
+
if (names.has(name)) {
|
|
5547
|
+
raiseError(`${STATE_EVENT_TOKENS_NAME} entry "${name}" is duplicated.`);
|
|
5548
|
+
}
|
|
5549
|
+
names.add(name);
|
|
5550
|
+
}
|
|
5551
|
+
return names;
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
/**
|
|
5555
|
+
* `$on: { <name>: (state, event, ...listIndexes) => {...} }` マップを解析し、
|
|
5556
|
+
* 各ハンドラを対応する event-token に subscribe する(state 側の受信配線)。
|
|
5557
|
+
*
|
|
5558
|
+
* - `$on` のキーは `$eventTokens` で宣言済みでなければならない(typo 耐性)。
|
|
5559
|
+
* - 各値は関数でなければならない。
|
|
5560
|
+
* - 引数規約は `(state, event, ...listIndexes)`。`this` 束縛は行わず引数で state を渡すため
|
|
5561
|
+
* アロー関数で書ける(command-token の emit 規約と対称)。
|
|
5562
|
+
*
|
|
5563
|
+
* `$eventTokens` で宣言されたが `$on` に対応が無い token は subscriber ゼロ(emit は no-op)。
|
|
5564
|
+
*/
|
|
5565
|
+
function processOnDeclaration(stateElement, state, eventTokenNames) {
|
|
5566
|
+
const declared = state[STATE_ON_NAME];
|
|
5567
|
+
if (typeof declared === "undefined") {
|
|
5568
|
+
return;
|
|
5569
|
+
}
|
|
5570
|
+
if (typeof declared !== "object" || declared === null) {
|
|
5571
|
+
raiseError(`${STATE_ON_NAME} must be an object mapping event-token names to handler functions.`);
|
|
5572
|
+
}
|
|
5573
|
+
for (const [name, handler] of Object.entries(declared)) {
|
|
5574
|
+
if (!eventTokenNames.has(name)) {
|
|
5575
|
+
raiseError(`${STATE_ON_NAME} entry "${name}" is not declared in $eventTokens.`);
|
|
5576
|
+
}
|
|
5577
|
+
if (typeof handler !== "function") {
|
|
5578
|
+
raiseError(`${STATE_ON_NAME} entry "${name}" must be a function.`);
|
|
5579
|
+
}
|
|
5580
|
+
const token = getOrCreateEventToken(stateElement, name);
|
|
5581
|
+
token.subscribe(handler);
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5356
5585
|
function getterFn(name) {
|
|
5357
5586
|
return function () {
|
|
5358
5587
|
const stateEl = this.stateElement;
|
|
@@ -7184,6 +7413,7 @@ class State extends HTMLElement {
|
|
|
7184
7413
|
_boundComponentStateProp = null;
|
|
7185
7414
|
_bindableEventMap = {};
|
|
7186
7415
|
_commandTokenNames = new Set();
|
|
7416
|
+
_eventTokenNames = new Set();
|
|
7187
7417
|
constructor() {
|
|
7188
7418
|
super();
|
|
7189
7419
|
this._initializePromise = new Promise((resolve) => {
|
|
@@ -7207,7 +7437,11 @@ class State extends HTMLElement {
|
|
|
7207
7437
|
}
|
|
7208
7438
|
set _state(value) {
|
|
7209
7439
|
this._commandTokenNames = processCommandTokensDeclaration(value);
|
|
7440
|
+
this._eventTokenNames = processEventTokensDeclaration(value);
|
|
7210
7441
|
this.__state = value;
|
|
7442
|
+
// 再 set 時に二重 subscribe しないよう registry をクリアしてから $on を配線し直す。
|
|
7443
|
+
clearEventTokenRegistry(this);
|
|
7444
|
+
processOnDeclaration(this, value, this._eventTokenNames);
|
|
7211
7445
|
this._listPaths.clear();
|
|
7212
7446
|
this._elementPaths.clear();
|
|
7213
7447
|
this._getterPaths.clear();
|
|
@@ -7421,6 +7655,7 @@ class State extends HTMLElement {
|
|
|
7421
7655
|
setStateElementByName(this.rootNode, this._name, null);
|
|
7422
7656
|
clearCommandTokenRegistry(this);
|
|
7423
7657
|
clearCommandNamespace(this);
|
|
7658
|
+
clearEventTokenRegistry(this);
|
|
7424
7659
|
this._rootNode = null;
|
|
7425
7660
|
}
|
|
7426
7661
|
}
|
|
@@ -7469,6 +7704,9 @@ class State extends HTMLElement {
|
|
|
7469
7704
|
get commandTokenNames() {
|
|
7470
7705
|
return this._commandTokenNames;
|
|
7471
7706
|
}
|
|
7707
|
+
get eventTokenNames() {
|
|
7708
|
+
return this._eventTokenNames;
|
|
7709
|
+
}
|
|
7472
7710
|
setBindableEventMap(map) {
|
|
7473
7711
|
this._bindableEventMap = map;
|
|
7474
7712
|
}
|