@wcstack/state 3.1.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 +128 -90
- package/README.md +129 -91
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/define.d.ts +11 -7
- package/dist/index.d.ts +58 -15
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/manifest.d.ts +39 -5
- package/dist/manifest.esm.js +315 -253
- 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 +2 -0
- package/dist/split/chunks/declarationAliases.js.map +1 -0
- 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 +21 -9
- 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 +98 -44
- 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
|
*
|
|
@@ -81,6 +161,26 @@ function valueMustBeArray(fnName) {
|
|
|
81
161
|
raiseError(`filter ${fnName} requires an array value`);
|
|
82
162
|
}
|
|
83
163
|
|
|
164
|
+
/**
|
|
165
|
+
* filters/filterAliases.ts — 組み込みフィルタの旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md V1〜V9)。
|
|
166
|
+
*
|
|
167
|
+
* 旧名は 3.x の間エイリアスとして残り、4.0 で外す(D4)。解決は登録簿(core/filterRegistry)が行い、
|
|
168
|
+
* 実装・引数の個数・メタデータは正式名だけが持つ。formats の install と manifest(tooling)の両方が読むので、
|
|
169
|
+
* 実装にもメタデータにも依存しない小さな表として独立させている。
|
|
170
|
+
*/
|
|
171
|
+
const builtinFilterAliases = {
|
|
172
|
+
inc: "add",
|
|
173
|
+
dec: "sub",
|
|
174
|
+
fix: "toFixed",
|
|
175
|
+
uc: "upper",
|
|
176
|
+
lc: "lower",
|
|
177
|
+
cap: "capitalize",
|
|
178
|
+
rep: "repeat",
|
|
179
|
+
rev: "reverse",
|
|
180
|
+
pad: "padStart",
|
|
181
|
+
null: "nullIfEmpty",
|
|
182
|
+
};
|
|
183
|
+
|
|
84
184
|
/**
|
|
85
185
|
* builtinFilters.ts
|
|
86
186
|
*
|
|
@@ -103,6 +203,47 @@ function validateNumberString(value) {
|
|
|
103
203
|
}
|
|
104
204
|
return true;
|
|
105
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
|
+
};
|
|
106
247
|
/**
|
|
107
248
|
* Equality filter - compares value with option.
|
|
108
249
|
*
|
|
@@ -116,6 +257,16 @@ const eq = (options, literals) => {
|
|
|
116
257
|
return (value) => {
|
|
117
258
|
// Align types for comparison
|
|
118
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
|
+
}
|
|
119
270
|
if (!validateNumberString(opt)) {
|
|
120
271
|
optionMustBeNumber('eq');
|
|
121
272
|
}
|
|
@@ -140,6 +291,11 @@ const ne = (options, literals) => {
|
|
|
140
291
|
return (value) => {
|
|
141
292
|
// Align types for comparison
|
|
142
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
|
+
}
|
|
143
299
|
if (!validateNumberString(opt)) {
|
|
144
300
|
optionMustBeNumber('ne');
|
|
145
301
|
}
|
|
@@ -153,18 +309,22 @@ const ne = (options, literals) => {
|
|
|
153
309
|
};
|
|
154
310
|
};
|
|
155
311
|
/**
|
|
156
|
-
* 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`).
|
|
157
322
|
*
|
|
158
323
|
* @param options - Unused
|
|
159
|
-
* @returns Filter function that returns inverted
|
|
324
|
+
* @returns Filter function that returns the inverted truthiness
|
|
160
325
|
*/
|
|
161
326
|
const not = (_options) => {
|
|
162
|
-
return (value) =>
|
|
163
|
-
if (typeof value !== 'boolean') {
|
|
164
|
-
valueMustBeBoolean('not');
|
|
165
|
-
}
|
|
166
|
-
return !value;
|
|
167
|
-
};
|
|
327
|
+
return (value) => !value;
|
|
168
328
|
};
|
|
169
329
|
/**
|
|
170
330
|
* Less than filter - checks if value is less than option.
|
|
@@ -173,15 +333,12 @@ const not = (_options) => {
|
|
|
173
333
|
* @returns Filter function that returns boolean
|
|
174
334
|
*/
|
|
175
335
|
const lt = (options) => {
|
|
176
|
-
const opt = options
|
|
177
|
-
if (!validateNumberString(opt)) {
|
|
178
|
-
optionMustBeNumber('lt');
|
|
179
|
-
}
|
|
336
|
+
const opt = requiredNumberOption(options, 0, 'lt');
|
|
180
337
|
return (value) => {
|
|
181
338
|
if (typeof value !== 'number') {
|
|
182
339
|
valueMustBeNumber('lt');
|
|
183
340
|
}
|
|
184
|
-
return value <
|
|
341
|
+
return value < opt;
|
|
185
342
|
};
|
|
186
343
|
};
|
|
187
344
|
/**
|
|
@@ -191,15 +348,12 @@ const lt = (options) => {
|
|
|
191
348
|
* @returns Filter function that returns boolean
|
|
192
349
|
*/
|
|
193
350
|
const le = (options) => {
|
|
194
|
-
const opt = options
|
|
195
|
-
if (!validateNumberString(opt)) {
|
|
196
|
-
optionMustBeNumber('le');
|
|
197
|
-
}
|
|
351
|
+
const opt = requiredNumberOption(options, 0, 'le');
|
|
198
352
|
return (value) => {
|
|
199
353
|
if (typeof value !== 'number') {
|
|
200
354
|
valueMustBeNumber('le');
|
|
201
355
|
}
|
|
202
|
-
return value <=
|
|
356
|
+
return value <= opt;
|
|
203
357
|
};
|
|
204
358
|
};
|
|
205
359
|
/**
|
|
@@ -209,15 +363,12 @@ const le = (options) => {
|
|
|
209
363
|
* @returns Filter function that returns boolean
|
|
210
364
|
*/
|
|
211
365
|
const gt = (options) => {
|
|
212
|
-
const opt = options
|
|
213
|
-
if (!validateNumberString(opt)) {
|
|
214
|
-
optionMustBeNumber('gt');
|
|
215
|
-
}
|
|
366
|
+
const opt = requiredNumberOption(options, 0, 'gt');
|
|
216
367
|
return (value) => {
|
|
217
368
|
if (typeof value !== 'number') {
|
|
218
369
|
valueMustBeNumber('gt');
|
|
219
370
|
}
|
|
220
|
-
return value >
|
|
371
|
+
return value > opt;
|
|
221
372
|
};
|
|
222
373
|
};
|
|
223
374
|
/**
|
|
@@ -227,15 +378,12 @@ const gt = (options) => {
|
|
|
227
378
|
* @returns Filter function that returns boolean
|
|
228
379
|
*/
|
|
229
380
|
const ge = (options) => {
|
|
230
|
-
const opt = options
|
|
231
|
-
if (!validateNumberString(opt)) {
|
|
232
|
-
optionMustBeNumber('ge');
|
|
233
|
-
}
|
|
381
|
+
const opt = requiredNumberOption(options, 0, 'ge');
|
|
234
382
|
return (value) => {
|
|
235
383
|
if (typeof value !== 'number') {
|
|
236
384
|
valueMustBeNumber('ge');
|
|
237
385
|
}
|
|
238
|
-
return value >=
|
|
386
|
+
return value >= opt;
|
|
239
387
|
};
|
|
240
388
|
};
|
|
241
389
|
/**
|
|
@@ -245,15 +393,12 @@ const ge = (options) => {
|
|
|
245
393
|
* @returns Filter function that returns incremented number
|
|
246
394
|
*/
|
|
247
395
|
const inc = (options) => {
|
|
248
|
-
const opt = options
|
|
249
|
-
if (!validateNumberString(opt)) {
|
|
250
|
-
optionMustBeNumber('inc');
|
|
251
|
-
}
|
|
396
|
+
const opt = requiredNumberOption(options, 0, 'add');
|
|
252
397
|
return (value) => {
|
|
253
398
|
if (typeof value !== 'number') {
|
|
254
|
-
valueMustBeNumber('
|
|
399
|
+
valueMustBeNumber('add');
|
|
255
400
|
}
|
|
256
|
-
return value +
|
|
401
|
+
return value + opt;
|
|
257
402
|
};
|
|
258
403
|
};
|
|
259
404
|
/**
|
|
@@ -263,15 +408,12 @@ const inc = (options) => {
|
|
|
263
408
|
* @returns Filter function that returns decremented number
|
|
264
409
|
*/
|
|
265
410
|
const dec = (options) => {
|
|
266
|
-
const opt = options
|
|
267
|
-
if (!validateNumberString(opt)) {
|
|
268
|
-
optionMustBeNumber('dec');
|
|
269
|
-
}
|
|
411
|
+
const opt = requiredNumberOption(options, 0, 'sub');
|
|
270
412
|
return (value) => {
|
|
271
413
|
if (typeof value !== 'number') {
|
|
272
|
-
valueMustBeNumber('
|
|
414
|
+
valueMustBeNumber('sub');
|
|
273
415
|
}
|
|
274
|
-
return value -
|
|
416
|
+
return value - opt;
|
|
275
417
|
};
|
|
276
418
|
};
|
|
277
419
|
/**
|
|
@@ -281,15 +423,12 @@ const dec = (options) => {
|
|
|
281
423
|
* @returns Filter function that returns multiplied number
|
|
282
424
|
*/
|
|
283
425
|
const mul = (options) => {
|
|
284
|
-
const opt = options
|
|
285
|
-
if (!validateNumberString(opt)) {
|
|
286
|
-
optionMustBeNumber('mul');
|
|
287
|
-
}
|
|
426
|
+
const opt = requiredNumberOption(options, 0, 'mul');
|
|
288
427
|
return (value) => {
|
|
289
428
|
if (typeof value !== 'number') {
|
|
290
429
|
valueMustBeNumber('mul');
|
|
291
430
|
}
|
|
292
|
-
return value *
|
|
431
|
+
return value * opt;
|
|
293
432
|
};
|
|
294
433
|
};
|
|
295
434
|
/**
|
|
@@ -299,15 +438,12 @@ const mul = (options) => {
|
|
|
299
438
|
* @returns Filter function that returns divided number
|
|
300
439
|
*/
|
|
301
440
|
const div = (options) => {
|
|
302
|
-
const opt = options
|
|
303
|
-
if (!validateNumberString(opt)) {
|
|
304
|
-
optionMustBeNumber('div');
|
|
305
|
-
}
|
|
441
|
+
const opt = requiredNumberOption(options, 0, 'div');
|
|
306
442
|
return (value) => {
|
|
307
443
|
if (typeof value !== 'number') {
|
|
308
444
|
valueMustBeNumber('div');
|
|
309
445
|
}
|
|
310
|
-
return value /
|
|
446
|
+
return value / opt;
|
|
311
447
|
};
|
|
312
448
|
};
|
|
313
449
|
/**
|
|
@@ -317,15 +453,12 @@ const div = (options) => {
|
|
|
317
453
|
* @returns Filter function that returns remainder
|
|
318
454
|
*/
|
|
319
455
|
const mod = (options) => {
|
|
320
|
-
const opt = options
|
|
321
|
-
if (!validateNumberString(opt)) {
|
|
322
|
-
optionMustBeNumber('mod');
|
|
323
|
-
}
|
|
456
|
+
const opt = requiredNumberOption(options, 0, 'mod');
|
|
324
457
|
return (value) => {
|
|
325
458
|
if (typeof value !== 'number') {
|
|
326
459
|
valueMustBeNumber('mod');
|
|
327
460
|
}
|
|
328
|
-
return value %
|
|
461
|
+
return value % opt;
|
|
329
462
|
};
|
|
330
463
|
};
|
|
331
464
|
/**
|
|
@@ -353,16 +486,8 @@ const abs = (_options) => {
|
|
|
353
486
|
* @returns Filter function that returns the clamped number
|
|
354
487
|
*/
|
|
355
488
|
const clamp = (options) => {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
optionMustBeNumber('clamp');
|
|
359
|
-
}
|
|
360
|
-
const opt2 = options?.[1] ?? optionsRequired('clamp');
|
|
361
|
-
if (!validateNumberString(opt2)) {
|
|
362
|
-
optionMustBeNumber('clamp');
|
|
363
|
-
}
|
|
364
|
-
const min = Number(opt1);
|
|
365
|
-
const max = Number(opt2);
|
|
489
|
+
const min = requiredNumberOption(options, 0, 'clamp');
|
|
490
|
+
const max = requiredNumberOption(options, 1, 'clamp');
|
|
366
491
|
return (value) => {
|
|
367
492
|
if (typeof value !== 'number') {
|
|
368
493
|
valueMustBeNumber('clamp');
|
|
@@ -377,15 +502,12 @@ const clamp = (options) => {
|
|
|
377
502
|
* @returns Filter function that returns formatted string
|
|
378
503
|
*/
|
|
379
504
|
const fix = (options) => {
|
|
380
|
-
const opt = options?.[0] ?? "0";
|
|
381
|
-
if (!validateNumberString(opt)) {
|
|
382
|
-
optionMustBeNumber('fix');
|
|
383
|
-
}
|
|
505
|
+
const opt = numberOption(options?.[0] ?? "0", 'toFixed');
|
|
384
506
|
return (value) => {
|
|
385
507
|
if (typeof value !== 'number') {
|
|
386
|
-
valueMustBeNumber('
|
|
508
|
+
valueMustBeNumber('toFixed');
|
|
387
509
|
}
|
|
388
|
-
return value.toFixed(
|
|
510
|
+
return value.toFixed(opt);
|
|
389
511
|
};
|
|
390
512
|
};
|
|
391
513
|
/**
|
|
@@ -478,18 +600,10 @@ const trim = (_options) => {
|
|
|
478
600
|
* @returns Filter function that returns sliced string
|
|
479
601
|
*/
|
|
480
602
|
const slice = (options) => {
|
|
481
|
-
const numberedOpts = [];
|
|
482
|
-
const opt1 = options?.[0] ?? optionsRequired('slice');
|
|
483
|
-
if (!validateNumberString(opt1)) {
|
|
484
|
-
optionMustBeNumber('slice');
|
|
485
|
-
}
|
|
486
|
-
numberedOpts.push(Number(opt1));
|
|
603
|
+
const numberedOpts = [requiredNumberOption(options, 0, 'slice')];
|
|
487
604
|
const opt2 = options?.[1];
|
|
488
605
|
if (typeof opt2 !== 'undefined') {
|
|
489
|
-
|
|
490
|
-
optionMustBeNumber('slice');
|
|
491
|
-
}
|
|
492
|
-
numberedOpts.push(Number(opt2));
|
|
606
|
+
numberedOpts.push(numberOption(opt2, 'slice'));
|
|
493
607
|
}
|
|
494
608
|
return (value) => {
|
|
495
609
|
return String(value).slice(...numberedOpts);
|
|
@@ -502,16 +616,10 @@ const slice = (options) => {
|
|
|
502
616
|
* @returns Filter function that returns substring
|
|
503
617
|
*/
|
|
504
618
|
const substr = (options) => {
|
|
505
|
-
const opt1 = options
|
|
506
|
-
|
|
507
|
-
optionMustBeNumber('substr');
|
|
508
|
-
}
|
|
509
|
-
const opt2 = options?.[1] ?? optionsRequired('substr');
|
|
510
|
-
if (!validateNumberString(opt2)) {
|
|
511
|
-
optionMustBeNumber('substr');
|
|
512
|
-
}
|
|
619
|
+
const opt1 = requiredNumberOption(options, 0, 'substr');
|
|
620
|
+
const opt2 = requiredNumberOption(options, 1, 'substr');
|
|
513
621
|
return (value) => {
|
|
514
|
-
return String(value).substr(
|
|
622
|
+
return String(value).substr(opt1, opt2);
|
|
515
623
|
};
|
|
516
624
|
};
|
|
517
625
|
/**
|
|
@@ -521,13 +629,23 @@ const substr = (options) => {
|
|
|
521
629
|
* @returns Filter function that returns padded string
|
|
522
630
|
*/
|
|
523
631
|
const pad = (options) => {
|
|
524
|
-
const opt1 = options
|
|
525
|
-
if (!validateNumberString(opt1)) {
|
|
526
|
-
optionMustBeNumber('pad');
|
|
527
|
-
}
|
|
632
|
+
const opt1 = requiredNumberOption(options, 0, 'padStart');
|
|
528
633
|
const opt2 = options?.[1] ?? '0';
|
|
529
634
|
return (value) => {
|
|
530
|
-
return String(value).padStart(
|
|
635
|
+
return String(value).padStart(opt1, opt2);
|
|
636
|
+
};
|
|
637
|
+
};
|
|
638
|
+
/**
|
|
639
|
+
* padEnd filter - pads string to specified length from the end (the pair of `padStart`, requirement B12).
|
|
640
|
+
*
|
|
641
|
+
* @param options - Array with target length and pad string (default: ' ')
|
|
642
|
+
* @returns Filter function that returns padded string
|
|
643
|
+
*/
|
|
644
|
+
const padEnd = (options) => {
|
|
645
|
+
const opt1 = requiredNumberOption(options, 0, 'padEnd');
|
|
646
|
+
const opt2 = options?.[1] ?? ' ';
|
|
647
|
+
return (value) => {
|
|
648
|
+
return String(value).padEnd(opt1, opt2);
|
|
531
649
|
};
|
|
532
650
|
};
|
|
533
651
|
/**
|
|
@@ -537,12 +655,9 @@ const pad = (options) => {
|
|
|
537
655
|
* @returns Filter function that returns repeated string
|
|
538
656
|
*/
|
|
539
657
|
const rep = (options) => {
|
|
540
|
-
const opt = options
|
|
541
|
-
if (!validateNumberString(opt)) {
|
|
542
|
-
optionMustBeNumber('rep');
|
|
543
|
-
}
|
|
658
|
+
const opt = requiredNumberOption(options, 0, 'repeat');
|
|
544
659
|
return (value) => {
|
|
545
|
-
return String(value).repeat(
|
|
660
|
+
return String(value).repeat(opt);
|
|
546
661
|
};
|
|
547
662
|
};
|
|
548
663
|
/**
|
|
@@ -585,15 +700,11 @@ const float = (_options) => {
|
|
|
585
700
|
* @returns Filter function that returns rounded number
|
|
586
701
|
*/
|
|
587
702
|
const round = (options) => {
|
|
588
|
-
const
|
|
589
|
-
if (!validateNumberString(opt)) {
|
|
590
|
-
optionMustBeNumber('round');
|
|
591
|
-
}
|
|
703
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'round'));
|
|
592
704
|
return (value) => {
|
|
593
705
|
if (typeof value !== 'number') {
|
|
594
706
|
valueMustBeNumber('round');
|
|
595
707
|
}
|
|
596
|
-
const optValue = Math.pow(10, Number(opt));
|
|
597
708
|
return Math.round(value * optValue) / optValue;
|
|
598
709
|
};
|
|
599
710
|
};
|
|
@@ -604,15 +715,11 @@ const round = (options) => {
|
|
|
604
715
|
* @returns Filter function that returns floored number
|
|
605
716
|
*/
|
|
606
717
|
const floor = (options) => {
|
|
607
|
-
const
|
|
608
|
-
if (!validateNumberString(opt)) {
|
|
609
|
-
optionMustBeNumber('floor');
|
|
610
|
-
}
|
|
718
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'floor'));
|
|
611
719
|
return (value) => {
|
|
612
720
|
if (typeof value !== 'number') {
|
|
613
721
|
valueMustBeNumber('floor');
|
|
614
722
|
}
|
|
615
|
-
const optValue = Math.pow(10, Number(opt));
|
|
616
723
|
return Math.floor(value * optValue) / optValue;
|
|
617
724
|
};
|
|
618
725
|
};
|
|
@@ -623,15 +730,11 @@ const floor = (options) => {
|
|
|
623
730
|
* @returns Filter function that returns ceiled number
|
|
624
731
|
*/
|
|
625
732
|
const ceil = (options) => {
|
|
626
|
-
const
|
|
627
|
-
if (!validateNumberString(opt)) {
|
|
628
|
-
optionMustBeNumber('ceil');
|
|
629
|
-
}
|
|
733
|
+
const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'ceil'));
|
|
630
734
|
return (value) => {
|
|
631
735
|
if (typeof value !== 'number') {
|
|
632
736
|
valueMustBeNumber('ceil');
|
|
633
737
|
}
|
|
634
|
-
const optValue = Math.pow(10, Number(opt));
|
|
635
738
|
return Math.ceil(value * optValue) / optValue;
|
|
636
739
|
};
|
|
637
740
|
};
|
|
@@ -642,15 +745,12 @@ const ceil = (options) => {
|
|
|
642
745
|
* @returns Filter function that returns percentage string with '%'
|
|
643
746
|
*/
|
|
644
747
|
const percent = (options) => {
|
|
645
|
-
const opt = options?.[0] ?? '0';
|
|
646
|
-
if (!validateNumberString(opt)) {
|
|
647
|
-
optionMustBeNumber('percent');
|
|
648
|
-
}
|
|
748
|
+
const opt = numberOption(options?.[0] ?? '0', 'percent');
|
|
649
749
|
return (value) => {
|
|
650
750
|
if (typeof value !== 'number') {
|
|
651
751
|
valueMustBeNumber('percent');
|
|
652
752
|
}
|
|
653
|
-
return `${(value * 100).toFixed(
|
|
753
|
+
return `${(value * 100).toFixed(opt)}%`;
|
|
654
754
|
};
|
|
655
755
|
};
|
|
656
756
|
/**
|
|
@@ -665,8 +765,10 @@ const percent = (options) => {
|
|
|
665
765
|
* `fix` / `percent`, which already return strings. Rejecting non-numbers here would
|
|
666
766
|
* break exactly the combination this filter exists for.
|
|
667
767
|
*
|
|
668
|
-
* `null` / `undefined` pass through untouched rather than becoming `"undefinedpx"`,
|
|
669
|
-
*
|
|
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.
|
|
670
772
|
*
|
|
671
773
|
* @param options - Array with the unit/suffix as first element (required)
|
|
672
774
|
* @returns Filter function that returns the value with the unit appended
|
|
@@ -674,9 +776,6 @@ const percent = (options) => {
|
|
|
674
776
|
const unit = (options) => {
|
|
675
777
|
const opt = options?.[0] ?? optionsRequired('unit');
|
|
676
778
|
return (value) => {
|
|
677
|
-
if (value === null || typeof value === 'undefined') {
|
|
678
|
-
return value;
|
|
679
|
-
}
|
|
680
779
|
return String(value) + opt;
|
|
681
780
|
};
|
|
682
781
|
};
|
|
@@ -709,11 +808,7 @@ const join = (options) => {
|
|
|
709
808
|
* @returns Filter function that returns the truncated string
|
|
710
809
|
*/
|
|
711
810
|
const truncate = (options) => {
|
|
712
|
-
const
|
|
713
|
-
if (!validateNumberString(opt1)) {
|
|
714
|
-
optionMustBeNumber('truncate');
|
|
715
|
-
}
|
|
716
|
-
const maxLength = Number(opt1);
|
|
811
|
+
const maxLength = requiredNumberOption(options, 0, 'truncate');
|
|
717
812
|
const suffix = options?.[1] ?? '…';
|
|
718
813
|
return (value) => {
|
|
719
814
|
const v = String(value);
|
|
@@ -847,6 +942,18 @@ const defaults = (options, literals) => {
|
|
|
847
942
|
return value;
|
|
848
943
|
};
|
|
849
944
|
};
|
|
945
|
+
/**
|
|
946
|
+
* coalesce filter - returns the default only for null / undefined (`defaults` also replaces 0, false and "").
|
|
947
|
+
* Requirement B12: the nullish counterpart of `defaults`, read like SQL COALESCE.
|
|
948
|
+
*
|
|
949
|
+
* @param options - Array with default value as first element
|
|
950
|
+
* @returns Filter function that returns value or default
|
|
951
|
+
*/
|
|
952
|
+
const coalesce = (options, literals) => {
|
|
953
|
+
const opt = options?.[0] ?? optionsRequired('coalesce');
|
|
954
|
+
const fallback = literals !== undefined && literals.length > 0 ? literals[0] : opt;
|
|
955
|
+
return (value) => value ?? fallback;
|
|
956
|
+
};
|
|
850
957
|
/**
|
|
851
958
|
* Boolean filter - converts value to boolean.
|
|
852
959
|
*
|
|
@@ -899,25 +1006,27 @@ const builtinFilters = {
|
|
|
899
1006
|
"le": le,
|
|
900
1007
|
"gt": gt,
|
|
901
1008
|
"ge": ge,
|
|
902
|
-
"
|
|
903
|
-
"
|
|
1009
|
+
"add": inc,
|
|
1010
|
+
"sub": dec,
|
|
904
1011
|
"mul": mul,
|
|
905
1012
|
"div": div,
|
|
906
1013
|
"mod": mod,
|
|
907
1014
|
"abs": abs,
|
|
908
1015
|
"clamp": clamp,
|
|
909
|
-
"
|
|
1016
|
+
"toFixed": fix,
|
|
910
1017
|
"locale": locale,
|
|
911
|
-
|
|
912
|
-
"
|
|
913
|
-
"
|
|
914
|
-
"
|
|
915
|
-
"
|
|
916
|
-
"
|
|
917
|
-
"
|
|
918
|
-
"
|
|
919
|
-
"
|
|
920
|
-
"
|
|
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),
|
|
921
1030
|
"join": join,
|
|
922
1031
|
"int": int,
|
|
923
1032
|
"float": float,
|
|
@@ -925,7 +1034,7 @@ const builtinFilters = {
|
|
|
925
1034
|
"floor": floor,
|
|
926
1035
|
"ceil": ceil,
|
|
927
1036
|
"percent": percent,
|
|
928
|
-
"unit": unit,
|
|
1037
|
+
"unit": nullishPassthrough(unit),
|
|
929
1038
|
"date": date,
|
|
930
1039
|
"time": time,
|
|
931
1040
|
"datetime": datetime,
|
|
@@ -934,10 +1043,11 @@ const builtinFilters = {
|
|
|
934
1043
|
"falsy": falsy,
|
|
935
1044
|
"truthy": truthy,
|
|
936
1045
|
"defaults": defaults,
|
|
1046
|
+
"coalesce": coalesce,
|
|
937
1047
|
"boolean": boolean,
|
|
938
1048
|
"number": number,
|
|
939
1049
|
"string": string,
|
|
940
|
-
"
|
|
1050
|
+
"nullIfEmpty": _null,
|
|
941
1051
|
};
|
|
942
1052
|
const outputBuiltinFilters = builtinFilters;
|
|
943
1053
|
|
|
@@ -957,34 +1067,39 @@ const builtinFilterMeta = {
|
|
|
957
1067
|
// 比較・論理
|
|
958
1068
|
eq: { description: "等しいか比較", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
959
1069
|
ne: { description: "異なるか比較", hasArgs: true, resultType: "boolean", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
960
|
-
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 },
|
|
961
1073
|
lt: { description: "より小さいか", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
962
1074
|
le: { description: "以下か", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
963
1075
|
gt: { description: "より大きいか", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
964
1076
|
ge: { description: "以上か", hasArgs: true, resultType: "boolean", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
965
1077
|
// 算術
|
|
966
|
-
|
|
967
|
-
|
|
1078
|
+
add: { description: "加算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
1079
|
+
sub: { description: "減算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
968
1080
|
mul: { description: "乗算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
969
1081
|
div: { description: "除算", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
970
1082
|
mod: { description: "剰余", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
971
1083
|
abs: { description: "絶対値", hasArgs: false, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 0 },
|
|
972
1084
|
clamp: { description: "範囲内に丸める (min,max)", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 2, maxArgs: 2, argTypes: ["number", "number"] },
|
|
973
1085
|
// 数値フォーマット
|
|
974
|
-
|
|
1086
|
+
toFixed: { description: "固定小数点表記", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
|
|
975
1087
|
locale: { description: "ロケール形式で数値フォーマット", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
976
1088
|
// 文字列
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1089
|
+
upper: { description: "大文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1090
|
+
lower: { description: "小文字に変換", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1091
|
+
capitalize: { description: "先頭文字を大文字に", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
980
1092
|
trim: { description: "前後の空白を削除", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
981
1093
|
slice: { description: "部分文字列 (start[,end])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "number"] },
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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"] },
|
|
1099
|
+
repeat: { description: "繰り返し (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
1100
|
+
reverse: { description: "文字順を反転", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
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"] },
|
|
988
1103
|
// 数値パース・丸め
|
|
989
1104
|
int: { description: "整数にパース", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
990
1105
|
float: { description: "浮動小数点数にパース", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
@@ -996,19 +1111,22 @@ const builtinFilterMeta = {
|
|
|
996
1111
|
// それらは既に string を返すため(builtinFilters.ts の unit を参照)
|
|
997
1112
|
unit: { description: "単位(接尾辞)を付加", hasArgs: true, resultType: "string", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["string"] },
|
|
998
1113
|
// 日付・時刻
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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"] },
|
|
1004
1121
|
// 真偽値・変換
|
|
1005
1122
|
falsy: { description: "偽値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1006
1123
|
truthy: { description: "真値か判定", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1007
1124
|
defaults: { description: "偽値の場合デフォルト値", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
1125
|
+
coalesce: { description: "null / undefined の場合デフォルト値", hasArgs: true, resultType: "passthrough", acceptTypes: "any", minArgs: 1, maxArgs: 1, argTypes: ["any"] },
|
|
1008
1126
|
boolean: { description: "ブール値に変換", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1009
1127
|
number: { description: "数値に変換", hasArgs: false, resultType: "number", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1010
1128
|
string: { description: "文字列に変換", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
1011
|
-
|
|
1129
|
+
nullIfEmpty: { description: "空文字列をnullに変換", hasArgs: false, resultType: "passthrough", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
1012
1130
|
};
|
|
1013
1131
|
|
|
1014
1132
|
const STRUCTURAL_BINDING_TYPE_SET = new Set([
|
|
@@ -1018,80 +1136,6 @@ const STRUCTURAL_BINDING_TYPE_SET = new Set([
|
|
|
1018
1136
|
"for",
|
|
1019
1137
|
]);
|
|
1020
1138
|
|
|
1021
|
-
const DELIMITER = '.';
|
|
1022
|
-
const WILDCARD = '*';
|
|
1023
|
-
const MAX_WILDCARD_DEPTH = 128;
|
|
1024
|
-
// data-wcs バインディング構文 `[prop][#mod]: [path][|filter...]` の区切り文字(単一正本・`@state` は v2 で撤去)。
|
|
1025
|
-
// これらは「死守の壁(構文契約)」であり値は不変。manifest.syntax.delimiters で公開される。
|
|
1026
|
-
const BINDING_SEPARATOR = ';'; // 複数バインディングの区切り
|
|
1027
|
-
const PROP_VALUE_SEPARATOR = ':'; // 左辺(prop)と右辺(path)の区切り
|
|
1028
|
-
const MODIFIER_SEPARATOR = '#'; // prop と修飾子の区切り
|
|
1029
|
-
const FILTER_SEPARATOR = '|'; // フィルタパイプの区切り
|
|
1030
|
-
// 修飾子(`#` 後)の語彙(単一正本)。manifest.syntax.modifiers で公開される。
|
|
1031
|
-
// フラグ形(`#prevent` — 値を取らない)とキー値形(`#init=element` — `=` で値を取る)。
|
|
1032
|
-
// 消費箇所(event/handler・BindingSession・twowayHandler・bindings/initialSync)は
|
|
1033
|
-
// この定数を参照する — 文字列リテラルの散在は tooling への収載漏れの温床だった
|
|
1034
|
-
// (docs/static-wiring-dx-design.md §2-2)。
|
|
1035
|
-
const MODIFIER_PREVENT = 'prevent';
|
|
1036
|
-
const MODIFIER_STOP = 'stop';
|
|
1037
|
-
const MODIFIER_READONLY = 'ro';
|
|
1038
|
-
const MODIFIER_FLAGS = Object.freeze([
|
|
1039
|
-
MODIFIER_PREVENT, MODIFIER_STOP, MODIFIER_READONLY,
|
|
1040
|
-
]);
|
|
1041
|
-
const MODIFIER_KEY_INIT = 'init';
|
|
1042
|
-
const MODIFIER_KEY_SYNC = 'sync';
|
|
1043
|
-
const MODIFIER_KEYS = Object.freeze([
|
|
1044
|
-
MODIFIER_KEY_INIT, MODIFIER_KEY_SYNC,
|
|
1045
|
-
]);
|
|
1046
|
-
// bindingType 判別と左辺 namespace の語彙(単一正本)。manifest.syntax.bindingTypes で
|
|
1047
|
-
// 公開される。パーサ(parseBindTextsForElement)とイベント層はこの定数に分岐する。
|
|
1048
|
-
// apply 層のディスパッチマップ(apply/applyChange.ts の applyChangeByFirstSegment)の
|
|
1049
|
-
// キー集合との一致は __tests__/manifest.test.ts の drift テストが強制する —
|
|
1050
|
-
// manifest エントリ(DOM 非依存)から apply 層を import しないための分離。
|
|
1051
|
-
const ELSE_KEYWORD = 'else';
|
|
1052
|
-
const SPREAD_PROP = '...';
|
|
1053
|
-
const EVENT_PROP_PREFIX = 'on';
|
|
1054
|
-
const EVENT_TOKEN_NAMESPACE = 'eventToken';
|
|
1055
|
-
const COMMAND_NAMESPACE = 'command';
|
|
1056
|
-
const CLASS_NAMESPACE = 'class';
|
|
1057
|
-
const ATTR_NAMESPACE = 'attr';
|
|
1058
|
-
const STYLE_NAMESPACE = 'style';
|
|
1059
|
-
// リストインデックス参照名(`$1`..`$N`)の接頭辞(単一正本)。
|
|
1060
|
-
// manifest.syntax.indexParam で公開される。
|
|
1061
|
-
const INDEX_PARAM_PREFIX = '$';
|
|
1062
|
-
/**
|
|
1063
|
-
* stackIndexByIndexName
|
|
1064
|
-
* インデックス名からスタックインデックスへのマッピング
|
|
1065
|
-
* $1 => 0
|
|
1066
|
-
* $2 => 1
|
|
1067
|
-
* :
|
|
1068
|
-
* ${i + 1} => i
|
|
1069
|
-
* i < MAX_WILDCARD_DEPTH
|
|
1070
|
-
*/
|
|
1071
|
-
const tmpIndexByIndexName = {};
|
|
1072
|
-
for (let i = 0; i < MAX_WILDCARD_DEPTH; i++) {
|
|
1073
|
-
tmpIndexByIndexName[`${INDEX_PARAM_PREFIX}${i + 1}`] = i;
|
|
1074
|
-
}
|
|
1075
|
-
Object.freeze(tmpIndexByIndexName);
|
|
1076
|
-
const STATE_CONNECTED_CALLBACK_NAME = "$connectedCallback";
|
|
1077
|
-
const STATE_DISCONNECTED_CALLBACK_NAME = "$disconnectedCallback";
|
|
1078
|
-
const STATE_UPDATED_CALLBACK_NAME = "$updatedCallback";
|
|
1079
|
-
const STATE_ERROR_CALLBACK_NAME = "$errorCallback";
|
|
1080
|
-
const WEBCOMPONENT_STATE_READY_CALLBACK_NAME = "$stateReadyCallback";
|
|
1081
|
-
const STATE_BINDABLES_NAME = "$bindables";
|
|
1082
|
-
const STATE_COMMANDS_NAME = "$commands";
|
|
1083
|
-
const STATE_COMMAND_TOKENS_NAME = "$commandTokens";
|
|
1084
|
-
const STATE_COMMAND_NAMESPACE_NAME = "$command";
|
|
1085
|
-
const STATE_EVENT_TOKENS_NAME = "$eventTokens";
|
|
1086
|
-
const STATE_ON_NAME = "$on";
|
|
1087
|
-
const STATE_STREAMS_NAME = "$streams";
|
|
1088
|
-
const STATE_WATCH_NAME = "$watch";
|
|
1089
|
-
const STATE_SCAN_NAME = "$scan";
|
|
1090
|
-
const STATE_RECURSION_NAME = "$recursion";
|
|
1091
|
-
const STATE_LIST_KEYS_NAME = "$listKeys";
|
|
1092
|
-
const STATE_STREAM_STATUS_NAMESPACE_NAME = "$streamStatus";
|
|
1093
|
-
const STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
|
|
1094
|
-
|
|
1095
1139
|
/**
|
|
1096
1140
|
* manifest.ts — `<wcs-state>` の構文・フィルタ・予約名を機械可読な単一正本として公開する。
|
|
1097
1141
|
*
|
|
@@ -1105,8 +1149,23 @@ const STATE_STREAM_ERROR_NAMESPACE_NAME = "$streamError";
|
|
|
1105
1149
|
* - 将来 `dist/wcs-manifest.json` としてビルド時に書き出し、vscode-wcs がそれを読む形に発展させる。
|
|
1106
1150
|
* - ドリフト検出テスト(__tests__/manifest.test.ts)が、フィルタ集合の golden と実装の一致を CI で保証する。
|
|
1107
1151
|
*/
|
|
1108
|
-
/**
|
|
1109
|
-
|
|
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
|
+
});
|
|
1110
1169
|
/** 機械可読な単一正本を返す。vscode-wcs はこれを消費する想定。 */
|
|
1111
1170
|
function getWcsManifest() {
|
|
1112
1171
|
return {
|
|
@@ -1150,10 +1209,13 @@ function getWcsManifest() {
|
|
|
1150
1209
|
// 実装(Record のキー)から自動導出。手リストを持たない=ドリフトの構造的排除。
|
|
1151
1210
|
filters: Object.keys(outputBuiltinFilters),
|
|
1152
1211
|
filterMeta: builtinFilterMeta,
|
|
1212
|
+
filterAliases: builtinFilterAliases,
|
|
1213
|
+
declarationAliases: DECLARATION_ALIASES,
|
|
1214
|
+
apiAliases: STATE_API_ALIASES,
|
|
1153
1215
|
reservedLifecycle: [
|
|
1154
1216
|
STATE_CONNECTED_CALLBACK_NAME,
|
|
1155
1217
|
STATE_DISCONNECTED_CALLBACK_NAME,
|
|
1156
|
-
|
|
1218
|
+
STATE_RENDERED_CALLBACK_NAME,
|
|
1157
1219
|
STATE_ERROR_CALLBACK_NAME,
|
|
1158
1220
|
WEBCOMPONENT_STATE_READY_CALLBACK_NAME,
|
|
1159
1221
|
],
|
|
@@ -1164,7 +1226,7 @@ function getWcsManifest() {
|
|
|
1164
1226
|
STATE_COMMAND_NAMESPACE_NAME,
|
|
1165
1227
|
STATE_EVENT_TOKENS_NAME,
|
|
1166
1228
|
STATE_ON_NAME,
|
|
1167
|
-
|
|
1229
|
+
STATE_STREAM_NAME,
|
|
1168
1230
|
STATE_WATCH_NAME,
|
|
1169
1231
|
STATE_SCAN_NAME,
|
|
1170
1232
|
STATE_LIST_KEYS_NAME,
|
|
@@ -1175,4 +1237,4 @@ function getWcsManifest() {
|
|
|
1175
1237
|
};
|
|
1176
1238
|
}
|
|
1177
1239
|
|
|
1178
|
-
export { STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterMeta, getWcsManifest };
|
|
1240
|
+
export { DECLARATION_ALIASES, STATE_API_ALIASES, STRUCTURAL_BINDING_TYPE_SET, WCS_MANIFEST_VERSION, builtinFilterAliases, builtinFilterMeta, getWcsManifest };
|