@wcstack/state 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.ja.md +47 -17
  2. package/README.md +47 -17
  3. package/dist/auto.min.js +1 -1
  4. package/dist/auto.min.js.map +1 -1
  5. package/dist/define.d.ts +11 -7
  6. package/dist/index.d.ts +15 -7
  7. package/dist/index.esm.js +1 -1
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/manifest.d.ts +14 -1
  10. package/dist/manifest.esm.js +91 -35
  11. package/dist/parser.esm.js +25 -0
  12. package/dist/split/chunks/binder.js +1 -1
  13. package/dist/split/chunks/binder.js.map +1 -1
  14. package/dist/split/chunks/checkDependency.js +1 -1
  15. package/dist/split/chunks/declarationAliases.js +2 -0
  16. package/dist/split/chunks/declarationAliases.js.map +1 -0
  17. package/dist/split/chunks/filterRegistry.js +1 -1
  18. package/dist/split/chunks/filterRegistry.js.map +1 -1
  19. package/dist/split/chunks/mount.js +1 -1
  20. package/dist/split/chunks/mount.js.map +1 -1
  21. package/dist/split/chunks/pathDiagnostics.js +1 -1
  22. package/dist/split/chunks/pathDiagnostics.js.map +1 -1
  23. package/dist/split/chunks/registerComponents.js +1 -1
  24. package/dist/split/chunks/registerComponents.js.map +1 -1
  25. package/dist/split/chunks/setAll.js +1 -1
  26. package/dist/split/chunks/version.js +1 -1
  27. package/dist/split/chunks/watchRuntime.js +1 -1
  28. package/dist/split/core.d.ts +11 -7
  29. package/dist/split/core.js +1 -1
  30. package/dist/split/core.js.map +1 -1
  31. package/dist/split/features/devtools.js +1 -1
  32. package/dist/split/features/devtools.js.map +1 -1
  33. package/dist/split/features/diagnostics.js +1 -1
  34. package/dist/split/features/formats.js +1 -1
  35. package/dist/split/features/formats.js.map +1 -1
  36. package/dist/split/features/recursion.js +1 -1
  37. package/dist/split/features/scopes.js +1 -1
  38. package/dist/split/features/scopes.js.map +1 -1
  39. package/dist/split/features/ssr.js +1 -1
  40. package/dist/split/features/ssr.js.map +1 -1
  41. package/dist/split/features/temporal.js +1 -1
  42. package/dist/split/features/temporal.js.map +1 -1
  43. package/dist/wcs-manifest.json +65 -25
  44. package/package.json +1 -1
