@wcstack/state 1.32.0 → 1.33.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 +164 -0
- package/README.md +164 -0
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +172 -0
- package/dist/index.esm.js +508 -39
- package/dist/index.esm.js.map +1 -1
- package/dist/parser.esm.js +1 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1928,6 +1928,32 @@ function parsePropPart(propPart) {
|
|
|
1928
1928
|
};
|
|
1929
1929
|
}
|
|
1930
1930
|
|
|
1931
|
+
/**
|
|
1932
|
+
* 名前付き State(`<wcs-state name>` / `path@name`)の deprecation 通知。
|
|
1933
|
+
*
|
|
1934
|
+
* v2 では名前の次元そのものが消え、`<wcs-state mount="path">` と接頭辞付きパスに
|
|
1935
|
+
* 置き換わる(docs/state-mount-design.md D1 / D16)。1.x には `mount=` が無く、
|
|
1936
|
+
* warn を出しても利用者は動けないので、**既定では出さない**(`config.debug` 下だけ)。
|
|
1937
|
+
* 主経路は lint(`wcs/named-state-deprecated`)と README の告知。
|
|
1938
|
+
*
|
|
1939
|
+
* 出すときは種別 × 対象ごとに 1 回(起動のたびに同じ行が並ばないように)。
|
|
1940
|
+
*/
|
|
1941
|
+
const reported$1 = new Set();
|
|
1942
|
+
function warnNamedStateDeprecated(kind, subject) {
|
|
1943
|
+
if (!config.debug) {
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
const key = `${kind}:${subject}`;
|
|
1947
|
+
if (reported$1.has(key)) {
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
reported$1.add(key);
|
|
1951
|
+
const hint = kind === 'attribute'
|
|
1952
|
+
? `<wcs-state name="${subject}"> will be removed in v2. Mount the state onto the root tree with <wcs-state mount="${subject}"> and read it as "${subject}.<path>".`
|
|
1953
|
+
: `"${subject}" uses the "@name" state selector, which will be removed in v2. Read the mounted tree as "<name>.<path>" instead.`;
|
|
1954
|
+
console.warn(`[@wcstack/state] [wcs/named-state-deprecated] ${hint} See docs/state-mount-design.md §9.`);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1931
1957
|
const cacheFilterInfos = new Map();
|
|
1932
1958
|
// format: statePath@stateName|filter|filter
|
|
1933
1959
|
// statePath-format: path.to.property (e.g., user.name.first, users.*.name, users.0.name, not include @)
|
|
@@ -1954,6 +1980,10 @@ function parseStatePart(statePart) {
|
|
|
1954
1980
|
else {
|
|
1955
1981
|
stateAndPath = statePart.trim();
|
|
1956
1982
|
}
|
|
1983
|
+
if (stateAndPath.indexOf(STATE_NAME_SEPARATOR) !== -1) {
|
|
1984
|
+
// `path@name` は v2 で消える(docs/state-mount-design.md D16。既定では黙る)
|
|
1985
|
+
warnNamedStateDeprecated('path', stateAndPath);
|
|
1986
|
+
}
|
|
1957
1987
|
const [statePathName, stateName = 'default'] = stateAndPath.split(STATE_NAME_SEPARATOR).map(trimFn);
|
|
1958
1988
|
const pathInfo = getPathInfo(statePathName);
|
|
1959
1989
|
return {
|
|
@@ -4019,11 +4049,12 @@ const outerMappingByElement = new WeakMap();
|
|
|
4019
4049
|
const primaryMappingRuleSetByElement = new WeakMap();
|
|
4020
4050
|
const primaryBindingByMappingRule = new WeakMap();
|
|
4021
4051
|
function createMappingRuleByBinding(innerState, binding) {
|
|
4022
|
-
const
|
|
4052
|
+
const innerPath = binding.propSegments.slice(1).join(DELIMITER);
|
|
4053
|
+
const innerPathInfo = getPathInfo(innerPath);
|
|
4023
4054
|
const innerAbsPathInfo = getAbsolutePathInfo(innerState, innerPathInfo);
|
|
4024
4055
|
const outerAbsStateAddress = getAbsoluteStateAddressByBinding(binding);
|
|
4025
4056
|
const outerAbsPathInfo = outerAbsStateAddress.absolutePathInfo;
|
|
4026
|
-
return { innerAbsPathInfo, outerAbsPathInfo };
|
|
4057
|
+
return { innerAbsPathInfo, outerAbsPathInfo, isRoot: innerPath.length === 0 };
|
|
4027
4058
|
}
|
|
4028
4059
|
function buildPrimaryMappingRule(webComponent, stateName, bindings) {
|
|
4029
4060
|
if (bindings.length === 0) {
|
|
@@ -4035,8 +4066,16 @@ function buildPrimaryMappingRule(webComponent, stateName, bindings) {
|
|
|
4035
4066
|
}
|
|
4036
4067
|
const innerMappingRule = new Map();
|
|
4037
4068
|
const outerMappingRule = new Map();
|
|
4069
|
+
// 同じ内側パスを 2 つの規則が指す形(`state.x: b; state.x: c`、`state: a; state: b`)は
|
|
4070
|
+
// どちらが勝つか書き手に見えないので、導出時ではなく構築時に落とす(設計書 §4-7 / M6)。
|
|
4071
|
+
const seenInnerPaths = new Set();
|
|
4038
4072
|
for (const binding of bindings) {
|
|
4039
4073
|
const mappingRule = createMappingRuleByBinding(innerState, binding);
|
|
4074
|
+
const innerPath = mappingRule.innerAbsPathInfo.pathInfo.path;
|
|
4075
|
+
if (seenInnerPaths.has(innerPath)) {
|
|
4076
|
+
raiseError('Duplicate mapping rule for web component.');
|
|
4077
|
+
}
|
|
4078
|
+
seenInnerPaths.add(innerPath);
|
|
4040
4079
|
let primaryMappingRuleSet = primaryMappingRuleSetByElement.get(webComponent);
|
|
4041
4080
|
if (typeof primaryMappingRuleSet === 'undefined') {
|
|
4042
4081
|
primaryMappingRuleSetByElement.set(webComponent, new Set([mappingRule]));
|
|
@@ -4084,11 +4123,14 @@ function resetDerivedMappingRules(webComponent) {
|
|
|
4084
4123
|
outerMappingByElement.set(webComponent, outerMappingRule);
|
|
4085
4124
|
}
|
|
4086
4125
|
/**
|
|
4087
|
-
*
|
|
4126
|
+
* このコンポーネントに張られたプライマリ規則の**内側パス**を列挙する(ルート規則を除く)。
|
|
4088
4127
|
*
|
|
4089
4128
|
* 切断 → 再接続を跨いだ子(行 content の再利用で起きる)は、切断中に親で起きた変更の
|
|
4090
4129
|
* 通知を受け取れていない。再接続時に「束ねているパスを読み直せ」と撃つための入力で、
|
|
4091
4130
|
* 何が変わったかは分からないのでプライマリ規則の粒度で丸ごと読み直す(§1.9)。
|
|
4131
|
+
*
|
|
4132
|
+
* ルート規則は内側パスが空で `$postUpdate("")` に意味が無いため含めない。ルート規則の
|
|
4133
|
+
* 読み直しは子の登録済みパス全部(`getRootReloadPaths`)で行う。
|
|
4092
4134
|
*/
|
|
4093
4135
|
function getPrimaryInnerPaths(webComponent) {
|
|
4094
4136
|
const primaryMappingRuleSet = primaryMappingRuleSetByElement.get(webComponent);
|
|
@@ -4097,13 +4139,37 @@ function getPrimaryInnerPaths(webComponent) {
|
|
|
4097
4139
|
}
|
|
4098
4140
|
const paths = [];
|
|
4099
4141
|
for (const rule of primaryMappingRuleSet) {
|
|
4142
|
+
if (rule.isRoot) {
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4100
4145
|
paths.push(rule.innerAbsPathInfo.pathInfo.path);
|
|
4101
4146
|
}
|
|
4102
4147
|
return paths;
|
|
4103
4148
|
}
|
|
4149
|
+
/** このコンポーネントのプライマリ規則の集合。無ければ null(plain)。 */
|
|
4150
|
+
function getPrimaryMappingRules(webComponent) {
|
|
4151
|
+
return primaryMappingRuleSetByElement.get(webComponent) ?? null;
|
|
4152
|
+
}
|
|
4153
|
+
/** ルート規則(`state: path` の丸ごとマウント)があるか。 */
|
|
4154
|
+
function hasRootMappingRule(webComponent) {
|
|
4155
|
+
const primaryMappingRuleSet = primaryMappingRuleSetByElement.get(webComponent);
|
|
4156
|
+
if (typeof primaryMappingRuleSet === 'undefined') {
|
|
4157
|
+
return false;
|
|
4158
|
+
}
|
|
4159
|
+
for (const rule of primaryMappingRuleSet) {
|
|
4160
|
+
if (rule.isRoot) {
|
|
4161
|
+
return true;
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
return false;
|
|
4165
|
+
}
|
|
4104
4166
|
/**
|
|
4105
4167
|
* 内側のパスを外側のパスへ翻訳する。規則が無ければプライマリ規則から導出する。
|
|
4106
4168
|
*
|
|
4169
|
+
* 導出は**最長接頭辞一致**。ルート規則(接頭辞長 0)はあらゆる内側パスに一致するが、
|
|
4170
|
+
* 部分規則(`state.theme: theme`)がより長い接頭辞で一致すればそちらが勝つ
|
|
4171
|
+
* (`state: rows; state.theme: theme` の併用 — 設計書 §3-2)。
|
|
4172
|
+
*
|
|
4107
4173
|
* `registerSubscriber` は導出に**副作用を持たせるか**の切り替え。既定(子の read /
|
|
4108
4174
|
* write からの呼び出し)では導出した規則を台帳に memo し、対応するバインディングを
|
|
4109
4175
|
* 親スコープの購読者として登録する。`false` を渡すと**参照専用**になり、台帳にも
|
|
@@ -4138,16 +4204,28 @@ function getOuterAbsolutePathInfo(webComponent, innerAbsPathInfo, registerSubscr
|
|
|
4138
4204
|
return null;
|
|
4139
4205
|
}
|
|
4140
4206
|
let primaryMappingRule = null;
|
|
4207
|
+
let matchedPrefixLength = -1;
|
|
4141
4208
|
for (const currentPrimaryMappingRule of primaryMappingRuleSet) {
|
|
4209
|
+
if (currentPrimaryMappingRule.isRoot) {
|
|
4210
|
+
// ルート規則は接頭辞長 0 で常に一致。部分規則が無ければこれが採られる
|
|
4211
|
+
if (matchedPrefixLength < 0) {
|
|
4212
|
+
primaryMappingRule = currentPrimaryMappingRule;
|
|
4213
|
+
matchedPrefixLength = 0;
|
|
4214
|
+
}
|
|
4215
|
+
continue;
|
|
4216
|
+
}
|
|
4142
4217
|
// innerPathInfoがprimaryMappingRuleのinnerPathInfoを包含しているか
|
|
4143
4218
|
if (!innerAbsPathInfo.pathInfo.cumulativePathInfoSet.has(currentPrimaryMappingRule.innerAbsPathInfo.pathInfo)) {
|
|
4144
4219
|
continue;
|
|
4145
4220
|
}
|
|
4146
|
-
|
|
4221
|
+
const prefixLength = currentPrimaryMappingRule.innerAbsPathInfo.pathInfo.segments.length;
|
|
4222
|
+
if (prefixLength === innerAbsPathInfo.pathInfo.segments.length) {
|
|
4147
4223
|
raiseError('Duplicate mapping rule for web component.');
|
|
4148
4224
|
}
|
|
4149
|
-
|
|
4150
|
-
|
|
4225
|
+
if (prefixLength > matchedPrefixLength) {
|
|
4226
|
+
primaryMappingRule = currentPrimaryMappingRule;
|
|
4227
|
+
matchedPrefixLength = prefixLength;
|
|
4228
|
+
}
|
|
4151
4229
|
}
|
|
4152
4230
|
if (primaryMappingRule === null) {
|
|
4153
4231
|
// マッピングルールに一致しない場合はnullを返し、ローカル状態へのフォールバックを許可する
|
|
@@ -4160,7 +4238,7 @@ function getOuterAbsolutePathInfo(webComponent, innerAbsPathInfo, registerSubscr
|
|
|
4160
4238
|
raiseError('Binding not found for primary mapping rule on web component.');
|
|
4161
4239
|
}
|
|
4162
4240
|
/* c8 ignore stop */
|
|
4163
|
-
const outerRemainingSegments = innerAbsPathInfo.pathInfo.segments.slice(
|
|
4241
|
+
const outerRemainingSegments = innerAbsPathInfo.pathInfo.segments.slice(matchedPrefixLength);
|
|
4164
4242
|
const outerSegments = primaryMappingRule.outerAbsPathInfo.pathInfo.segments.concat(outerRemainingSegments);
|
|
4165
4243
|
const outerPathInfo = getPathInfo(outerSegments.join(DELIMITER));
|
|
4166
4244
|
const rootNode = webComponent.getRootNode();
|
|
@@ -5670,6 +5748,33 @@ function markWebComponentAsComplete(webComponent, stateProp) {
|
|
|
5670
5748
|
function isWebComponentComplete(webComponent, stateProp) {
|
|
5671
5749
|
return completedStatePropsByWebComponent.get(webComponent)?.has(stateProp) === true;
|
|
5672
5750
|
}
|
|
5751
|
+
/**
|
|
5752
|
+
* `<wcs-state bind-component="<prop>">` が接続され、(webComponent, stateProp) を束ねると
|
|
5753
|
+
* **宣言した**台帳。完了(上)より前 — ホストの `whenDefined` / `waitInitializeBinding` を
|
|
5754
|
+
* 待つ前 — に記録する。
|
|
5755
|
+
*
|
|
5756
|
+
* 用途は丸ごとマウント `data-wcs="state: user"` の**完了前の初期適用の抑止**。完了前の
|
|
5757
|
+
* 1 セグメントバインディングは applyChangeToProperty が `element.state = userObject` と
|
|
5758
|
+
* 親のオブジェクトそのものをコンポーネントの state プロパティに書いてしまい、
|
|
5759
|
+
* bindWebComponent がそれを子 state の実体として取り込む — own data key が親のキー全部に
|
|
5760
|
+
* なり、R1 では全部が私有に化ける。宣言済みなら値を書かずに完了を待つ(子は完了後に
|
|
5761
|
+
* innerState 経由でライブに読む — docs/state-mount-design.md §3-2 / impl-plan P1-1)。
|
|
5762
|
+
*
|
|
5763
|
+
* 未宣言(子がまだ接続していない)の間は従来どおり書く。未 upgrade 要素への own property は
|
|
5764
|
+
* upgrade 時のクラスフィールド初期化で置き換わるので、実害は無い。
|
|
5765
|
+
*/
|
|
5766
|
+
const declaredStatePropsByWebComponent = new WeakMap();
|
|
5767
|
+
function markWebComponentStatePropDeclared(webComponent, stateProp) {
|
|
5768
|
+
let declaredStateProps = declaredStatePropsByWebComponent.get(webComponent);
|
|
5769
|
+
if (!declaredStateProps) {
|
|
5770
|
+
declaredStateProps = new Set();
|
|
5771
|
+
declaredStatePropsByWebComponent.set(webComponent, declaredStateProps);
|
|
5772
|
+
}
|
|
5773
|
+
declaredStateProps.add(stateProp);
|
|
5774
|
+
}
|
|
5775
|
+
function isWebComponentStatePropDeclared(webComponent, stateProp) {
|
|
5776
|
+
return declaredStatePropsByWebComponent.get(webComponent)?.has(stateProp) === true;
|
|
5777
|
+
}
|
|
5673
5778
|
|
|
5674
5779
|
function applyChangeToAttribute(binding, _context, newValue) {
|
|
5675
5780
|
const element = binding.node;
|
|
@@ -7292,6 +7397,68 @@ function applyMirrorAttribute(element, attributeName, value) {
|
|
|
7292
7397
|
element.setAttribute(attributeName, formatted);
|
|
7293
7398
|
}
|
|
7294
7399
|
|
|
7400
|
+
/**
|
|
7401
|
+
* `bind-component` の**完了前**に親スコープの初期適用がコンポーネントの state プロパティへ
|
|
7402
|
+
* 行った書き込みの控え(docs/state-mount-design.md D19 / impl-plan P1-1・P1-10)。
|
|
7403
|
+
*
|
|
7404
|
+
* 完了前の親→子の適用は `applyChangeToProperty` が素のプロパティに値を積む
|
|
7405
|
+
* (webComponent/completeWebComponent.ts)。丸ごとマウントと R1 が入って、この積みが
|
|
7406
|
+
* 2 つの取り違えを生むようになった。
|
|
7407
|
+
*
|
|
7408
|
+
* 1. **丸ごと(1 セグメント・`state: user`)**: `element.state = userObject` と親の
|
|
7409
|
+
* オブジェクトそのもので state プロパティを**置き換える**。子がまだ宣言していない
|
|
7410
|
+
* タイミング(happy-dom の template clone は upgrade 済みで、挿入前に適用が走る)では
|
|
7411
|
+
* 宣言台帳のガードが効かず、作者の state オブジェクト(getter / 私有キー)が失われる。
|
|
7412
|
+
* → 置き換え前のオブジェクトを控え、子の初期化時に戻す(`takeOverwrittenObject`)。
|
|
7413
|
+
* 2. **部分(2 セグメント・`state.theme: theme`)**: `element.state.theme = themeObject` と
|
|
7414
|
+
* 作者のオブジェクトに**キーを注入する**。R1 の判定はこれを own data key と区別できない。
|
|
7415
|
+
* → 注入したキーを控え、衝突の報告(ownKeyShadow)から外す(`getInjectedKeys`)。
|
|
7416
|
+
* 私有判定そのものは innerState が「部分規則が覆うキー」を静的に除くので、ここには依らない。
|
|
7417
|
+
*
|
|
7418
|
+
* どちらもカスタム要素・オブジェクト値のときだけ記録するので、通常のプロパティ書き込み
|
|
7419
|
+
* (textContent / value / checked …)のホットパスには typeof 判定 1 つしか載らない。
|
|
7420
|
+
*/
|
|
7421
|
+
const overwrittenObjectByElement = new WeakMap();
|
|
7422
|
+
const injectedKeysByElement = new WeakMap();
|
|
7423
|
+
/** 1 セグメント書き込みで置き換えられる直前のオブジェクトを控える。最初の 1 回だけ(作者のもの)。 */
|
|
7424
|
+
function rememberOverwrittenObject(element, prop, previous) {
|
|
7425
|
+
let byProp = overwrittenObjectByElement.get(element);
|
|
7426
|
+
if (!byProp) {
|
|
7427
|
+
byProp = new Map();
|
|
7428
|
+
overwrittenObjectByElement.set(element, byProp);
|
|
7429
|
+
}
|
|
7430
|
+
if (!byProp.has(prop)) {
|
|
7431
|
+
byProp.set(prop, previous);
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
/** 控えを取り出して消す。無ければ undefined。 */
|
|
7435
|
+
function takeOverwrittenObject(element, prop) {
|
|
7436
|
+
const byProp = overwrittenObjectByElement.get(element);
|
|
7437
|
+
if (!byProp) {
|
|
7438
|
+
return undefined;
|
|
7439
|
+
}
|
|
7440
|
+
const previous = byProp.get(prop);
|
|
7441
|
+
byProp.delete(prop);
|
|
7442
|
+
return previous;
|
|
7443
|
+
}
|
|
7444
|
+
/** 2 セグメント書き込み(`state.theme`)が作者のオブジェクトに無かったキーを作ったことを控える。 */
|
|
7445
|
+
function recordInjectedKey(element, prop, key) {
|
|
7446
|
+
let byProp = injectedKeysByElement.get(element);
|
|
7447
|
+
if (!byProp) {
|
|
7448
|
+
byProp = new Map();
|
|
7449
|
+
injectedKeysByElement.set(element, byProp);
|
|
7450
|
+
}
|
|
7451
|
+
let keys = byProp.get(prop);
|
|
7452
|
+
if (!keys) {
|
|
7453
|
+
keys = new Set();
|
|
7454
|
+
byProp.set(prop, keys);
|
|
7455
|
+
}
|
|
7456
|
+
keys.add(key);
|
|
7457
|
+
}
|
|
7458
|
+
function getInjectedKeys(element, prop) {
|
|
7459
|
+
return injectedKeysByElement.get(element)?.get(prop);
|
|
7460
|
+
}
|
|
7461
|
+
|
|
7295
7462
|
/**
|
|
7296
7463
|
* SSR 時に HTML 属性で表現できないプロパティバインディングを蓄積するストア。
|
|
7297
7464
|
* ハイドレーション時にクライアント側で復元する。
|
|
@@ -7388,7 +7555,17 @@ function applyChangeToProperty(binding, _context, newValue) {
|
|
|
7388
7555
|
const propSegments = binding.propSegments;
|
|
7389
7556
|
if (propSegments.length === 1) {
|
|
7390
7557
|
const firstSegment = propSegments[0];
|
|
7391
|
-
|
|
7558
|
+
const current = element[firstSegment];
|
|
7559
|
+
if (current !== newValue) {
|
|
7560
|
+
// 完了前の丸ごとマウント(`state: user`)は、作者の state オブジェクトを親の
|
|
7561
|
+
// オブジェクトで置き換えてしまう。あとで戻せるように置き換え前を控える
|
|
7562
|
+
// (webComponent/preCompletionWrites.ts)。オブジェクト → オブジェクトの書き込みで
|
|
7563
|
+
// 相手がカスタム要素のときだけ台帳に触る(通常の書き込みは typeof 判定で抜ける)。
|
|
7564
|
+
if (current !== null && typeof current === 'object'
|
|
7565
|
+
&& newValue !== null && typeof newValue === 'object'
|
|
7566
|
+
&& getCustomElement(element) !== null) {
|
|
7567
|
+
rememberOverwrittenObject(element, firstSegment, current);
|
|
7568
|
+
}
|
|
7392
7569
|
const performWrite = () => {
|
|
7393
7570
|
let propertyWriteSucceeded = false;
|
|
7394
7571
|
try {
|
|
@@ -7490,7 +7667,8 @@ function applyChangeToProperty(binding, _context, newValue) {
|
|
|
7490
7667
|
}
|
|
7491
7668
|
subObject = subObject[segment];
|
|
7492
7669
|
}
|
|
7493
|
-
const
|
|
7670
|
+
const lastSegment = propSegments[propSegments.length - 1];
|
|
7671
|
+
const oldValue = subObject[lastSegment];
|
|
7494
7672
|
if (oldValue !== newValue) {
|
|
7495
7673
|
if (Object.isFrozen(subObject)) {
|
|
7496
7674
|
if (config.debug) {
|
|
@@ -7503,8 +7681,15 @@ function applyChangeToProperty(binding, _context, newValue) {
|
|
|
7503
7681
|
}
|
|
7504
7682
|
return;
|
|
7505
7683
|
}
|
|
7684
|
+
// 完了前の部分マウント(`state.theme: theme`)が、作者の state オブジェクトに無かった
|
|
7685
|
+
// キーを作る(積み)ことを控える。R1 の衝突報告はこのキーを作者のものとして扱わない
|
|
7686
|
+
// (webComponent/preCompletionWrites.ts)
|
|
7687
|
+
if (propSegments.length === 2 && typeof subObject === 'object' && subObject !== null
|
|
7688
|
+
&& !(lastSegment in subObject) && getCustomElement(element) !== null) {
|
|
7689
|
+
recordInjectedKey(element, firstSegment, lastSegment);
|
|
7690
|
+
}
|
|
7506
7691
|
try {
|
|
7507
|
-
subObject[
|
|
7692
|
+
subObject[lastSegment] = newValue;
|
|
7508
7693
|
}
|
|
7509
7694
|
catch (error) {
|
|
7510
7695
|
if (config.debug) {
|
|
@@ -7562,6 +7747,34 @@ function applyChangeToText(binding, _context, newValue) {
|
|
|
7562
7747
|
}
|
|
7563
7748
|
}
|
|
7564
7749
|
|
|
7750
|
+
/**
|
|
7751
|
+
* ルート規則(`data-wcs="state: path"` の丸ごとマウント)で、親がマウント先を
|
|
7752
|
+
* 丸ごと差し替えた/子が切断 → 再接続した、というときに子へ「読み直せ」と撃つパスの集合。
|
|
7753
|
+
*
|
|
7754
|
+
* 部分規則ならプライマリの内側パス(`state.items: rows` なら `items`)を撃てば依存 walk が
|
|
7755
|
+
* 配下へ展開するが、ルート規則は内側パスが空で `$postUpdate("")` に意味が無い。
|
|
7756
|
+
* 代わりに子の登録済みパス(`boundPaths`)の**先頭セグメント**を撃つ — `tags.*.name` は
|
|
7757
|
+
* `tags` から静的依存で展開されるので、先頭だけで配下を覆える。
|
|
7758
|
+
*
|
|
7759
|
+
* `$` 名前空間(`$1` など)は state に実体を持たないので除く。私有キー(R1)への通知は
|
|
7760
|
+
* 値が変わっていないので再描画が同値で終わるだけ(無害)。
|
|
7761
|
+
*/
|
|
7762
|
+
function getRootReloadPaths(innerStateElement) {
|
|
7763
|
+
const boundPaths = innerStateElement.boundPaths;
|
|
7764
|
+
if (typeof boundPaths === 'undefined') {
|
|
7765
|
+
return [];
|
|
7766
|
+
}
|
|
7767
|
+
const roots = new Set();
|
|
7768
|
+
for (const path of boundPaths) {
|
|
7769
|
+
if (path[0] === '$') {
|
|
7770
|
+
continue;
|
|
7771
|
+
}
|
|
7772
|
+
const dot = path.indexOf(DELIMITER);
|
|
7773
|
+
roots.add(dot === -1 ? path : path.slice(0, dot));
|
|
7774
|
+
}
|
|
7775
|
+
return [...roots];
|
|
7776
|
+
}
|
|
7777
|
+
|
|
7565
7778
|
/**
|
|
7566
7779
|
* 親 state → `bind-component` 済みコンポーネントの再読込通知(内部チャネル)。
|
|
7567
7780
|
*
|
|
@@ -7580,13 +7793,14 @@ function applyChangeToText(binding, _context, newValue) {
|
|
|
7580
7793
|
* (apply/applyChange.ts)、`bindWebComponent` は完了済み = state element は登録済み。
|
|
7581
7794
|
* ただし**登録済みと使用可能は別**で、切断済みの state element が台帳に残っている
|
|
7582
7795
|
* 窓がある(§1.9)。下の使用可能判定を参照。
|
|
7796
|
+
*
|
|
7797
|
+
* 残余パスが空(`data-wcs="state: user"` — ルート規則の丸ごとマウント)は、親が
|
|
7798
|
+
* マウント先を丸ごと差し替えたという通知。何が変わったかは分からないので、子の
|
|
7799
|
+
* 登録済みパス全部を読み直す(docs/state-mount-design.md §3-2 / impl-plan P1-2)。
|
|
7583
7800
|
*/
|
|
7584
7801
|
function applyChangeToWebComponent(binding, _context, _newValue) {
|
|
7585
7802
|
const element = binding.node;
|
|
7586
7803
|
const propSegments = binding.propSegments;
|
|
7587
|
-
if (propSegments.length <= 1) {
|
|
7588
|
-
raiseError(`Invalid propSegments for web component binding: ${propSegments.join(DELIMITER)}`);
|
|
7589
|
-
}
|
|
7590
7804
|
const [firstSegment, ...restSegments] = propSegments;
|
|
7591
7805
|
const innerStateElement = getStateElementByWebComponent(element, firstSegment);
|
|
7592
7806
|
if (innerStateElement === null) {
|
|
@@ -7611,6 +7825,18 @@ function applyChangeToWebComponent(binding, _context, _newValue) {
|
|
|
7611
7825
|
}
|
|
7612
7826
|
return;
|
|
7613
7827
|
}
|
|
7828
|
+
if (restSegments.length === 0) {
|
|
7829
|
+
const paths = getRootReloadPaths(innerStateElement);
|
|
7830
|
+
if (paths.length === 0) {
|
|
7831
|
+
return;
|
|
7832
|
+
}
|
|
7833
|
+
innerStateElement.createState("readonly", (state) => {
|
|
7834
|
+
for (const path of paths) {
|
|
7835
|
+
state.$postUpdate(path);
|
|
7836
|
+
}
|
|
7837
|
+
});
|
|
7838
|
+
return;
|
|
7839
|
+
}
|
|
7614
7840
|
innerStateElement.createState("readonly", (state) => {
|
|
7615
7841
|
state.$postUpdate(restSegments.join(DELIMITER));
|
|
7616
7842
|
});
|
|
@@ -7997,21 +8223,36 @@ const deferredSelectBindingByBinding = new WeakMap();
|
|
|
7997
8223
|
// registry 照会を省略できる。scoped registry を導入する場合はこの不可逆前提を再検討。
|
|
7998
8224
|
const definedApplyVerifiedByBinding = new WeakMap();
|
|
7999
8225
|
/**
|
|
8000
|
-
*
|
|
8001
|
-
*
|
|
8226
|
+
* 丸ごとマウント(`state: user`)の完了前の初期適用は書かない。子が完了すれば
|
|
8227
|
+
* innerState 経由でライブに読むので、ここで親のオブジェクトを書く意味は無い
|
|
8228
|
+
* (書くと害がある — webComponent/completeWebComponent.ts の宣言台帳を参照)。
|
|
8229
|
+
*/
|
|
8230
|
+
function skipPendingRootMount() { }
|
|
8231
|
+
/**
|
|
8232
|
+
* カスタム要素へのプロパティバインディングの適用関数を決める。
|
|
8233
|
+
*
|
|
8234
|
+
* - 完了済み(bindWebComponent が公開プロパティを差し替え終えた)→ 値を運ばない
|
|
8235
|
+
* 再読込通知(applyChangeToWebComponent)。1 セグメント(`state: user`)も含む —
|
|
8236
|
+
* 残余が空なら「子の登録済みパス全部を読み直せ」の意味(ルート規則。
|
|
8237
|
+
* docs/state-mount-design.md §3-2 / impl-plan P1-2)
|
|
8238
|
+
* - 未完了だが `<wcs-state bind-component>` が宣言済みで、かつ 1 セグメント → 今回は
|
|
8239
|
+
* 書かない(skipPendingRootMount)
|
|
8240
|
+
* - それ以外 → 素のプロパティ書き込み(`state.name: x` の完了前の積みも含む)
|
|
8002
8241
|
*
|
|
8003
|
-
*
|
|
8004
|
-
*
|
|
8005
|
-
*
|
|
8006
|
-
* 「先頭セグメント=束ね先の state 要素、残り=子側のパス」を前提にしており
|
|
8007
|
-
* 残余が空だと raiseError する。updater の drain は例外を捕まえないので、
|
|
8008
|
-
* 誤設定タグ 1 つが同じバッチの無関係な更新まで巻き添えにしてしまう。
|
|
8009
|
-
* ここで弾いておけば従来どおり applyChangeToProperty に落ち、挙動は変わらない
|
|
8010
|
-
* (getter だけの公開プロパティへの代入が握り潰される = 無言の no-op)。
|
|
8242
|
+
* 以前は 1 セグメントを通知チャネルから除いていた(残余が空だと applyChangeToWebComponent
|
|
8243
|
+
* が raiseError し、updater の drain が捕まえないので同じバッチの無関係な更新まで
|
|
8244
|
+
* 巻き添えにした)。残余空がルート規則の意味を持った今、その除外は要らない。
|
|
8011
8245
|
*/
|
|
8012
|
-
function
|
|
8013
|
-
|
|
8014
|
-
|
|
8246
|
+
function resolveCustomElementApply(binding) {
|
|
8247
|
+
const element = binding.replaceNode;
|
|
8248
|
+
const stateProp = binding.propSegments[0];
|
|
8249
|
+
if (isWebComponentComplete(element, stateProp)) {
|
|
8250
|
+
return applyChangeToWebComponent;
|
|
8251
|
+
}
|
|
8252
|
+
if (binding.propSegments.length === 1 && isWebComponentStatePropDeclared(element, stateProp)) {
|
|
8253
|
+
return skipPendingRootMount;
|
|
8254
|
+
}
|
|
8255
|
+
return applyChangeToProperty;
|
|
8015
8256
|
}
|
|
8016
8257
|
function _applyChange(binding, context) {
|
|
8017
8258
|
const value = getValue(context.state, binding);
|
|
@@ -8026,13 +8267,10 @@ function _applyChange(binding, context) {
|
|
|
8026
8267
|
return;
|
|
8027
8268
|
}
|
|
8028
8269
|
if (fnByBinding.has(binding)) {
|
|
8029
|
-
|
|
8030
|
-
|
|
8270
|
+
fn = resolveCustomElementApply(binding);
|
|
8271
|
+
if (fn === applyChangeToWebComponent) {
|
|
8031
8272
|
fnByBinding.set(binding, fn); // 確定したのでキャッシュ
|
|
8032
8273
|
}
|
|
8033
|
-
else {
|
|
8034
|
-
fn = applyChangeToProperty;
|
|
8035
|
-
}
|
|
8036
8274
|
fn(binding, context, filteredValue);
|
|
8037
8275
|
return;
|
|
8038
8276
|
}
|
|
@@ -8044,13 +8282,10 @@ function _applyChange(binding, context) {
|
|
|
8044
8282
|
if (typeof fn === 'undefined') {
|
|
8045
8283
|
const customTag = getCustomElement(binding.replaceNode);
|
|
8046
8284
|
if (customTag) {
|
|
8047
|
-
|
|
8048
|
-
|
|
8285
|
+
fn = resolveCustomElementApply(binding);
|
|
8286
|
+
if (fn === applyChangeToWebComponent) {
|
|
8049
8287
|
fnByBinding.set(binding, fn); // 確定したのでキャッシュ
|
|
8050
8288
|
}
|
|
8051
|
-
else {
|
|
8052
|
-
fn = applyChangeToProperty;
|
|
8053
|
-
}
|
|
8054
8289
|
}
|
|
8055
8290
|
else {
|
|
8056
8291
|
fn = applyChangeToProperty;
|
|
@@ -8753,7 +8988,7 @@ async function buildBindings(root) {
|
|
|
8753
8988
|
}
|
|
8754
8989
|
}
|
|
8755
8990
|
|
|
8756
|
-
var version = "1.
|
|
8991
|
+
var version = "1.33.0";
|
|
8757
8992
|
var pkg = {
|
|
8758
8993
|
version: version};
|
|
8759
8994
|
|
|
@@ -10533,9 +10768,19 @@ function describeImportFailure(name, error, cspBlocked) {
|
|
|
10533
10768
|
return `Failed to evaluate the inline <script> of state "${name}": ${detail}. ` +
|
|
10534
10769
|
`If this page sets a Content-Security-Policy, see ${CSP_GUIDE}`;
|
|
10535
10770
|
}
|
|
10771
|
+
/**
|
|
10772
|
+
* ロードごとに増える通し番号。`data:` URL フォールバック(createObjectURL の無い
|
|
10773
|
+
* テスト / SSR 環境)では URL がスクリプト本文そのものなので、同じ本文を 2 度
|
|
10774
|
+
* 読み込むと ESM ローダーのキャッシュに当たり **同じモジュール = 同じ default export
|
|
10775
|
+
* オブジェクト** が返る — 2 つ目の `<wcs-state>` が 1 つ目の state を共有してしまう
|
|
10776
|
+
* (テストでは前のテストの書き込みが次のテストに漏れ、SSR では同一テンプレートの
|
|
10777
|
+
* 再描画で state が共有される)。blob: URL は生成のたびに一意なのでブラウザでは
|
|
10778
|
+
* 起きない。sourceURL コメントに番号を混ぜて本文を毎回変え、両経路を揃える。
|
|
10779
|
+
*/
|
|
10780
|
+
let loadSequence = 0;
|
|
10536
10781
|
async function loadFromInnerScript(script, name) {
|
|
10537
10782
|
let scriptModule = null;
|
|
10538
|
-
const uniq_comment = `\n//# sourceURL=${name}\n`;
|
|
10783
|
+
const uniq_comment = `\n//# sourceURL=${name}#${++loadSequence}\n`;
|
|
10539
10784
|
// import() が失敗した理由が CSP かどうかを判別するために、評価の間だけ違反を購読する。
|
|
10540
10785
|
let cspBlocked = false;
|
|
10541
10786
|
const onViolation = (event) => {
|
|
@@ -15010,9 +15255,58 @@ function meltFrozenObject(frozenObj) {
|
|
|
15010
15255
|
class InnerStateProxyHandler {
|
|
15011
15256
|
_webComponent;
|
|
15012
15257
|
_innerStateElement;
|
|
15258
|
+
/**
|
|
15259
|
+
* ルート規則(`state: path` の丸ごとマウント)の下にいるか。プライマリ規則は
|
|
15260
|
+
* bindWebComponent が createInnerState より先に組むので、構築時に確定する。
|
|
15261
|
+
*/
|
|
15262
|
+
_rootMounted;
|
|
15263
|
+
/**
|
|
15264
|
+
* 部分規則(`state.theme: theme`)が覆う内側の先頭セグメント。ルート規則と併用された
|
|
15265
|
+
* とき、これらのキーは own data key があってもマッピング(1.x の既存挙動)に落とす。
|
|
15266
|
+
* 完了前の親の初期適用が `element.state.theme = obj` とキーを注入する(積み)ので、
|
|
15267
|
+
* own key の有無では作者の意図を判定できない(webComponent/preCompletionWrites.ts)。
|
|
15268
|
+
*/
|
|
15269
|
+
_partialFirstSegments;
|
|
15013
15270
|
constructor(webComponent, stateName) {
|
|
15014
15271
|
this._webComponent = webComponent;
|
|
15015
15272
|
this._innerStateElement = getStateElementByWebComponent(webComponent, stateName) ?? raiseError('State element not found for web component.');
|
|
15273
|
+
this._rootMounted = hasRootMappingRule(webComponent);
|
|
15274
|
+
this._partialFirstSegments = null;
|
|
15275
|
+
if (this._rootMounted) {
|
|
15276
|
+
const rules = getPrimaryMappingRules(webComponent);
|
|
15277
|
+
for (const rule of rules ?? []) {
|
|
15278
|
+
if (!rule.isRoot) {
|
|
15279
|
+
(this._partialFirstSegments ??= new Set()).add(rule.innerAbsPathInfo.pathInfo.segments[0]);
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
}
|
|
15283
|
+
}
|
|
15284
|
+
/**
|
|
15285
|
+
* R1(docs/state-mount-design.md D4 / D19): ルートマウント下では、コンポーネントが
|
|
15286
|
+
* 自分で書いた data key は**私有**で、マウント先のツリーを隠す。v1 の解決順
|
|
15287
|
+
* (getter → マッピング → ローカル)のままルート規則を載せると、ルート規則は全キーに
|
|
15288
|
+
* 一致するので own key が全部ツリーに隠され、2.0 と逆の意味論で出荷することになる。
|
|
15289
|
+
*
|
|
15290
|
+
* 判定は**先頭セグメント**で行う。getByAddress は `"draft.title" in target` → 偽 →
|
|
15291
|
+
* 親アドレス `draft` の読み、と降りてくるので、先頭が私有ならその下は素のオブジェクト
|
|
15292
|
+
* 走査になる(plain な state と同じ)。getter / setter は規則 1(chroot 評価)なので除く。
|
|
15293
|
+
*
|
|
15294
|
+
* 部分マウントだけのコンポーネントには適用しない(1.x の既存挙動=マッピングが勝つ、を
|
|
15295
|
+
* 維持する。2.0 での反転は bindWebComponent が warn で予告する — D19)。
|
|
15296
|
+
*/
|
|
15297
|
+
_isPrivateKey(target, prop) {
|
|
15298
|
+
if (!this._rootMounted) {
|
|
15299
|
+
return false;
|
|
15300
|
+
}
|
|
15301
|
+
const dot = prop.indexOf(DELIMITER);
|
|
15302
|
+
const first = dot === -1 ? prop : prop.slice(0, dot);
|
|
15303
|
+
if (!Object.prototype.hasOwnProperty.call(target, first)) {
|
|
15304
|
+
return false;
|
|
15305
|
+
}
|
|
15306
|
+
if (this._partialFirstSegments !== null && this._partialFirstSegments.has(first)) {
|
|
15307
|
+
return false;
|
|
15308
|
+
}
|
|
15309
|
+
return !this._innerStateElement.getterPaths.has(first) && !this._innerStateElement.setterPaths.has(first);
|
|
15016
15310
|
}
|
|
15017
15311
|
/**
|
|
15018
15312
|
* 親スコープで読み書きするときのループ文脈を決める。候補は 2 つある。
|
|
@@ -15065,6 +15359,10 @@ class InnerStateProxyHandler {
|
|
|
15065
15359
|
if (this._innerStateElement.getterPaths.has(prop) && prop in target) {
|
|
15066
15360
|
return Reflect.get(target, prop, receiver);
|
|
15067
15361
|
}
|
|
15362
|
+
// 1'. R1: ルートマウント下の own data key → 私有(マッピングより先)
|
|
15363
|
+
if (this._isPrivateKey(target, prop)) {
|
|
15364
|
+
return Reflect.get(target, prop, receiver);
|
|
15365
|
+
}
|
|
15068
15366
|
// 2 & 3. マッピング完全一致 / サブパス → 親の状態
|
|
15069
15367
|
const innerPathInfo = getPathInfo(prop);
|
|
15070
15368
|
const innerAbsPathInfo = getAbsolutePathInfo(this._innerStateElement, innerPathInfo);
|
|
@@ -15096,6 +15394,10 @@ class InnerStateProxyHandler {
|
|
|
15096
15394
|
if (this._innerStateElement.setterPaths.has(prop) && prop in target) {
|
|
15097
15395
|
return Reflect.set(target, prop, value, receiver);
|
|
15098
15396
|
}
|
|
15397
|
+
// 1'. R1: ルートマウント下の own data key → 私有に書く
|
|
15398
|
+
if (this._isPrivateKey(target, prop)) {
|
|
15399
|
+
return Reflect.set(target, prop, value, receiver);
|
|
15400
|
+
}
|
|
15099
15401
|
// 2 & 3. マッピング完全一致 / サブパス → 親に書く
|
|
15100
15402
|
const innerPathInfo = getPathInfo(prop);
|
|
15101
15403
|
const innerAbsPathInfo = getAbsolutePathInfo(this._innerStateElement, innerPathInfo);
|
|
@@ -15129,6 +15431,11 @@ class InnerStateProxyHandler {
|
|
|
15129
15431
|
if ((this._innerStateElement.getterPaths.has(prop) || this._innerStateElement.setterPaths.has(prop)) && prop in target) {
|
|
15130
15432
|
return true;
|
|
15131
15433
|
}
|
|
15434
|
+
// 1'. R1: 先頭が私有キーなら素のオブジェクトの答え(完全一致だけ真。`draft.title` は
|
|
15435
|
+
// 偽を返して getByAddress に親アドレスから降りてもらう)
|
|
15436
|
+
if (this._isPrivateKey(target, prop)) {
|
|
15437
|
+
return prop in target;
|
|
15438
|
+
}
|
|
15132
15439
|
// 2 & 3. マッピング
|
|
15133
15440
|
const innerPathInfo = getPathInfo(prop);
|
|
15134
15441
|
const innerAbsPathInfo = getAbsolutePathInfo(this._innerStateElement, innerPathInfo);
|
|
@@ -15220,6 +15527,116 @@ function createOuterState(webComponent, stateName) {
|
|
|
15220
15527
|
return new Proxy({}, handler);
|
|
15221
15528
|
}
|
|
15222
15529
|
|
|
15530
|
+
/**
|
|
15531
|
+
* コンポーネントの own data key とマウントの衝突を、バインド確立時に 1 回だけ報告する
|
|
15532
|
+
* (docs/state-mount-design.md D4 / D19、impl-plan P1-10 / P1-11)。
|
|
15533
|
+
*
|
|
15534
|
+
* 2 つの形がある。
|
|
15535
|
+
*
|
|
15536
|
+
* - **ルートマウント**(`state: user`): R1 では own data key は私有で、マウント先の
|
|
15537
|
+
* 同名キー(`user.name`)を**隠す**。書き手が「既定値」のつもりで置いたキーがツリーを
|
|
15538
|
+
* 読まなくなるので、マウント先の値がオブジェクトで同名キーを持つときに報告する。
|
|
15539
|
+
* - **部分マウント**(`state.message: x` + `state = { message: "" }`): 1.x では
|
|
15540
|
+
* マッピングが勝つ(既存挙動・不変)が、v2 では R1 で own key が私有になり逆転する。
|
|
15541
|
+
* 反転を 1.x の時点で予告する。
|
|
15542
|
+
*
|
|
15543
|
+
* どちらも「既定値を消す(ツリーを読む)か、名前を変える(私有のまま)」で直る。
|
|
15544
|
+
* 報告はタグ名 × プロパティ × キーで 1 回(リストの行ごとに並ばないように)。
|
|
15545
|
+
* ホットパス外(bindWebComponent の中・要素につき 1 回)。
|
|
15546
|
+
*/
|
|
15547
|
+
const reported = new Set();
|
|
15548
|
+
function ownDataKeys(state, injected) {
|
|
15549
|
+
const keys = [];
|
|
15550
|
+
for (const key of Object.keys(state)) {
|
|
15551
|
+
if (key.startsWith('$')) {
|
|
15552
|
+
continue;
|
|
15553
|
+
}
|
|
15554
|
+
// 完了前の親の初期適用(`state.theme: theme` の積み)が作ったキーは作者のものではない
|
|
15555
|
+
if (typeof injected !== 'undefined' && injected.has(key)) {
|
|
15556
|
+
continue;
|
|
15557
|
+
}
|
|
15558
|
+
const descriptor = Object.getOwnPropertyDescriptor(state, key);
|
|
15559
|
+
if (typeof descriptor.get === 'function' || typeof descriptor.set === 'function') {
|
|
15560
|
+
continue;
|
|
15561
|
+
}
|
|
15562
|
+
if (typeof descriptor.value === 'function') {
|
|
15563
|
+
continue;
|
|
15564
|
+
}
|
|
15565
|
+
keys.push(key);
|
|
15566
|
+
}
|
|
15567
|
+
return keys;
|
|
15568
|
+
}
|
|
15569
|
+
/**
|
|
15570
|
+
* ルート規則のマウント先の現在値を読む。ワイルドカードを含むマウント先(`users.*`)は
|
|
15571
|
+
* ホスト要素のループ文脈で解決する。文脈が無ければ判定できないので undefined。
|
|
15572
|
+
*/
|
|
15573
|
+
function readMountTarget(component, rule) {
|
|
15574
|
+
const outer = rule.outerAbsPathInfo;
|
|
15575
|
+
const loopContext = getLoopContextByNode(component);
|
|
15576
|
+
if (outer.pathInfo.wildcardCount > 0 && loopContext === null) {
|
|
15577
|
+
return undefined;
|
|
15578
|
+
}
|
|
15579
|
+
let value = undefined;
|
|
15580
|
+
outer.stateElement.createState("readonly", (state) => {
|
|
15581
|
+
state[setLoopContextSymbol](loopContext, () => {
|
|
15582
|
+
value = state[outer.pathInfo.path];
|
|
15583
|
+
});
|
|
15584
|
+
});
|
|
15585
|
+
return value;
|
|
15586
|
+
}
|
|
15587
|
+
function report(key, message) {
|
|
15588
|
+
if (reported.has(key)) {
|
|
15589
|
+
return;
|
|
15590
|
+
}
|
|
15591
|
+
reported.add(key);
|
|
15592
|
+
console.warn(`[@wcstack/state] [wcs/mount-own-key-shadow] ${message} See docs/state-mount-design.md §4-3.`);
|
|
15593
|
+
}
|
|
15594
|
+
function warnOwnKeyShadows(component, stateProp, state) {
|
|
15595
|
+
const rules = getPrimaryMappingRules(component);
|
|
15596
|
+
if (rules === null) {
|
|
15597
|
+
return;
|
|
15598
|
+
}
|
|
15599
|
+
const keys = ownDataKeys(state, getInjectedKeys(component, stateProp));
|
|
15600
|
+
if (keys.length === 0) {
|
|
15601
|
+
return;
|
|
15602
|
+
}
|
|
15603
|
+
const tag = component.tagName.toLowerCase();
|
|
15604
|
+
let rootRule = null;
|
|
15605
|
+
const partialRuleByKey = new Map();
|
|
15606
|
+
for (const rule of rules) {
|
|
15607
|
+
if (rule.isRoot) {
|
|
15608
|
+
rootRule = rule;
|
|
15609
|
+
}
|
|
15610
|
+
else if (rule.innerAbsPathInfo.pathInfo.segments.length === 1) {
|
|
15611
|
+
partialRuleByKey.set(rule.innerAbsPathInfo.pathInfo.path, rule);
|
|
15612
|
+
}
|
|
15613
|
+
}
|
|
15614
|
+
let mountTarget = undefined;
|
|
15615
|
+
let mountTargetRead = false;
|
|
15616
|
+
for (const key of keys) {
|
|
15617
|
+
const partial = partialRuleByKey.get(key);
|
|
15618
|
+
if (typeof partial !== 'undefined') {
|
|
15619
|
+
const outerPath = partial.outerAbsPathInfo.pathInfo.path;
|
|
15620
|
+
report(`${tag}|${stateProp}|${key}|partial`, `<${tag}>.${stateProp}.${key} is an own key of the component and is also mapped from the host ("${stateProp}.${key}: ${outerPath}"). ` +
|
|
15621
|
+
`Today the host value wins; in v2 the own key becomes private and hides the host value. Remove the default, or drop the mapping.`);
|
|
15622
|
+
continue;
|
|
15623
|
+
}
|
|
15624
|
+
if (rootRule === null) {
|
|
15625
|
+
continue;
|
|
15626
|
+
}
|
|
15627
|
+
if (!mountTargetRead) {
|
|
15628
|
+
mountTarget = readMountTarget(component, rootRule);
|
|
15629
|
+
mountTargetRead = true;
|
|
15630
|
+
}
|
|
15631
|
+
if (typeof mountTarget !== 'object' || mountTarget === null || !(key in mountTarget)) {
|
|
15632
|
+
continue;
|
|
15633
|
+
}
|
|
15634
|
+
const outerPath = rootRule.outerAbsPathInfo.pathInfo.path;
|
|
15635
|
+
report(`${tag}|${stateProp}|${key}|root`, `<${tag}>.${stateProp}.${key} is private and hides the mounted tree key "${outerPath}.${key}" (${stateProp}: ${outerPath}). ` +
|
|
15636
|
+
`Remove the default to read the tree, or rename it to keep it private.`);
|
|
15637
|
+
}
|
|
15638
|
+
}
|
|
15639
|
+
|
|
15223
15640
|
const getOuter = (outerState) => () => outerState;
|
|
15224
15641
|
function bindWebComponent(innerStateElement, component, stateProp, state) {
|
|
15225
15642
|
setStateElementByWebComponent(component, stateProp, innerStateElement);
|
|
@@ -15236,6 +15653,9 @@ function bindWebComponent(innerStateElement, component, stateProp, state) {
|
|
|
15236
15653
|
// innerState proxy、plain なら melt 済みのローカル state。
|
|
15237
15654
|
if (bindings.length > 0) {
|
|
15238
15655
|
buildPrimaryMappingRule(component, stateProp, bindings);
|
|
15656
|
+
// own data key とマウントの衝突を 1 回だけ報告する(R1 の私有キーがツリーを隠す形と、
|
|
15657
|
+
// 部分マウントで v2 に反転する形 — docs/state-mount-design.md D19)。
|
|
15658
|
+
warnOwnKeyShadows(component, stateProp, state);
|
|
15239
15659
|
// 値の正本が親スコープにあることを state 要素に記録する。越境アドレスの受け渡しと
|
|
15240
15660
|
// リストパスの外向き伝播はこのフラグでのみ有効になる(§1.8)。
|
|
15241
15661
|
innerStateElement.markComponentStateMapped?.();
|
|
@@ -15266,6 +15686,27 @@ function bindWebComponent(innerStateElement, component, stateProp, state) {
|
|
|
15266
15686
|
}
|
|
15267
15687
|
}
|
|
15268
15688
|
|
|
15689
|
+
/**
|
|
15690
|
+
* ホストが `<stateProp>: path`(1 セグメント = 丸ごとマウント・ルート規則)を
|
|
15691
|
+
* このコンポーネントに書いているか。バインディング初期化が済んだ後に呼ぶこと
|
|
15692
|
+
* (`State._initializeBindWebComponent` は `waitInitializeBinding` の後で呼ぶ)。
|
|
15693
|
+
*/
|
|
15694
|
+
function hasRootMountBinding(component, stateProp) {
|
|
15695
|
+
if (!component.hasAttribute(config.bindAttributeName)) {
|
|
15696
|
+
return false;
|
|
15697
|
+
}
|
|
15698
|
+
const bindings = getBindingsByNode(component);
|
|
15699
|
+
if (bindings === null) {
|
|
15700
|
+
return false;
|
|
15701
|
+
}
|
|
15702
|
+
for (const binding of bindings) {
|
|
15703
|
+
if (binding.propSegments.length === 1 && binding.propSegments[0] === stateProp) {
|
|
15704
|
+
return true;
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
return false;
|
|
15708
|
+
}
|
|
15709
|
+
|
|
15269
15710
|
function getStateInfo(state) {
|
|
15270
15711
|
const getterPaths = new Set();
|
|
15271
15712
|
const setterPaths = new Set();
|
|
@@ -15496,6 +15937,11 @@ class State extends HTMLElementBase {
|
|
|
15496
15937
|
}
|
|
15497
15938
|
await this._loadingPromise;
|
|
15498
15939
|
this._name = this.getAttribute('name') || 'default';
|
|
15940
|
+
if (this.hasAttribute('name') && !this.hasAttribute('bind-component')) {
|
|
15941
|
+
// 名前付き State は v2 でマウント(`mount=`)に置き換わる(docs/state-mount-design.md D16)。
|
|
15942
|
+
// Light DOM の bind-component は今日 name が必須なので、そちらには言わない。
|
|
15943
|
+
warnNamedStateDeprecated('attribute', this._name);
|
|
15944
|
+
}
|
|
15499
15945
|
setStateElementByName(this.rootNode, this._name, this);
|
|
15500
15946
|
}
|
|
15501
15947
|
async _initializeBindWebComponent() {
|
|
@@ -15531,6 +15977,11 @@ class State extends HTMLElementBase {
|
|
|
15531
15977
|
raiseError(`"bind-component" cannot be combined with ${conflicting.join(", ")}. The component's "${this.getAttribute("bind-component")}" property is the only state source.`);
|
|
15532
15978
|
}
|
|
15533
15979
|
const boundComponentStateProp = this.getAttribute("bind-component");
|
|
15980
|
+
// 束ねる意思をここで宣言する(完了はずっと後)。丸ごとマウント `state: user` の
|
|
15981
|
+
// 完了前の初期適用は、この宣言を見て書き込みを抑止する
|
|
15982
|
+
// (webComponent/completeWebComponent.ts)。下の await より前でなければ、
|
|
15983
|
+
// 親の初期適用が先に走って親のオブジェクトを state プロパティに書いてしまう。
|
|
15984
|
+
markWebComponentStatePropDeclared(boundComponent, boundComponentStateProp);
|
|
15534
15985
|
const componentRegistry = getCustomElementRegistry(boundComponent);
|
|
15535
15986
|
if (componentRegistry === null) {
|
|
15536
15987
|
// null レジストリのサブツリーではホストは永久に upgrade されない。
|
|
@@ -15545,10 +15996,19 @@ class State extends HTMLElementBase {
|
|
|
15545
15996
|
if (!(boundComponentStateProp in boundComponent)) {
|
|
15546
15997
|
raiseError(`Component does not have property "${boundComponentStateProp}" for state binding.`);
|
|
15547
15998
|
}
|
|
15548
|
-
|
|
15999
|
+
let state = boundComponent[boundComponentStateProp];
|
|
15549
16000
|
if (typeof state !== 'object' || state === null) {
|
|
15550
16001
|
raiseError(`Component property "${boundComponentStateProp}" is not an object for state binding.`);
|
|
15551
16002
|
}
|
|
16003
|
+
// 丸ごとマウント(`state: user`)の完了前の初期適用が、宣言より先に走って state
|
|
16004
|
+
// プロパティを親のオブジェクトごと置き換えていたら、作者のオブジェクトに戻す
|
|
16005
|
+
// (webComponent/preCompletionWrites.ts)。戻さないと親のキー全部が own data key に
|
|
16006
|
+
// なり、R1 で全部が私有に化ける。
|
|
16007
|
+
const authored = takeOverwrittenObject(boundComponent, boundComponentStateProp);
|
|
16008
|
+
if (typeof authored !== 'undefined' && hasRootMountBinding(boundComponent, boundComponentStateProp)) {
|
|
16009
|
+
boundComponent[boundComponentStateProp] = authored;
|
|
16010
|
+
state = authored;
|
|
16011
|
+
}
|
|
15552
16012
|
this._boundComponent = boundComponent;
|
|
15553
16013
|
this._boundComponentStateProp = boundComponentStateProp;
|
|
15554
16014
|
bindWebComponent(this, this._boundComponent, this._boundComponentStateProp, state);
|
|
@@ -15605,11 +16065,17 @@ class State extends HTMLElementBase {
|
|
|
15605
16065
|
}
|
|
15606
16066
|
// mapped = プライマリ規則が 1 件以上あることと同義(bindWebComponent の分岐)
|
|
15607
16067
|
const innerPaths = getPrimaryInnerPaths(this._boundComponent);
|
|
16068
|
+
// ルート規則(丸ごとマウント)は内側パスが空なので、子の登録済みパス全部を読み直す
|
|
16069
|
+
// (webComponent/rootReloadPaths.ts)
|
|
16070
|
+
const rootPaths = hasRootMappingRule(this._boundComponent) ? getRootReloadPaths(this) : [];
|
|
15608
16071
|
resetDerivedMappingRules(this._boundComponent);
|
|
15609
16072
|
this.createState("readonly", (state) => {
|
|
15610
16073
|
for (const path of innerPaths) {
|
|
15611
16074
|
state.$postUpdate(path);
|
|
15612
16075
|
}
|
|
16076
|
+
for (const path of rootPaths) {
|
|
16077
|
+
state.$postUpdate(path);
|
|
16078
|
+
}
|
|
15613
16079
|
});
|
|
15614
16080
|
}
|
|
15615
16081
|
async _callStateConnectedCallback() {
|
|
@@ -15854,6 +16320,9 @@ class State extends HTMLElementBase {
|
|
|
15854
16320
|
get hasMappedComponentState() {
|
|
15855
16321
|
return this._hasMappedComponentState;
|
|
15856
16322
|
}
|
|
16323
|
+
get boundPaths() {
|
|
16324
|
+
return this._pathSet;
|
|
16325
|
+
}
|
|
15857
16326
|
/**
|
|
15858
16327
|
* この state の実体が innerState proxy であることを記録する。唯一の呼び手は
|
|
15859
16328
|
* `bindWebComponent` の mapped 分岐(§1.8)。
|