@wcstack/state 3.2.0 → 3.3.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 +106 -76
- package/README.md +107 -77
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +45 -8
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/manifest.d.ts +28 -5
- package/dist/manifest.esm.js +253 -246
- package/dist/parser.d.ts +16 -2
- package/dist/parser.esm.js +223 -50
- package/dist/split/chunks/binder.js +1 -1
- package/dist/split/chunks/binder.js.map +1 -1
- package/dist/split/chunks/checkDependency.js +1 -1
- package/dist/split/chunks/declarationAliases.js +1 -1
- package/dist/split/chunks/declarationAliases.js.map +1 -1
- package/dist/split/chunks/declarationHooks.js +2 -0
- package/dist/split/chunks/declarationHooks.js.map +1 -0
- package/dist/split/chunks/featureBridge.js +2 -0
- package/dist/split/chunks/featureBridge.js.map +1 -0
- package/dist/split/chunks/filterRegistry.js +1 -1
- package/dist/split/chunks/filterRegistry.js.map +1 -1
- package/dist/split/chunks/getAllPropertyDescriptors.js +1 -1
- package/dist/split/chunks/getAllPropertyDescriptors.js.map +1 -1
- package/dist/split/chunks/lifecycleHooks.js +1 -1
- package/dist/split/chunks/pathDiagnostics.js +1 -1
- package/dist/split/chunks/pathDiagnostics.js.map +1 -1
- package/dist/split/chunks/registerComponents.js +1 -1
- package/dist/split/chunks/registerComponents.js.map +1 -1
- package/dist/split/chunks/setAll.js +1 -1
- package/dist/split/chunks/setAll.js.map +1 -1
- package/dist/split/chunks/version.js +1 -1
- package/dist/split/core.d.ts +10 -2
- package/dist/split/core.js +1 -1
- package/dist/split/core.js.map +1 -1
- package/dist/split/features/devtools.js +1 -1
- package/dist/split/features/devtools.js.map +1 -1
- package/dist/split/features/diagnostics.js +1 -1
- package/dist/split/features/diagnostics.js.map +1 -1
- package/dist/split/features/formats.js +1 -1
- package/dist/split/features/formats.js.map +1 -1
- package/dist/split/features/recursion.js +1 -1
- package/dist/split/features/recursion.js.map +1 -1
- package/dist/split/features/scopes.js +1 -1
- package/dist/split/features/scopes.js.map +1 -1
- package/dist/split/features/ssr.js +1 -1
- package/dist/split/features/ssr.js.map +1 -1
- package/dist/split/features/temporal.js +1 -1
- package/dist/split/features/temporal.js.map +1 -1
- package/dist/wcs-manifest.json +36 -21
- package/package.json +1 -1
- package/dist/split/chunks/mount.js +0 -2
- package/dist/split/chunks/mount.js.map +0 -1
- package/dist/split/chunks/watchRuntime.js +0 -2
- package/dist/split/chunks/watchRuntime.js.map +0 -1
package/dist/manifest.esm.js
CHANGED
|
@@ -6,10 +6,99 @@ const _config = {
|
|
|
6
6
|
// backward compatible export (read-only usage)
|
|
7
7
|
const config = _config;
|
|
8
8
|
|
|
9
|
+
const DELIMITER = '.';
|
|
10
|
+
const WILDCARD = '*';
|
|
11
|
+
const MAX_WILDCARD_DEPTH = 128;
|
|
12
|
+
// data-wcs バインディング構文 `[prop][#mod]: [path][|filter...]` の区切り文字(単一正本・`@state` は v2 で撤去)。
|
|
13
|
+
// これらは「死守の壁(構文契約)」であり値は不変。manifest.syntax.delimiters で公開される。
|
|
14
|
+
const BINDING_SEPARATOR = ';'; // 複数バインディングの区切り
|
|
15
|
+
const PROP_VALUE_SEPARATOR = ':'; // 左辺(prop)と右辺(path)の区切り
|
|
16
|
+
const MODIFIER_SEPARATOR = '#'; // prop と修飾子の区切り
|
|
17
|
+
const FILTER_SEPARATOR = '|'; // フィルタパイプの区切り
|
|
18
|
+
// 修飾子(`#` 後)の語彙(単一正本)。manifest.syntax.modifiers で公開される。
|
|
19
|
+
// フラグ形(`#prevent` — 値を取らない)とキー値形(`#init=element` — `=` で値を取る)。
|
|
20
|
+
// 消費箇所(event/handler・BindingSession・twowayHandler・bindings/initialSync)は
|
|
21
|
+
// この定数を参照する — 文字列リテラルの散在は tooling への収載漏れの温床だった
|
|
22
|
+
// (docs/static-wiring-dx-design.md §2-2)。
|
|
23
|
+
const MODIFIER_PREVENT = 'prevent';
|
|
24
|
+
const MODIFIER_STOP = 'stop';
|
|
25
|
+
const MODIFIER_READONLY = 'ro';
|
|
26
|
+
const MODIFIER_FLAGS = Object.freeze([
|
|
27
|
+
MODIFIER_PREVENT, MODIFIER_STOP, MODIFIER_READONLY,
|
|
28
|
+
]);
|
|
29
|
+
const MODIFIER_KEY_INIT = 'init';
|
|
30
|
+
const MODIFIER_KEY_SYNC = 'sync';
|
|
31
|
+
const MODIFIER_KEYS = Object.freeze([
|
|
32
|
+
MODIFIER_KEY_INIT, MODIFIER_KEY_SYNC,
|
|
33
|
+
]);
|
|
34
|
+
// bindingType 判別と左辺 namespace の語彙(単一正本)。manifest.syntax.bindingTypes で
|
|
35
|
+
// 公開される。パーサ(parseBindTextsForElement)とイベント層はこの定数に分岐する。
|
|
36
|
+
// apply 層のディスパッチマップ(apply/applyChange.ts の applyChangeByFirstSegment)の
|
|
37
|
+
// キー集合との一致は __tests__/manifest.test.ts の drift テストが強制する —
|
|
38
|
+
// manifest エントリ(DOM 非依存)から apply 層を import しないための分離。
|
|
39
|
+
const ELSE_KEYWORD = 'else';
|
|
40
|
+
const SPREAD_PROP = '...';
|
|
41
|
+
const EVENT_PROP_PREFIX = 'on';
|
|
42
|
+
const EVENT_TOKEN_NAMESPACE = 'eventToken';
|
|
43
|
+
const COMMAND_NAMESPACE = 'command';
|
|
44
|
+
const CLASS_NAMESPACE = 'class';
|
|
45
|
+
const ATTR_NAMESPACE = 'attr';
|
|
46
|
+
const STYLE_NAMESPACE = 'style';
|
|
47
|
+
// リストインデックス参照名(`$1`..`$N`)の接頭辞(単一正本)。
|
|
48
|
+
// manifest.syntax.indexParam で公開される。
|
|
49
|
+
const INDEX_PARAM_PREFIX = '$';
|
|
50
|
+
/**
|
|
51
|
+
* stackIndexByIndexName
|
|
52
|
+
* インデックス名からスタックインデックスへのマッピング
|
|
53
|
+
* $1 => 0
|
|
54
|
+
* $2 => 1
|
|
55
|
+
* :
|
|
56
|
+
* ${i + 1} => i
|
|
57
|
+
* i < MAX_WILDCARD_DEPTH
|
|
58
|
+
*/
|
|
59
|
+
const tmpIndexByIndexName = {};
|
|
60
|
+
for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
|
|
61
|
+
tmpIndexByIndexName[`${INDEX_PARAM_PREFIX}${i + 1}`] = i;
|
|
62
|
+
}
|
|
63
|
+
Object.freeze(tmpIndexByIndexName);
|
|
64
|
+
const STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
|
|
65
|
+
const STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
|
|
66
|
+
/** 旧名 `$updatedCallback` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
|
|
67
|
+
const STATE_RENDERED_CALLBACK_NAME = "$renderedCallback";
|
|
68
|
+
const STATE_ERROR_CALLBACK_NAME = "$errorCallback";
|
|
69
|
+
const WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
|
|
70
|
+
const STATE_BINDABLES_NAME = "$bindables";
|
|
71
|
+
const STATE_COMMANDS_NAME = "$commands";
|
|
72
|
+
const STATE_COMMAND_TOKENS_NAME = "$commandTokens";
|
|
73
|
+
const STATE_COMMAND_NAMESPACE_NAME = "$command";
|
|
74
|
+
const STATE_EVENT_TOKENS_NAME = "$eventTokens";
|
|
75
|
+
const STATE_ON_NAME = "$on";
|
|
76
|
+
/** 旧名 `$streams` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
|
|
77
|
+
const STATE_STREAM_NAME = "$stream";
|
|
78
|
+
const STATE_WATCH_NAME = "$watch";
|
|
79
|
+
const STATE_SCAN_NAME = "$scan";
|
|
80
|
+
const STATE_RECURSION_NAME = "$recursion";
|
|
81
|
+
const STATE_LIST_KEYS_NAME = "$listKeys";
|
|
82
|
+
const STATE_STREAM_STATUS_NAMESPACE_NAME = "$streamStatus";
|
|
83
|
+
const STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
|
|
84
|
+
|
|
9
85
|
function raiseError(message) {
|
|
10
86
|
throw new Error(`[@wcstack/state] ${message}`);
|
|
11
87
|
}
|
|
12
88
|
|
|
89
|
+
/**
|
|
90
|
+
* declarationAliases.ts — 宣言キーの旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md V13 / V14)。
|
|
91
|
+
*
|
|
92
|
+
* `$updatedCallback` → `$renderedCallback`、`$streams` → `$stream`。旧名は 3.x の間は受け、4.0 で外す(D4)。
|
|
93
|
+
* state オブジェクトがランタイムに入る入口(State の `_state` と `_loadStateFromSource`、マウント記録)で
|
|
94
|
+
* 1 回だけ正規化し、以降のすべての読み手は正式名だけを見る。両方の綴りを宣言したらどちらが効くのか
|
|
95
|
+
* 書き手に見えないので、名指しで落とす(D38)。
|
|
96
|
+
*/
|
|
97
|
+
const DECLARATION_ALIASES = {
|
|
98
|
+
$updatedCallback: STATE_RENDERED_CALLBACK_NAME,
|
|
99
|
+
$streams: STATE_STREAM_NAME,
|
|
100
|
+
};
|
|
101
|
+
|
|
13
102
|
/**
|
|
14
103
|
* errorMessages.ts
|
|
15
104
|
*
|
|
@@ -23,8 +112,8 @@ function raiseError(message) {
|
|
|
23
112
|
* - optionsRequired: Error when required option is not specified
|
|
24
113
|
* - optionMustBeNumber: Error when option value is not a number
|
|
25
114
|
* - valueMustBeNumber: Error when value is not a number
|
|
26
|
-
* - valueMustBeBoolean: Error when value is not boolean
|
|
27
115
|
* - valueMustBeDate: Error when value is not a Date
|
|
116
|
+
* - valueMustBeArray: Error when value is not an array
|
|
28
117
|
*/
|
|
29
118
|
/**
|
|
30
119
|
* Throws error when filter requires at least one option but none provided.
|
|
@@ -53,15 +142,6 @@ function optionMustBeNumber(fnName) {
|
|
|
53
142
|
function valueMustBeNumber(fnName) {
|
|
54
143
|
raiseError(`filter ${fnName} requires a number value`);
|
|
55
144
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Throws error when filter requires boolean value but non-boolean provided.
|
|
58
|
-
*
|
|
59
|
-
* @param fnName - Name of the filter function
|
|
60
|
-
* @returns Never returns (always throws)
|
|
61
|
-
*/
|
|
62
|
-
function valueMustBeBoolean(fnName) {
|
|
63
|
-
raiseError(`filter ${fnName} requires a boolean value`);
|
|
64
|
-
}
|
|
65
145
|
/**
|
|
66
146
|
* Throws error when filter requires Date value but non-Date provided.
|
|
67
147
|
*
|
|
@@ -123,6 +203,47 @@ function validateNumberString(value) {
|
|
|
123
203
|
}
|
|
124
204
|
return true;
|
|
125
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Reads one numeric option **at construction time** and returns the number itself.
|
|
208
|
+
*
|
|
209
|
+
* Every numeric filter used to write out the same three lines (`options?.[i] ?? optionsRequired`,
|
|
210
|
+
* `validateNumberString`, `optionMustBeNumber`) and then call `Number(opt)` again on **every
|
|
211
|
+
* apply**. Folding it here keeps the option checks in one place and takes the conversion off the
|
|
212
|
+
* hot path.
|
|
213
|
+
*/
|
|
214
|
+
function numberOption(value, fnName) {
|
|
215
|
+
if (!validateNumberString(value)) {
|
|
216
|
+
optionMustBeNumber(fnName);
|
|
217
|
+
}
|
|
218
|
+
return Number(value);
|
|
219
|
+
}
|
|
220
|
+
/** A required numeric option: missing → `optionsRequired`, non-numeric → `optionMustBeNumber`. */
|
|
221
|
+
function requiredNumberOption(options, index, fnName) {
|
|
222
|
+
return numberOption(options?.[index] ?? optionsRequired(fnName), fnName);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Extends the display-side empty-value contract (requirement B8) to the formatting filters.
|
|
226
|
+
*
|
|
227
|
+
* A filter that builds its result with `String(value)` turns `undefined` / `null` into the
|
|
228
|
+
* *characters* `"undefined"` / `"null"`, so `attr.title: x|trim` wrote `title="undefined"` where
|
|
229
|
+
* the unfiltered `attr.title: x` correctly removes the attribute — one filter was enough to undo
|
|
230
|
+
* what B8 established. This family therefore passes an absent value straight through and leaves
|
|
231
|
+
* the decision to the apply side (attribute removed, class cleared, style cleared, text emptied).
|
|
232
|
+
*
|
|
233
|
+
* Deliberately **not** wrapped:
|
|
234
|
+
* - filters for which an absent value *is* the input: `defaults` / `coalesce` / `nullIfEmpty` /
|
|
235
|
+
* `boolean` / `truthy` / `falsy` / `not` / `eq` / `ne`;
|
|
236
|
+
* - the conversions, whose whole job is to convert: `int` / `float` / `number` / `string`;
|
|
237
|
+
* - the filters that reject a value of the wrong type (the number, date and array families).
|
|
238
|
+
* Their throw is confined to the one binding and reported through `$errorCallback`
|
|
239
|
+
* (`apply/applyChangeFromBindings.ts`), which is a different quality of failure from silently
|
|
240
|
+
* painting the word "undefined" into the page.
|
|
241
|
+
*/
|
|
242
|
+
const nullishPassthrough = (factory) => (options) => {
|
|
243
|
+
const filterFn = factory(options);
|
|
244
|
+
// `== null` は null と undefined の両方(意図的な緩い比較)
|
|
245
|
+
return (value) => (value == null ? value : filterFn(value));
|
|
246
|
+
};
|
|
126
247
|
/**
|
|
127
248
|
* Equality filter - compares value with option.
|
|
128
249
|
*
|
|
@@ -136,6 +257,16 @@ const eq = (options, literals) => {
|
|
|
136
257
|
return (value) => {
|
|
137
258
|
// Align types for comparison
|
|
138
259
|
if (typeof value === 'number') {
|
|
260
|
+
// A typed literal that is not a string compares as itself (B9). A number is never equal to
|
|
261
|
+
// `true` / `false` / `null`, so `selectedId|eq(null)` is simply false once the id is a
|
|
262
|
+
// number — it used to throw `optionMustBeNumber` on **every apply**, which broke every
|
|
263
|
+
// binding on a path that is sometimes null and sometimes numeric. Only an unquoted
|
|
264
|
+
// non-number (a bare string such as `eq(abc)`) still reports the type mismatch, and it has
|
|
265
|
+
// to stay inside the closure: `eq` accepts values of any type, so `status|eq(active)` is
|
|
266
|
+
// perfectly valid and cannot be rejected at construction the way `lt` / `add` can.
|
|
267
|
+
if (typeof literal !== 'string') {
|
|
268
|
+
return value === literal;
|
|
269
|
+
}
|
|
139
270
|
if (!validateNumberString(opt)) {
|
|
140
271
|
optionMustBeNumber('eq');
|
|
141
272
|
}
|
|
@@ -160,6 +291,11 @@ const ne = (options, literals) => {
|
|
|
160
291
|
return (value) => {
|
|
161
292
|
// Align types for comparison
|
|
162
293
|
if (typeof value === 'number') {
|
|
294
|
+
// Same as `eq`: a typed `true` / `false` / `null` compares as itself instead of being
|
|
295
|
+
// forced through `Number()` (B9)
|
|
296
|
+
if (typeof literal !== 'string') {
|
|
297
|
+
return value !== literal;
|
|
298
|
+
}
|
|
163
299
|
if (!validateNumberString(opt)) {
|
|
164
300
|
optionMustBeNumber('ne');
|
|
165
301
|
}
|
|
@@ -173,18 +309,22 @@ const ne = (options, literals) => {
|
|
|
173
309
|
};
|
|
174
310
|
};
|
|
175
311
|
/**
|
|
176
|
-
* Boolean NOT filter - inverts
|
|
312
|
+
* Boolean NOT filter - inverts the truthiness of the value.
|
|
313
|
+
*
|
|
314
|
+
* Deliberately the same lenient `!value` as the engine-owned copy in
|
|
315
|
+
* `core/filterRegistry.ts` (`CORE_FILTERS.not`). `if:` / `else:` are built as one
|
|
316
|
+
* `if` parse result plus an engine-injected `not` (`structural/createNotFilter.ts`),
|
|
317
|
+
* and `apply/applyChangeToIf.ts` coerces the `if` side with `Boolean()`. A strict
|
|
318
|
+
* `not` therefore made `else:` throw — and render neither branch — whenever the
|
|
319
|
+
* condition was a falsy non-boolean (`0` / `""` / `undefined` / `null`). Keeping the
|
|
320
|
+
* two implementations identical also means a page behaves the same whether or not
|
|
321
|
+
* `features/formats` is installed (the split core entry registers no `not`).
|
|
177
322
|
*
|
|
178
323
|
* @param options - Unused
|
|
179
|
-
* @returns Filter function that returns inverted
|
|
324
|
+
* @returns Filter function that returns the inverted truthiness
|
|
180
325
|
*/
|
|
181
326
|
const not = (_options) => {
|
|
182
|
-
return (value) =>
|
|
183
|
-
if (typeof value !== 'boolean') {
|
|
184
|
-
valueMustBeBoolean('not');
|
|
185
|
-
}
|
|
186
|
-
return !value;
|
|
187
|
-
};
|
|
327
|
+
return (value) => !value;
|
|
188
328
|
};
|
|
189
329
|
/**
|
|
190
330
|
* Less than filter - checks if value is less than option.
|
|
@@ -193,15 +333,12 @@ const not = (_options) => {
|
|
|
193
333
|
* @returns Filter function that returns boolean
|
|
194
334
|
*/
|
|
195
335
|
const lt = (options) => {
|
|
196
|
-
const opt = options
|
|
197
|
-
if (!validateNumberString(opt)) {
|
|
198
|
-
optionMustBeNumber('lt');
|
|
199
|
-
}
|
|
336
|
+
const opt = requiredNumberOption(options, 0, 'lt');
|
|
200
337
|
return (value) => {
|
|
201
338
|
if (typeof value !== 'number') {
|
|
202
339
|
valueMustBeNumber('lt');
|
|
203
340
|
}
|
|
204
|
-
return value <
|
|
341
|
+
return value < opt;
|
|
205
342
|
};
|
|
206
343
|
};
|
|
207
344
|
/**
|
|
@@ -211,15 +348,12 @@ const lt = (options) => {
|
|
|
211
348
|
* @returns Filter function that returns boolean
|
|
212
349
|
*/
|
|
213
350
|
const le = (options) => {
|
|
214
|
-
const opt = options
|
|
215
|
-
if (!validateNumberString(opt)) {
|
|
216
|
-
optionMustBeNumber('le');
|
|
217
|
-
}
|
|
351
|
+
const opt = requiredNumberOption(options, 0, 'le');
|
|
218
352
|
return (value) => {
|
|
219
353
|
if (typeof value !== 'number') {
|
|
220
354
|
valueMustBeNumber('le');
|
|
221
355
|
}
|
|
222
|
-
return value <=
|
|
356
|
+
return value <= opt;
|
|
223
357
|
};
|
|
224
358
|
};
|
|
225
359
|
/**
|
|
@@ -229,15 +363,12 @@ const le = (options) => {
|
|
|
229
363
|
* @returns Filter function that returns boolean
|
|
230
364
|
*/
|
|
231
365
|
const gt = (options) => {
|
|
232
|
-
const opt = options
|
|
233
|
-
if (!validateNumberString(opt)) {
|
|
234
|
-
optionMustBeNumber('gt');
|
|
235
|
-
}
|
|
366
|
+
const opt = requiredNumberOption(options, 0, 'gt');
|
|
236
367
|
return (value) => {
|
|
237
368
|
if (typeof value !== 'number') {
|
|
238
369
|
valueMustBeNumber('gt');
|
|
239
370
|
}
|
|
240
|
-
return value >
|
|
371
|
+
return value > opt;
|
|
241
372
|
};
|
|
242
373
|
};
|
|
243
374
|
/**
|
|
@@ -247,15 +378,12 @@ const gt = (options) => {
|
|
|
247
378
|
* @returns Filter function that returns boolean
|
|
248
379
|
*/
|
|
249
380
|
const ge = (options) => {
|
|
250
|
-
const opt = options
|
|
251
|
-
if (!validateNumberString(opt)) {
|
|
252
|
-
optionMustBeNumber('ge');
|
|
253
|
-
}
|
|
381
|
+
const opt = requiredNumberOption(options, 0, 'ge');
|
|
254
382
|
return (value) => {
|
|
255
383
|
if (typeof value !== 'number') {
|
|
256
384
|
valueMustBeNumber('ge');
|
|
257
385
|
}
|
|
258
|
-
return value >=
|
|
386
|
+
return value >= opt;
|
|
259
387
|
};
|
|
260
388
|
};
|
|
261
389
|
/**
|
|
@@ -265,15 +393,12 @@ const ge = (options) => {
|
|
|
265
393
|
* @returns Filter function that returns incremented number
|
|
266
394
|
*/
|
|
267
395
|
const inc = (options) => {
|
|
268
|
-
const opt = options
|
|
269
|
-
if (!validateNumberString(opt)) {
|
|
270
|
-
optionMustBeNumber('add');
|
|
271
|
-
}
|
|
396
|
+
const opt = requiredNumberOption(options, 0, 'add');
|
|
272
397
|
return (value) => {
|
|
273
398
|
if (typeof value !== 'number') {
|
|
274
399
|
valueMustBeNumber('add');
|
|
275
400
|
}
|
|
276
|
-
return value +
|
|
401
|
+
return value + opt;
|
|
277
402
|
};
|
|
278
403
|
};
|
|
279
404
|
/**
|
|
@@ -283,15 +408,12 @@ const inc = (options) => {
|
|
|
283
408
|
* @returns Filter function that returns decremented number
|
|
284
409
|
*/
|
|
285
410
|
const dec = (options) => {
|
|
286
|
-
const opt = options
|
|
287
|
-
if (!validateNumberString(opt)) {
|
|
288
|
-
optionMustBeNumber('sub');
|
|
289
|
-
}
|
|
411
|
+
const opt = requiredNumberOption(options, 0, 'sub');
|
|
290
412
|
return (value) => {
|
|
291
413
|
if (typeof value !== 'number') {
|
|
292
414
|
valueMustBeNumber('sub');
|
|
293
415
|
}
|
|
294
|
-
return value -
|
|
416
|
+
return value - opt;
|
|
295
417
|
};
|
|
296
418
|
};
|
|
297
419
|
/**
|
|
@@ -301,15 +423,12 @@ const dec = (options) => {
|
|
|
301
423
|
* @returns Filter function that returns multiplied number
|
|
302
424
|
*/
|
|
303
425
|
const mul = (options) => {
|
|
304
|
-
const opt = options
|
|
305
|
-
if (!validateNumberString(opt)) {
|
|
306
|
-
optionMustBeNumber('mul');
|
|
307
|
-
}
|
|
426
|
+
const opt = requiredNumberOption(options, 0, 'mul');
|
|
308
427
|
return (value) => {
|
|
309
428
|
if (typeof value !== 'number') {
|
|
310
429
|
valueMustBeNumber('mul');
|
|
311
430
|
}
|
|
312
|
-
return value *
|
|
431
|
+
return value * opt;
|
|
313
432
|
};
|
|
314
433
|
};
|
|
315
434
|
/**
|
|
@@ -319,15 +438,12 @@ const mul = (options) => {
|
|
|
319
438
|
* @returns Filter function that returns divided number
|
|
320
439
|
*/
|
|
321
440
|
const div = (options) => {
|
|
322
|
-
const opt = options
|
|
323
|
-
if (!validateNumberString(opt)) {
|
|
324
|
-
optionMustBeNumber('div');
|
|
325
|
-
}
|
|
441
|
+
const opt = requiredNumberOption(options, 0, 'div');
|
|
326
442
|
return (value) => {
|
|
327
443
|
if (typeof value !== 'number') {
|
|
328
444
|
valueMustBeNumber('div');
|
|
329
445
|
}
|
|
330
|
-
return value /
|
|
446
|
+
return value / opt;
|
|
331
447
|
};
|
|
332
448
|
};
|
|
333
449
|
/**
|
|
@@ -337,15 +453,12 @@ const div = (options) => {
|
|
|
337
453
|
* @returns Filter function that returns remainder
|
|
338
454
|
*/
|
|
339
455
|
const mod = (options) => {
|
|
340
|
-
const opt = options
|
|
341
|
-
if (!validateNumberString(opt)) {
|
|
342
|
-
optionMustBeNumber('mod');
|
|
343
|
-
}
|
|
456
|
+
const opt = requiredNumberOption(options, 0, 'mod');
|
|
344
457
|
return (value) => {
|
|
345
458
|
if (typeof value !== 'number') {
|
|
346
459
|
valueMustBeNumber('mod');
|
|
347
460
|
}
|
|
348
|
-
return value %
|
|
461
|
+
return value % opt;
|
|
349
462
|
};
|
|
350
463
|
};
|
|
351
464
|
/**
|
|
@@ -373,16 +486,8 @@ const abs = (_options) => {
|
|
|
373
486
|
* @returns Filter function that returns the clamped number
|
|
374
487
|
*/
|
|
375
488
|
const clamp = (options) => {
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
optionMustBeNumber('clamp');
|
|
379
|
-
}
|
|
380
|
-
const opt2 = options?.[1] ?? optionsRequired('clamp');
|
|
381
|
-
if (!validateNumberString(opt2)) {
|
|
382
|
-
optionMustBeNumber('clamp');
|
|
383
|
-
}
|
|
384
|
-
const min = Number(opt1);
|
|
385
|
-
const max = Number(opt2);
|
|
489
|
+
const min = requiredNumberOption(options, 0, 'clamp');
|
|
490
|
+
const max = requiredNumberOption(options, 1, 'clamp');
|
|
386
491
|
return (value) => {
|
|
387
492
|
if (typeof value !== 'number') {
|
|
388
493
|
valueMustBeNumber('clamp');
|
|
@@ -397,15 +502,12 @@ const clamp = (options) => {
|
|
|
397
502
|
* @returns Filter function that returns formatted string
|
|
398
503
|
*/
|
|
399
504
|
const fix = (options) => {
|
|
400
|
-
const opt = options?.[0] ?? "0";
|
|
401
|
-
if (!validateNumberString(opt)) {
|
|
402
|
-
optionMustBeNumber('toFixed');
|
|
403
|
-
}
|
|
505
|
+
const opt = numberOption(options?.[0] ?? "0", 'toFixed');
|
|
404
506
|
return (value) => {
|
|
405
507
|
if (typeof value !== 'number') {
|
|
406
508
|
valueMustBeNumber('toFixed');
|
|
407
509
|
}
|
|
408
|
-
return value.toFixed(
|
|
510
|
+
return value.toFixed(opt);
|
|
409
511
|
};
|
|
410
512
|
};
|
|
411
513
|
/**
|
|
@@ -498,18 +600,10 @@ const trim = (_options) => {
|
|
|
498
600
|
* @returns Filter function that returns sliced string
|
|
499
601
|
*/
|
|
500
602
|
const slice = (options) => {
|
|
501
|
-
const numberedOpts = [];
|
|
502
|
-
const opt1 = options?.[0] ?? optionsRequired('slice');
|
|
503
|
-
if (!validateNumberString(opt1)) {
|
|
504
|
-
optionMustBeNumber('slice');
|
|
505
|
-
}
|
|
506
|
-
numberedOpts.push(Number(opt1));
|
|
603
|
+
const numberedOpts = [requiredNumberOption(options, 0, 'slice')];
|
|
507
604
|
const opt2 = options?.[1];
|
|
508
605
|
if (typeof opt2 !== 'undefined') {
|
|
509
|
-
|
|
510
|
-
optionMustBeNumber('slice');
|
|
511
|
-
}
|
|
512
|
-
numberedOpts.push(Number(opt2));
|
|
606
|
+
numberedOpts.push(numberOption(opt2, 'slice'));
|
|
513
607
|
}
|
|
514
608
|
return (value) => {
|
|
515
609
|
return String(value).slice(...numberedOpts);
|
|
@@ -522,16 +616,10 @@ const slice = (options) => {
|
|
|
522
616
|
* @returns Filter function that returns substring
|
|
523
617
|
*/
|
|
524
618
|
const substr = (options) => {
|
|
525
|
-
const opt1 = options
|
|
526
|
-
|
|
527
|
-
optionMustBeNumber('substr');
|
|
528
|
-
}
|
|
529
|
-
const opt2 = options?.[1] ?? optionsRequired('substr');
|
|
530
|
-
if (!validateNumberString(opt2)) {
|
|
531
|
-
optionMustBeNumber('substr');
|
|
532
|
-
}
|
|
619
|
+
const opt1 = requiredNumberOption(options, 0, 'substr');
|
|
620
|
+
const opt2 = requiredNumberOption(options, 1, 'substr');
|
|
533
621
|
return (value) => {
|
|
534
|
-
return String(value).substr(
|
|
622
|
+
return String(value).substr(opt1, opt2);
|
|
535
623
|
};
|
|
536
624
|
};
|
|
537
625
|
/**
|
|
@@ -541,13 +629,10 @@ const substr = (options) => {
|
|
|
541
629
|
* @returns Filter function that returns padded string
|
|
542
630
|
*/
|
|
543
631
|
const pad = (options) => {
|
|
544
|
-
const opt1 = options
|
|
545
|
-
if (!validateNumberString(opt1)) {
|
|
546
|
-
optionMustBeNumber('padStart');
|
|
547
|
-
}
|
|
632
|
+
const opt1 = requiredNumberOption(options, 0, 'padStart');
|
|
548
633
|
const opt2 = options?.[1] ?? '0';
|
|
549
634
|
return (value) => {
|
|
550
|
-
return String(value).padStart(
|
|
635
|
+
return String(value).padStart(opt1, opt2);
|
|
551
636
|
};
|
|
552
637
|
};
|
|
553
638
|
/**
|
|
@@ -557,13 +642,10 @@ const pad = (options) => {
|
|
|
557
642
|
* @returns Filter function that returns padded string
|
|
558
643
|
*/
|
|
559
644
|
const padEnd = (options) => {
|
|
560
|
-
const opt1 = options
|
|
561
|
-
if (!validateNumberString(opt1)) {
|
|
562
|
-
optionMustBeNumber('padEnd');
|
|
563
|
-
}
|
|
645
|
+
const opt1 = requiredNumberOption(options, 0, 'padEnd');
|
|
564
646
|
const opt2 = options?.[1] ?? ' ';
|
|
565
647
|
return (value) => {
|
|
566
|
-
return String(value).padEnd(
|
|
648
|
+
return String(value).padEnd(opt1, opt2);
|
|
567
649
|
};
|
|
568
650
|
};
|
|
569
651
|
/**
|
|
@@ -573,12 +655,9 @@ const padEnd = (options) => {
|
|
|
573
655
|
* @returns Filter function that returns repeated string
|
|
574
656
|
*/
|
|
575
657
|
const rep = (options) => {
|
|
576
|
-
const opt = options
|
|
577
|
-
if (!validateNumberString(opt)) {
|
|
578
|
-
optionMustBeNumber('repeat');
|
|
579
|
-
}
|
|
658
|
+
const opt = requiredNumberOption(options, 0, 'repeat');
|
|
580
659
|
return (value) => {
|
|
581
|
-
return String(value).repeat(
|
|
660
|
+
return String(value).repeat(opt);
|
|
582
661
|
};
|
|
583
662
|
};
|
|
584
663
|
/**
|
|
@@ -621,15 +700,11 @@ const float = (_options) => {
|
|
|
621
700
|
* @returns Filter function that returns rounded number
|
|
622
701
|
*/
|
|
623
702
|
const round = (options) => {
|
|
624
|
-
const
|
|
625
|
-
if (!validateNumberString(opt)) {
|
|
626
|
-
optionMustBeNumber('round');
|
|
627
|
-
}
|
|
703
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'round'));
|
|
628
704
|
return (value) => {
|
|
629
705
|
if (typeof value !== 'number') {
|
|
630
706
|
valueMustBeNumber('round');
|
|
631
707
|
}
|
|
632
|
-
const optValue = Math.pow(10, Number(opt));
|
|
633
708
|
return Math.round(value * optValue) / optValue;
|
|
634
709
|
};
|
|
635
710
|
};
|
|
@@ -640,15 +715,11 @@ const round = (options) => {
|
|
|
640
715
|
* @returns Filter function that returns floored number
|
|
641
716
|
*/
|
|
642
717
|
const floor = (options) => {
|
|
643
|
-
const
|
|
644
|
-
if (!validateNumberString(opt)) {
|
|
645
|
-
optionMustBeNumber('floor');
|
|
646
|
-
}
|
|
718
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'floor'));
|
|
647
719
|
return (value) => {
|
|
648
720
|
if (typeof value !== 'number') {
|
|
649
721
|
valueMustBeNumber('floor');
|
|
650
722
|
}
|
|
651
|
-
const optValue = Math.pow(10, Number(opt));
|
|
652
723
|
return Math.floor(value * optValue) / optValue;
|
|
653
724
|
};
|
|
654
725
|
};
|
|
@@ -659,15 +730,11 @@ const floor = (options) => {
|
|
|
659
730
|
* @returns Filter function that returns ceiled number
|
|
660
731
|
*/
|
|
661
732
|
const ceil = (options) => {
|
|
662
|
-
const
|
|
663
|
-
if (!validateNumberString(opt)) {
|
|
664
|
-
optionMustBeNumber('ceil');
|
|
665
|
-
}
|
|
733
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'ceil'));
|
|
666
734
|
return (value) => {
|
|
667
735
|
if (typeof value !== 'number') {
|
|
668
736
|
valueMustBeNumber('ceil');
|
|
669
737
|
}
|
|
670
|
-
const optValue = Math.pow(10, Number(opt));
|
|
671
738
|
return Math.ceil(value * optValue) / optValue;
|
|
672
739
|
};
|
|
673
740
|
};
|
|
@@ -678,15 +745,12 @@ const ceil = (options) => {
|
|
|
678
745
|
* @returns Filter function that returns percentage string with '%'
|
|
679
746
|
*/
|
|
680
747
|
const percent = (options) => {
|
|
681
|
-
const opt = options?.[0] ?? '0';
|
|
682
|
-
if (!validateNumberString(opt)) {
|
|
683
|
-
optionMustBeNumber('percent');
|
|
684
|
-
}
|
|
748
|
+
const opt = numberOption(options?.[0] ?? '0', 'percent');
|
|
685
749
|
return (value) => {
|
|
686
750
|
if (typeof value !== 'number') {
|
|
687
751
|
valueMustBeNumber('percent');
|
|
688
752
|
}
|
|
689
|
-
return `${(value * 100).toFixed(
|
|
753
|
+
return `${(value * 100).toFixed(opt)}%`;
|
|
690
754
|
};
|
|
691
755
|
};
|
|
692
756
|
/**
|
|
@@ -701,8 +765,10 @@ const percent = (options) => {
|
|
|
701
765
|
* `fix` / `percent`, which already return strings. Rejecting non-numbers here would
|
|
702
766
|
* break exactly the combination this filter exists for.
|
|
703
767
|
*
|
|
704
|
-
* `null` / `undefined` pass through untouched rather than becoming `"undefinedpx"`,
|
|
705
|
-
*
|
|
768
|
+
* `null` / `undefined` pass through untouched rather than becoming `"undefinedpx"`, so the
|
|
769
|
+
* binding layer's "undefined skips the write, null clears" semantics survive. That guard is no
|
|
770
|
+
* longer written here: it is `nullishPassthrough`, shared with the rest of the `String(value)`
|
|
771
|
+
* family, which used to paint `"undefined"` into the page for exactly the same reason.
|
|
706
772
|
*
|
|
707
773
|
* @param options - Array with the unit/suffix as first element (required)
|
|
708
774
|
* @returns Filter function that returns the value with the unit appended
|
|
@@ -710,9 +776,6 @@ const percent = (options) => {
|
|
|
710
776
|
const unit = (options) => {
|
|
711
777
|
const opt = options?.[0] ?? optionsRequired('unit');
|
|
712
778
|
return (value) => {
|
|
713
|
-
if (value === null || typeof value === 'undefined') {
|
|
714
|
-
return value;
|
|
715
|
-
}
|
|
716
779
|
return String(value) + opt;
|
|
717
780
|
};
|
|
718
781
|
};
|
|
@@ -745,11 +808,7 @@ const join = (options) => {
|
|
|
745
808
|
* @returns Filter function that returns the truncated string
|
|
746
809
|
*/
|
|
747
810
|
const truncate = (options) => {
|
|
748
|
-
const
|
|
749
|
-
if (!validateNumberString(opt1)) {
|
|
750
|
-
optionMustBeNumber('truncate');
|
|
751
|
-
}
|
|
752
|
-
const maxLength = Number(opt1);
|
|
811
|
+
const maxLength = requiredNumberOption(options, 0, 'truncate');
|
|
753
812
|
const suffix = options?.[1] ?? '…';
|
|
754
813
|
return (value) => {
|
|
755
814
|
const v = String(value);
|
|
@@ -956,17 +1015,18 @@ const builtinFilters = {
|
|
|
956
1015
|
"clamp": clamp,
|
|
957
1016
|
"toFixed": fix,
|
|
958
1017
|
"locale": locale,
|
|
959
|
-
|
|
960
|
-
"
|
|
961
|
-
"
|
|
962
|
-
"
|
|
963
|
-
"
|
|
964
|
-
"
|
|
965
|
-
"
|
|
966
|
-
"
|
|
967
|
-
"
|
|
968
|
-
"
|
|
969
|
-
"
|
|
1018
|
+
// `String(value)` で組み立てる族は、値が無いときに素通しする(要件 B8 — nullishPassthrough を参照)
|
|
1019
|
+
"upper": nullishPassthrough(uc),
|
|
1020
|
+
"lower": nullishPassthrough(lc),
|
|
1021
|
+
"capitalize": nullishPassthrough(cap),
|
|
1022
|
+
"trim": nullishPassthrough(trim),
|
|
1023
|
+
"slice": nullishPassthrough(slice),
|
|
1024
|
+
"substr": nullishPassthrough(substr),
|
|
1025
|
+
"padStart": nullishPassthrough(pad),
|
|
1026
|
+
"padEnd": nullishPassthrough(padEnd),
|
|
1027
|
+
"repeat": nullishPassthrough(rep),
|
|
1028
|
+
"reverse": nullishPassthrough(rev),
|
|
1029
|
+
"truncate": nullishPassthrough(truncate),
|
|
970
1030
|
"join": join,
|
|
971
1031
|
"int": int,
|
|
972
1032
|
"float": float,
|
|
@@ -974,7 +1034,7 @@ const builtinFilters = {
|
|
|
974
1034
|
"floor": floor,
|
|
975
1035
|
"ceil": ceil,
|
|
976
1036
|
"percent": percent,
|
|
977
|
-
"unit": unit,
|
|
1037
|
+
"unit": nullishPassthrough(unit),
|
|
978
1038
|
"date": date,
|
|
979
1039
|
"time": time,
|
|
980
1040
|
"datetime": datetime,
|
|
@@ -1007,7 +1067,9 @@ const builtinFilterMeta = {
|
|
|
1007
1067
|
// 比較・論理
|
|
1008
1068
|
eq: { description: "等しいか比較", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
1009
1069
|
ne: { description: "異なるか比較", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
1010
|
-
not
|
|
1070
|
+
// `not` は真偽性(truthy / falsy)の反転。`if:` が `Boolean()` で寄せるのと同じ規則で、
|
|
1071
|
+
// `else:` はこのフィルタを足した束縛として組み立てられる(structural/createNotFilter.ts)
|
|
1072
|
+
not: { description: "真偽性を反転(falsy → true)", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1011
1073
|
lt: { description: "より小さいか", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
1012
1074
|
le: { description: "以下か", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
1013
1075
|
gt: { description: "より大きいか", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
@@ -1029,13 +1091,15 @@ const builtinFilterMeta = {
|
|
|
1029
1091
|
capitalize: { description: "先頭文字を大文字に", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1030
1092
|
trim: { description: "前後の空白を削除", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1031
1093
|
slice: { description: "部分文字列 (start[,end])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1094
|
+
// 長さは省略できない(実装が両方読む)。minArgs: 1 だった頃は補完・lint が `substr(0)` を
|
|
1095
|
+
// 通し、実行時にだけ落ちていた
|
|
1096
|
+
substr: { description: "部分文字列 (pos,len)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 2, maxArgs: 2, argTypes: ["number", "number"] },
|
|
1097
|
+
padStart: { description: "先頭を埋める (length[,char]。char の既定は 0 — JS の既定は空白なので注意)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
1098
|
+
padEnd: { description: "末尾を埋める (length[,char]。char の既定は空白 — JS と同じ)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
1035
1099
|
repeat: { description: "繰り返し (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
1036
1100
|
reverse: { description: "文字順を反転", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1037
|
-
truncate: { description: "切り詰めて省略記号 (length[,suffix])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
1038
|
-
join: { description: "配列を連結 ([separator])", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1101
|
+
truncate: { description: "切り詰めて省略記号 (length[,suffix]。suffix の既定は … — U+2026 の 1 文字)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
1102
|
+
join: { description: "配列を連結 ([separator]。既定はカンマ + 空白)", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1039
1103
|
// 数値パース・丸め
|
|
1040
1104
|
int: { description: "整数にパース", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
1041
1105
|
float: { description: "浮動小数点数にパース", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
@@ -1047,11 +1111,13 @@ const builtinFilterMeta = {
|
|
|
1047
1111
|
// それらは既に string を返すため(builtinFilters.ts の unit を参照)
|
|
1048
1112
|
unit: { description: "単位(接尾辞)を付加", hasArgs: true, resultType: "string", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["string"] },
|
|
1049
1113
|
// 日付・時刻
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1114
|
+
// `locale` と同じくロケールを 1 つ受ける(`date(ja-JP)`)。実装は最初からこれを読んでいたが、
|
|
1115
|
+
// メタデータ側が maxArgs: 0 だったため lint と補完が正しい書き方を誤りとして報告していた
|
|
1116
|
+
date: { description: "ロケール形式の日付 ([locale]。既定は config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1117
|
+
time: { description: "ロケール形式の時刻 ([locale]。既定は config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1118
|
+
datetime: { description: "ロケール形式の日時 ([locale]。既定は config.locale)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1119
|
+
ymd: { description: "YYYY-MM-DD 形式 ([separator]。既定は -)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1120
|
+
hms: { description: "HH:MM:SS 形式 ([separator]。既定は :)", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
1055
1121
|
// 真偽値・変換
|
|
1056
1122
|
falsy: { description: "偽値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1057
1123
|
truthy: { description: "真値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
@@ -1070,82 +1136,6 @@ const STRUCTURAL_BINDING_TYPE_SET = new Set([
|
|
|
1070
1136
|
"for",
|
|
1071
1137
|
]);
|
|
1072
1138
|
|
|
1073
|
-
const DELIMITER = '.';
|
|
1074
|
-
const WILDCARD = '*';
|
|
1075
|
-
const MAX_WILDCARD_DEPTH = 128;
|
|
1076
|
-
// data-wcs バインディング構文 `[prop][#mod]: [path][|filter...]` の区切り文字(単一正本・`@state` は v2 で撤去)。
|
|
1077
|
-
// これらは「死守の壁(構文契約)」であり値は不変。manifest.syntax.delimiters で公開される。
|
|
1078
|
-
const BINDING_SEPARATOR = ';'; // 複数バインディングの区切り
|
|
1079
|
-
const PROP_VALUE_SEPARATOR = ':'; // 左辺(prop)と右辺(path)の区切り
|
|
1080
|
-
const MODIFIER_SEPARATOR = '#'; // prop と修飾子の区切り
|
|
1081
|
-
const FILTER_SEPARATOR = '|'; // フィルタパイプの区切り
|
|
1082
|
-
// 修飾子(`#` 後)の語彙(単一正本)。manifest.syntax.modifiers で公開される。
|
|
1083
|
-
// フラグ形(`#prevent` — 値を取らない)とキー値形(`#init=element` — `=` で値を取る)。
|
|
1084
|
-
// 消費箇所(event/handler・BindingSession・twowayHandler・bindings/initialSync)は
|
|
1085
|
-
// この定数を参照する — 文字列リテラルの散在は tooling への収載漏れの温床だった
|
|
1086
|
-
// (docs/static-wiring-dx-design.md §2-2)。
|
|
1087
|
-
const MODIFIER_PREVENT = 'prevent';
|
|
1088
|
-
const MODIFIER_STOP = 'stop';
|
|
1089
|
-
const MODIFIER_READONLY = 'ro';
|
|
1090
|
-
const MODIFIER_FLAGS = Object.freeze([
|
|
1091
|
-
MODIFIER_PREVENT, MODIFIER_STOP, MODIFIER_READONLY,
|
|
1092
|
-
]);
|
|
1093
|
-
const MODIFIER_KEY_INIT = 'init';
|
|
1094
|
-
const MODIFIER_KEY_SYNC = 'sync';
|
|
1095
|
-
const MODIFIER_KEYS = Object.freeze([
|
|
1096
|
-
MODIFIER_KEY_INIT, MODIFIER_KEY_SYNC,
|
|
1097
|
-
]);
|
|
1098
|
-
// bindingType 判別と左辺 namespace の語彙(単一正本)。manifest.syntax.bindingTypes で
|
|
1099
|
-
// 公開される。パーサ(parseBindTextsForElement)とイベント層はこの定数に分岐する。
|
|
1100
|
-
// apply 層のディスパッチマップ(apply/applyChange.ts の applyChangeByFirstSegment)の
|
|
1101
|
-
// キー集合との一致は __tests__/manifest.test.ts の drift テストが強制する —
|
|
1102
|
-
// manifest エントリ(DOM 非依存)から apply 層を import しないための分離。
|
|
1103
|
-
const ELSE_KEYWORD = 'else';
|
|
1104
|
-
const SPREAD_PROP = '...';
|
|
1105
|
-
const EVENT_PROP_PREFIX = 'on';
|
|
1106
|
-
const EVENT_TOKEN_NAMESPACE = 'eventToken';
|
|
1107
|
-
const COMMAND_NAMESPACE = 'command';
|
|
1108
|
-
const CLASS_NAMESPACE = 'class';
|
|
1109
|
-
const ATTR_NAMESPACE = 'attr';
|
|
1110
|
-
const STYLE_NAMESPACE = 'style';
|
|
1111
|
-
// リストインデックス参照名(`$1`..`$N`)の接頭辞(単一正本)。
|
|
1112
|
-
// manifest.syntax.indexParam で公開される。
|
|
1113
|
-
const INDEX_PARAM_PREFIX = '$';
|
|
1114
|
-
/**
|
|
1115
|
-
* stackIndexByIndexName
|
|
1116
|
-
* インデックス名からスタックインデックスへのマッピング
|
|
1117
|
-
* $1 => 0
|
|
1118
|
-
* $2 => 1
|
|
1119
|
-
* :
|
|
1120
|
-
* ${i + 1} => i
|
|
1121
|
-
* i < MAX_WILDCARD_DEPTH
|
|
1122
|
-
*/
|
|
1123
|
-
const tmpIndexByIndexName = {};
|
|
1124
|
-
for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
|
|
1125
|
-
tmpIndexByIndexName[`${INDEX_PARAM_PREFIX}${i + 1}`] = i;
|
|
1126
|
-
}
|
|
1127
|
-
Object.freeze(tmpIndexByIndexName);
|
|
1128
|
-
const STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
|
|
1129
|
-
const STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
|
|
1130
|
-
/** 旧名 `$updatedCallback` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
|
|
1131
|
-
const STATE_UPDATED_CALLBACK_NAME = "$renderedCallback";
|
|
1132
|
-
const STATE_ERROR_CALLBACK_NAME = "$errorCallback";
|
|
1133
|
-
const WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
|
|
1134
|
-
const STATE_BINDABLES_NAME = "$bindables";
|
|
1135
|
-
const STATE_COMMANDS_NAME = "$commands";
|
|
1136
|
-
const STATE_COMMAND_TOKENS_NAME = "$commandTokens";
|
|
1137
|
-
const STATE_COMMAND_NAMESPACE_NAME = "$command";
|
|
1138
|
-
const STATE_EVENT_TOKENS_NAME = "$eventTokens";
|
|
1139
|
-
const STATE_ON_NAME = "$on";
|
|
1140
|
-
/** 旧名 `$streams` は 3.x の間のエイリアス(要件 B12・declarationAliases.ts) */
|
|
1141
|
-
const STATE_STREAMS_NAME = "$stream";
|
|
1142
|
-
const STATE_WATCH_NAME = "$watch";
|
|
1143
|
-
const STATE_SCAN_NAME = "$scan";
|
|
1144
|
-
const STATE_RECURSION_NAME = "$recursion";
|
|
1145
|
-
const STATE_LIST_KEYS_NAME = "$listKeys";
|
|
1146
|
-
const STATE_STREAM_STATUS_NAMESPACE_NAME = "$streamStatus";
|
|
1147
|
-
const STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
|
|
1148
|
-
|
|
1149
1139
|
/**
|
|
1150
1140
|
* manifest.ts — `<wcs-state>` の構文・フィルタ・予約名を機械可読な単一正本として公開する。
|
|
1151
1141
|
*
|
|
@@ -1159,8 +1149,23 @@ const STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
|
|
|
1159
1149
|
* - 将来 `dist/wcs-manifest.json` としてビルド時に書き出し、vscode-wcs がそれを読む形に発展させる。
|
|
1160
1150
|
* - ドリフト検出テスト(__tests__/manifest.test.ts)が、フィルタ集合の golden と実装の一致を CI で保証する。
|
|
1161
1151
|
*/
|
|
1162
|
-
/**
|
|
1163
|
-
|
|
1152
|
+
/**
|
|
1153
|
+
* マニフェストのバージョン(構造を変えたら上げる)。
|
|
1154
|
+
*
|
|
1155
|
+
* 3.1 で `syntax.bindingTypes.explicitPropertyPrefix`、3.2 で `filterAliases`、
|
|
1156
|
+
* 3.x の次で `declarationAliases` / `apiAliases` を足したので 2。
|
|
1157
|
+
* 消費側(vscode-wcs)はまだこの定数を参照していないが、公開している以上ドリフトさせない。
|
|
1158
|
+
*/
|
|
1159
|
+
const WCS_MANIFEST_VERSION = 2;
|
|
1160
|
+
/**
|
|
1161
|
+
* state API の旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md)。正本は
|
|
1162
|
+
* `proxy/traps/get.ts` の case ラベルで、ここはそれを機械可読にした写し
|
|
1163
|
+
* (一致は `__tests__/manifest.test.ts` が固定する)。
|
|
1164
|
+
*/
|
|
1165
|
+
const STATE_API_ALIASES = Object.freeze({
|
|
1166
|
+
$trackDependency: "$dependOn",
|
|
1167
|
+
$untrackDependency: "$untracked",
|
|
1168
|
+
});
|
|
1164
1169
|
/** 機械可読な単一正本を返す。vscode-wcs はこれを消費する想定。 */
|
|
1165
1170
|
function getWcsManifest() {
|
|
1166
1171
|
return {
|
|
@@ -1205,10 +1210,12 @@ function getWcsManifest() {
|
|
|
1205
1210
|
filters: Object.keys(outputBuiltinFilters),
|
|
1206
1211
|
filterMeta: builtinFilterMeta,
|
|
1207
1212
|
filterAliases: builtinFilterAliases,
|
|
1213
|
+
declarationAliases: DECLARATION_ALIASES,
|
|
1214
|
+
apiAliases: STATE_API_ALIASES,
|
|
1208
1215
|
reservedLifecycle: [
|
|
1209
1216
|
STATE_CONNECTED_CALLBACK_NAME,
|
|
1210
1217
|
STATE_DISCONNECTED_CALLBACK_NAME,
|
|
1211
|
-
|
|
1218
|
+
STATE_RENDERED_CALLBACK_NAME,
|
|
1212
1219
|
STATE_ERROR_CALLBACK_NAME,
|
|
1213
1220
|
WEBCOMPONENT_STATE_READY_CALLBACK_NAME,
|
|
1214
1221
|
],
|
|
@@ -1219,7 +1226,7 @@ function getWcsManifest() {
|
|
|
1219
1226
|
STATE_COMMAND_NAMESPACE_NAME,
|
|
1220
1227
|
STATE_EVENT_TOKENS_NAME,
|
|
1221
1228
|
STATE_ON_NAME,
|
|
1222
|
-
|
|
1229
|
+
STATE_STREAM_NAME,
|
|
1223
1230
|
STATE_WATCH_NAME,
|
|
1224
1231
|
STATE_SCAN_NAME,
|
|
1225
1232
|
STATE_LIST_KEYS_NAME,
|
|
@@ -1230,4 +1237,4 @@ function getWcsManifest() {
|
|
|
1230
1237
|
};
|
|
1231
1238
|
}
|
|
1232
1239
|
|
|
1233
|
-
export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterAliases, builtinFilterMeta, getWcsManifest };
|
|
1240
|
+
export { DECLARATION_ALIASES, STATE_API_ALIASES, STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterAliases, builtinFilterMeta, getWcsManifest };
|