@@ -30,6 +30,15 @@ interface IFilterMeta {
30
30
  /** 組み込みフィルタ名 → 構造化メタデータ。キー集合は builtinFilters と一致しなければならない。 */
31
31
  declare const builtinFilterMeta: Record<string, IFilterMeta>;
32
32
 
33
+ /**
34
+ * filters/filterAliases.ts — 組み込みフィルタの旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md V1〜V9)。
35
+ *
36
+ * 旧名は 3.x の間エイリアスとして残り、4.0 で外す(D4)。解決は登録簿(core/filterRegistry)が行い、
37
+ * 実装・引数の個数・メタデータは正式名だけが持つ。formats の install と manifest(tooling)の両方が読むので、
38
+ * 実装にもメタデータにも依存しない小さな表として独立させている。
39
+ */
40
+ declare const builtinFilterAliases: Readonly<Record<string, string>>;
41
+
33
42
  type BindingType = 'text' | 'prop' | 'event' | 'for' | 'if' | 'elseif' | 'else' | 'radio' | 'checkbox' | 'spread';
34
43
 
35
44
  declare const STRUCTURAL_BINDING_TYPE_SET: Set<BindingType>;
@@ -83,6 +92,8 @@ interface IWcsManifest {
83
92
  elseKeyword: string;
84
93
  spread: string;
85
94
  eventPropertyPrefix: string;
95
+ /** 左辺の先頭に付けると、名前が `on` で始まってもイベントにしない明示のプロパティ形(`.online:`、要件 B5) */
96
+ explicitPropertyPrefix: string;
86
97
  propNamespaces: {
87
98
  eventToken: string;
88
99
  command: string;
@@ -96,6 +107,8 @@ interface IWcsManifest {
96
107
  filters: string[];
97
108
  /** 組み込みフィルタの構造化メタデータ(説明・引数仕様・型)。vscode-wcs の手リスト撤去用。 */
98
109
  filterMeta: Record<string, IFilterMeta>;
110
+ /** 組み込みフィルタの旧名 → 正式名(要件 B12)。旧名も解決するが、ツールは正式名を提案する */
111
+ filterAliases: Readonly<Record<string, string>>;
99
112
  /** 予約ライフサイクルフック名 */
100
113
  reservedLifecycle: readonly string[];
101
114
  /** 予約 state API(プロトコル系の `$` 名前空間) */
@@ -104,5 +117,5 @@ interface IWcsManifest {
104
117
  /** 機械可読な単一正本を返す。vscode-wcs はこれを消費する想定。 */
105
118
  declare function getWcsManifest(): IWcsManifest;
106
119
 
107
- export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterMeta, getWcsManifest };
120
+ export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterAliases, builtinFilterMeta, getWcsManifest };
108
121
  export type { FilterArgType, FilterResultType, IFilterMeta, IWcsManifest };
@@ -81,6 +81,26 @@ function valueMustBeArray(fnName) {
81
81
  raiseError(`filter ${fnName} requires an array value`);
82
82
  }
83
83
 
84
+ /**
85
+ * filters/filterAliases.ts — 組み込みフィルタの旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md V1〜V9)。
86
+ *
87
+ * 旧名は 3.x の間エイリアスとして残り、4.0 で外す(D4)。解決は登録簿(core/filterRegistry)が行い、
88
+ * 実装・引数の個数・メタデータは正式名だけが持つ。formats の install と manifest(tooling)の両方が読むので、
89
+ * 実装にもメタデータにも依存しない小さな表として独立させている。
90
+ */
91
+ const builtinFilterAliases = {
92
+ inc: "add",
93
+ dec: "sub",
94
+ fix: "toFixed",
95
+ uc: "upper",
96
+ lc: "lower",
97
+ cap: "capitalize",
98
+ rep: "repeat",
99
+ rev: "reverse",
100
+ pad: "padStart",
101
+ null: "nullIfEmpty",
102
+ };
103
+
84
104
  /**
85
105
  * builtinFilters.ts
86
106
  *
@@ -245,13 +265,13 @@ const ge = (options) => {
245
265
  * @returns Filter function that returns incremented number
246
266
  */
247
267
  const inc = (options) => {
248
- const opt = options?.[0] ?? optionsRequired('inc');
268
+ const opt = options?.[0] ?? optionsRequired('add');
249
269
  if (!validateNumberString(opt)) {
250
- optionMustBeNumber('inc');
270
+ optionMustBeNumber('add');
251
271
  }
252
272
  return (value) => {
253
273
  if (typeof value !== 'number') {
254
- valueMustBeNumber('inc');
274
+ valueMustBeNumber('add');
255
275
  }
256
276
  return value + Number(opt);
257
277
  };
@@ -263,13 +283,13 @@ const inc = (options) => {
263
283
  * @returns Filter function that returns decremented number
264
284
  */
265
285
  const dec = (options) => {
266
- const opt = options?.[0] ?? optionsRequired('dec');
286
+ const opt = options?.[0] ?? optionsRequired('sub');
267
287
  if (!validateNumberString(opt)) {
268
- optionMustBeNumber('dec');
288
+ optionMustBeNumber('sub');
269
289
  }
270
290
  return (value) => {
271
291
  if (typeof value !== 'number') {
272
- valueMustBeNumber('dec');
292
+ valueMustBeNumber('sub');
273
293
  }
274
294
  return value - Number(opt);
275
295
  };
@@ -379,11 +399,11 @@ const clamp = (options) => {
379
399
  const fix = (options) => {
380
400
  const opt = options?.[0] ?? "0";
381
401
  if (!validateNumberString(opt)) {
382
- optionMustBeNumber('fix');
402
+ optionMustBeNumber('toFixed');
383
403
  }
384
404
  return (value) => {
385
405
  if (typeof value !== 'number') {
386
- valueMustBeNumber('fix');
406
+ valueMustBeNumber('toFixed');
387
407
  }
388
408
  return value.toFixed(Number(opt));
389
409
  };
@@ -521,15 +541,31 @@ const substr = (options) => {
521
541
  * @returns Filter function that returns padded string
522
542
  */
523
543
  const pad = (options) => {
524
- const opt1 = options?.[0] ?? optionsRequired('pad');
544
+ const opt1 = options?.[0] ?? optionsRequired('padStart');
525
545
  if (!validateNumberString(opt1)) {
526
- optionMustBeNumber('pad');
546
+ optionMustBeNumber('padStart');
527
547
  }
528
548
  const opt2 = options?.[1] ?? '0';
529
549
  return (value) => {
530
550
  return String(value).padStart(Number(opt1), opt2);
531
551
  };
532
552
  };
553
+ /**
554
+ * padEnd filter - pads string to specified length from the end (the pair of `padStart`, requirement B12).
555
+ *
556
+ * @param options - Array with target length and pad string (default: ' ')
557
+ * @returns Filter function that returns padded string
558
+ */
559
+ const padEnd = (options) => {
560
+ const opt1 = options?.[0] ?? optionsRequired('padEnd');
561
+ if (!validateNumberString(opt1)) {
562
+ optionMustBeNumber('padEnd');
563
+ }
564
+ const opt2 = options?.[1] ?? ' ';
565
+ return (value) => {
566
+ return String(value).padEnd(Number(opt1), opt2);
567
+ };
568
+ };
533
569
  /**
534
570
  * Repeat filter - repeats string specified number of times.
535
571
  *
@@ -537,9 +573,9 @@ const pad = (options) => {
537
573
  * @returns Filter function that returns repeated string
538
574
  */
539
575
  const rep = (options) => {
540
- const opt = options?.[0] ?? optionsRequired('rep');
576
+ const opt = options?.[0] ?? optionsRequired('repeat');
541
577
  if (!validateNumberString(opt)) {
542
- optionMustBeNumber('rep');
578
+ optionMustBeNumber('repeat');
543
579
  }
544
580
  return (value) => {
545
581
  return String(value).repeat(Number(opt));
@@ -847,6 +883,18 @@ const defaults = (options, literals) => {
847
883
  return value;
848
884
  };
849
885
  };
886
+ /**
887
+ * coalesce filter - returns the default only for null / undefined (`defaults` also replaces 0, false and "").
888
+ * Requirement B12: the nullish counterpart of `defaults`, read like SQL COALESCE.
889
+ *
890
+ * @param options - Array with default value as first element
891
+ * @returns Filter function that returns value or default
892
+ */
893
+ const coalesce = (options, literals) => {
894
+ const opt = options?.[0] ?? optionsRequired('coalesce');
895
+ const fallback = literals !== undefined && literals.length > 0 ? literals[0] : opt;
896
+ return (value) => value ?? fallback;
897
+ };
850
898
  /**
851
899
  * Boolean filter - converts value to boolean.
852
900
  *
@@ -899,24 +947,25 @@ const builtinFilters = {
899
947
  "le": le,
900
948
  "gt": gt,
901
949
  "ge": ge,
902
- "inc": inc,
903
- "dec": dec,
950
+ "add": inc,
951
+ "sub": dec,
904
952
  "mul": mul,
905
953
  "div": div,
906
954
  "mod": mod,
907
955
  "abs": abs,
908
956
  "clamp": clamp,
909
- "fix": fix,
957
+ "toFixed": fix,
910
958
  "locale": locale,
911
- "uc": uc,
912
- "lc": lc,
913
- "cap": cap,
959
+ "upper": uc,
960
+ "lower": lc,
961
+ "capitalize": cap,
914
962
  "trim": trim,
915
963
  "slice": slice,
916
964
  "substr": substr,
917
- "pad": pad,
918
- "rep": rep,
919
- "rev": rev,
965
+ "padStart": pad,
966
+ "padEnd": padEnd,
967
+ "repeat": rep,
968
+ "reverse": rev,
920
969
  "truncate": truncate,
921
970
  "join": join,
922
971
  "int": int,
@@ -934,10 +983,11 @@ const builtinFilters = {
934
983
  "falsy": falsy,
935
984
  "truthy": truthy,
936
985
  "defaults": defaults,
986
+ "coalesce": coalesce,
937
987
  "boolean": boolean,
938
988
  "number": number,
939
989
  "string": string,
940
- "null": _null,
990
+ "nullIfEmpty": _null,
941
991
  };
942
992
  const outputBuiltinFilters = builtinFilters;
943
993
 
@@ -963,26 +1013,27 @@ const builtinFilterMeta = {
963
1013
  gt: { description: "より大きいか", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
964
1014
  ge: { description: "以上か", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
965
1015
  // 算術
966
- inc: { description: "加算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
967
- dec: { description: "減算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
1016
+ add: { description: "加算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
1017
+ sub: { description: "減算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
968
1018
  mul: { description: "乗算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
969
1019
  div: { description: "除算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
970
1020
  mod: { description: "剰余", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
971
1021
  abs: { description: "絶対値", hasArgs: false, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 0 },
972
1022
  clamp: { description: "範囲内に丸める (min,max)", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 2, maxArgs: 2, argTypes: ["number", "number"] },
973
1023
  // 数値フォーマット
974
- fix: { description: "固定小数点表記", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
1024
+ toFixed: { description: "固定小数点表記", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
975
1025
  locale: { description: "ロケール形式で数値フォーマット", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
976
1026
  // 文字列
977
- uc: { description: "大文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
978
- lc: { description: "小文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
979
- cap: { description: "先頭文字を大文字に", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1027
+ upper: { description: "大文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1028
+ lower: { description: "小文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1029
+ capitalize: { description: "先頭文字を大文字に", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
980
1030
  trim: { description: "前後の空白を削除", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
981
1031
  slice: { description: "部分文字列 (start[,end])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
982
1032
  substr: { description: "部分文字列 (pos,len)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
983
- pad: { description: "パディング (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
984
- rep: { description: "繰り返し (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
985
- rev: { description: "文字順を反転", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1033
+ padStart: { description: "先頭を埋める (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
1034
+ padEnd: { description: "末尾を埋める (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
1035
+ repeat: { description: "繰り返し (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
1036
+ reverse: { description: "文字順を反転", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
986
1037
  truncate: { description: "切り詰めて省略記号 (length[,suffix])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
987
1038
  join: { description: "配列を連結 ([separator])", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
988
1039
  // 数値パース・丸め
@@ -1005,10 +1056,11 @@ const builtinFilterMeta = {
1005
1056
  falsy: { description: "偽値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1006
1057
  truthy: { description: "真値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1007
1058
  defaults: { description: "偽値の場合デフォルト値", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1059
+ coalesce: { description: "null / undefined の場合デフォルト値", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
1008
1060
  boolean: { description: "ブール値に変換", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1009
1061
  number: { description: "数値に変換", hasArgs: false, resultType: "number", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1010
1062
  string: { description: "文字列に変換", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
1011
- null: { description: "空文字列をnullに変換", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1063
+ nullIfEmpty: { description: "空文字列をnullに変換", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
1012
1064
  };
1013
1065
 
1014
1066
  const STRUCTURAL_BINDING_TYPE_SET = new Set([
@@ -1075,7 +1127,8 @@ for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
1075
1127
  Object.freeze(tmpIndexByIndexName);
1076
1128
  const STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
1077
1129
  const STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
1078
- const STATE_UPDATED_CALLBACK_NAME = "$updatedCallback";
1130
+ /** 旧名 `$updatedCallback` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
1131
+ const STATE_UPDATED_CALLBACK_NAME = "$renderedCallback";
1079
1132
  const STATE_ERROR_CALLBACK_NAME = "$errorCallback";
1080
1133
  const WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
1081
1134
  const STATE_BINDABLES_NAME = "$bindables";
@@ -1084,7 +1137,8 @@ const STATE_COMMAND_TOKENS_NAME = "$commandTokens";
1084
1137
  const STATE_COMMAND_NAMESPACE_NAME = "$command";
1085
1138
  const STATE_EVENT_TOKENS_NAME = "$eventTokens";
1086
1139
  const STATE_ON_NAME = "$on";
1087
- const STATE_STREAMS_NAME = "$streams";
1140
+ /** 旧名 `$streams` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
1141
+ const STATE_STREAMS_NAME = "$stream";
1088
1142
  const STATE_WATCH_NAME = "$watch";
1089
1143
  const STATE_SCAN_NAME = "$scan";
1090
1144
  const STATE_RECURSION_NAME = "$recursion";
@@ -1137,6 +1191,7 @@ function getWcsManifest() {
1137
1191
  elseKeyword: ELSE_KEYWORD,
1138
1192
  spread: SPREAD_PROP,
1139
1193
  eventPropertyPrefix: EVENT_PROP_PREFIX,
1194
+ explicitPropertyPrefix: DELIMITER,
1140
1195
  propNamespaces: {
1141
1196
  eventToken: EVENT_TOKEN_NAMESPACE,
1142
1197
  command: COMMAND_NAMESPACE,
@@ -1149,6 +1204,7 @@ function getWcsManifest() {
1149
1204
  // 実装(Record のキー)から自動導出。手リストを持たない=ドリフトの構造的排除。
1150
1205
  filters: Object.keys(outputBuiltinFilters),
1151
1206
  filterMeta: builtinFilterMeta,
1207
+ filterAliases: builtinFilterAliases,
1152
1208
  reservedLifecycle: [
1153
1209
  STATE_CONNECTED_CALLBACK_NAME,
1154
1210
  STATE_DISCONNECTED_CALLBACK_NAME,
@@ -1174,4 +1230,4 @@ function getWcsManifest() {
1174
1230
  };
1175
1231
  }
1176
1232
 
1177
- export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterMeta, getWcsManifest };
1233
+ export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterAliases, builtinFilterMeta, getWcsManifest };
@@ -16,6 +16,10 @@ const ELSE_KEYWORD = 'else';
16
16
  const SPREAD_PROP = '...';
17
17
  const EVENT_PROP_PREFIX = 'on';
18
18
  const EVENT_TOKEN_NAMESPACE = 'eventToken';
19
+ const COMMAND_NAMESPACE = 'command';
20
+ const CLASS_NAMESPACE = 'class';
21
+ const ATTR_NAMESPACE = 'attr';
22
+ const STYLE_NAMESPACE = 'style';
19
23
  // リストインデックス参照名(`$1`..`$N`)の接頭辞(単一正本)。
20
24
  // manifest.syntax.indexParam で公開される。
21
25
  const INDEX_PARAM_PREFIX = '$';
@@ -503,6 +507,10 @@ function parseStatePart(statePart) {
503
507
  // ...: statePart (spread — expand wcBindable properties+inputs of target object)
504
508
  /** 左辺に修飾子も入力フィルタも取らない束縛(構造ディレクティブと spread)— 付いていれば拒否する(要件 B4) */
505
509
  const KEYWORDS_WITHOUT_MODIFIERS = new Set([ELSE_KEYWORD, 'if', 'elseif', 'for', SPREAD_PROP]);
510
+ /** 明示のプロパティ形(`.name:`)の先頭に置けない語 — 名前空間として読まれる語(要件 B5) */
511
+ const EXPLICIT_PROPERTY_REJECTED_HEADS = new Set([
512
+ CLASS_NAMESPACE, ATTR_NAMESPACE, STYLE_NAMESPACE, COMMAND_NAMESPACE, EVENT_TOKEN_NAMESPACE,
513
+ ]);
506
514
  /**
507
515
  * `data-wcs` の値をバインディングごとに区切る(前後の空白は残す — tooling が位置を数えられるように)。
508
516
  * 引用符の中の `;` は区切りではない(要件 B1 — `join(';')`)。ランタイムと tooling(`@wcstack/state/parser`)で共有する
@@ -585,6 +593,23 @@ function parseBindTextsForElement(bindText) {
585
593
  else {
586
594
  const stateResult = parseStatePart(statePart);
587
595
  const propResult = parsePropPart(propPart);
596
+ // 左辺の先頭ドット(`.online:`)は明示のプロパティ形(要件 B5・3.x 計画 D34)。ドットの無い形と
597
+ // 同じ束縛だが、`on` で始まってもイベントにはならない(`online:` は "line" イベントを待つ)。
598
+ // 名前空間の語(`.class.x` / `.command.x` …)はプロパティか名前空間か曖昧なので受けない
599
+ if (propResult.propSegments[0] === '' && propResult.propSegments.length > 1) {
600
+ const propSegments = propResult.propSegments.slice(1);
601
+ if (propSegments.includes('') || EXPLICIT_PROPERTY_REJECTED_HEADS.has(propSegments[0])) {
602
+ raiseError(`[wcs/binding-syntax] "${propPart}": a leading "." binds an element property by name — ` +
603
+ `write a non-empty property that is not a namespace (${[...EXPLICIT_PROPERTY_REJECTED_HEADS].join(", ")}).${LINT_HINT}`);
604
+ }
605
+ return {
606
+ ...propResult,
607
+ propName: propSegments.join(DELIMITER),
608
+ propSegments,
609
+ ...stateResult,
610
+ bindingType: 'prop',
611
+ };
612
+ }
588
613
  // eventToken.<prop>: <name> は要素 dispatch を state へ流す pub/sub 配線。
589
614
  // 値適用ではないため bindingType 'event' として listener attach 経路に乗せる。
590
615
  if (propResult.propSegments[0] === EVENT_TOKEN_NAMESPACE) {