@neeloong/form 0.1.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/index.d.mts +312 -0
- package/index.js +3658 -0
- package/index.min.js +60 -0
- package/index.min.mjs +60 -0
- package/index.mjs +3076 -0
- package/package.json +36 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,3076 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @neeloong/form v0.1.0
|
|
3
|
+
* (c) 2024-2025 Fierflame
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Signal } from 'signal-polyfill';
|
|
8
|
+
export { Signal } from 'signal-polyfill';
|
|
9
|
+
|
|
10
|
+
/** @import Store from './index.mjs' */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {Store} self
|
|
15
|
+
* @param {boolean?} [defState]
|
|
16
|
+
* @param {boolean | ((store: Store) => boolean) | null} [fn]
|
|
17
|
+
* @param {Signal.Computed<boolean>?} [parent]
|
|
18
|
+
* @returns {[Signal.State<boolean?>, Signal.Computed<boolean>]}
|
|
19
|
+
*/
|
|
20
|
+
const createBooleanStates = (self, defState, fn, parent) => {
|
|
21
|
+
|
|
22
|
+
const selfState = new Signal.State(typeof defState === 'boolean' ? defState : null);
|
|
23
|
+
const scriptState = typeof fn === 'function' ? new Signal.Computed(() => fn(self)) : fn ? new Signal.Computed(() => true) : new Signal.Computed(() => false);
|
|
24
|
+
|
|
25
|
+
const getState = () => {
|
|
26
|
+
const s = selfState.get();
|
|
27
|
+
return s === null ? scriptState.get() : s;
|
|
28
|
+
};
|
|
29
|
+
const state = parent
|
|
30
|
+
? new Signal.Computed(() => parent.get() || getState())
|
|
31
|
+
: new Signal.Computed(getState);
|
|
32
|
+
|
|
33
|
+
return [selfState, state];
|
|
34
|
+
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** @import { Schema } from '../types.mjs' */
|
|
38
|
+
/**
|
|
39
|
+
* @template [T=any]
|
|
40
|
+
*/
|
|
41
|
+
class Store {
|
|
42
|
+
/**
|
|
43
|
+
* @param {Schema} schema
|
|
44
|
+
* @param {object} [options]
|
|
45
|
+
* @param {boolean} [options.new]
|
|
46
|
+
*/
|
|
47
|
+
static create(schema, options = {}) {
|
|
48
|
+
return new ObjectStore({type: null, props: schema}, { ...options, parent: null });
|
|
49
|
+
}
|
|
50
|
+
#null = false;
|
|
51
|
+
get null() { return this.#null; }
|
|
52
|
+
/**
|
|
53
|
+
* @param {Schema.Field} schema
|
|
54
|
+
* @param {object} options
|
|
55
|
+
* @param {*} [options.parent]
|
|
56
|
+
* @param {*} [options.state]
|
|
57
|
+
* @param {number | string | null} [options.index]
|
|
58
|
+
* @param {number} [options.length]
|
|
59
|
+
* @param {boolean} [options.null]
|
|
60
|
+
* @param {boolean} [options.new]
|
|
61
|
+
* @param {boolean} [options.hidden]
|
|
62
|
+
* @param {boolean} [options.clearable]
|
|
63
|
+
* @param {boolean} [options.required]
|
|
64
|
+
* @param {boolean} [options.readonly]
|
|
65
|
+
* @param {boolean} [options.disabled]
|
|
66
|
+
* @param {((value: any) => any)?} [options.setValue]
|
|
67
|
+
* @param {((value: any) => any)?} [options.setState]
|
|
68
|
+
* @param {((value: any, state: any) => [value: any, state: any])?} [options.convert]
|
|
69
|
+
* @param {((value: T?, index: any) => void)?} [options.onUpdate]
|
|
70
|
+
* @param {((value: T?, index: any) => void)?} [options.onUpdateState]
|
|
71
|
+
*/
|
|
72
|
+
constructor(schema, {
|
|
73
|
+
null: isNull, state,
|
|
74
|
+
setValue, setState, convert, onUpdate, onUpdateState,
|
|
75
|
+
index, length, new: isNew, parent: parentNode,
|
|
76
|
+
hidden, clearable, required, disabled, readonly,
|
|
77
|
+
}) {
|
|
78
|
+
this.schema = schema;
|
|
79
|
+
this.#state.set(typeof state === 'object' && state || {});
|
|
80
|
+
const parent = parentNode instanceof Store ? parentNode : null;
|
|
81
|
+
if (parent) {
|
|
82
|
+
this.#parent = parent;
|
|
83
|
+
this.#root = parent.#root;
|
|
84
|
+
// TODO: 事件向上冒泡
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const selfNewState = new Signal.State(Boolean(isNew));
|
|
88
|
+
this.#selfNew = selfNewState;
|
|
89
|
+
/** @type {Signal.Computed<boolean>} */
|
|
90
|
+
const newState = parent
|
|
91
|
+
? new Signal.Computed(() => parent.#new.get() || selfNewState.get())
|
|
92
|
+
: new Signal.Computed(() => selfNewState.get());
|
|
93
|
+
|
|
94
|
+
this.#new = newState;
|
|
95
|
+
const immutable = Boolean(schema.immutable);
|
|
96
|
+
const creatable = schema.creatable !== false;
|
|
97
|
+
this.#immutable = immutable;
|
|
98
|
+
this.#creatable = creatable;
|
|
99
|
+
this.#editable = new Signal.Computed(() => newState.get() ? creatable : !immutable);
|
|
100
|
+
|
|
101
|
+
[this.#selfHidden, this.#hidden] = createBooleanStates(this, hidden, schema.hidden, parent ? parent.#hidden : null);
|
|
102
|
+
[this.#selfClearable, this.#clearable] = createBooleanStates(this, clearable, schema.clearable, parent ? parent.#clearable : null);
|
|
103
|
+
[this.#selfRequired, this.#required] = createBooleanStates(this, required, schema.required, parent ? parent.#required : null);
|
|
104
|
+
[this.#selfDisabled, this.#disabled] = createBooleanStates(this, disabled, schema.disabled, parent ? parent.#disabled : null);
|
|
105
|
+
[this.#selfReadonly, this.#readonly] = createBooleanStates(this, readonly, schema.readonly, parent ? parent.#readonly : null);
|
|
106
|
+
|
|
107
|
+
if (isNull) {
|
|
108
|
+
this.#null = true;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.#onUpdate = onUpdate || null;
|
|
112
|
+
this.#onUpdateState = onUpdateState || null;
|
|
113
|
+
this.#setValue = typeof setValue === 'function' ? setValue : null;
|
|
114
|
+
this.#setState = typeof setState === 'function' ? setState : null;
|
|
115
|
+
this.#convert = typeof convert === 'function' ? convert : null;
|
|
116
|
+
this.#length.set(length || 0);
|
|
117
|
+
this.#index.set(index ?? '');
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
#destroyed = false;
|
|
121
|
+
/** @type {((value: any) => any)?} */
|
|
122
|
+
#setValue = null
|
|
123
|
+
/** @type {((value: any) => any)?} */
|
|
124
|
+
#setState = null
|
|
125
|
+
/** @type {((value: any, state: any) => [value: any, state: any])?} */
|
|
126
|
+
#convert = null
|
|
127
|
+
/** @type {((value: any, index: any) => void)?} */
|
|
128
|
+
#onUpdate = null
|
|
129
|
+
/** @type {((value: any, index: any) => void)?} */
|
|
130
|
+
#onUpdateState = null
|
|
131
|
+
/** @readonly @type {Store?} */
|
|
132
|
+
#parent = null;
|
|
133
|
+
/** @readonly @type {Store} */
|
|
134
|
+
#root = this;
|
|
135
|
+
get parent() { return this.#parent; }
|
|
136
|
+
get root() { return this.#root; }
|
|
137
|
+
|
|
138
|
+
#length = new Signal.State(0);
|
|
139
|
+
get length() { return this.#length.get(); }
|
|
140
|
+
set length(v) { this.#length.set(v); }
|
|
141
|
+
#index = new Signal.State(/** @type {string | number} */(''));
|
|
142
|
+
get index() { return this.#index.get(); }
|
|
143
|
+
set index(v) { this.#index.set(v); }
|
|
144
|
+
get no() {
|
|
145
|
+
if (this.#null) { return ''; }
|
|
146
|
+
const index = this.index;
|
|
147
|
+
return typeof index === 'number' ? index + 1 : index;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#creatable = true;
|
|
151
|
+
get creatable() { return this.#creatable; }
|
|
152
|
+
#immutable = false;
|
|
153
|
+
get immutable() { return this.#immutable; }
|
|
154
|
+
|
|
155
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
156
|
+
#new
|
|
157
|
+
/** @readonly @type {Signal.State<boolean>} */
|
|
158
|
+
#selfNew
|
|
159
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
160
|
+
#editable
|
|
161
|
+
get selfNew() { return this.#selfNew.get(); }
|
|
162
|
+
set selfNew(v) { this.#selfNew.set(Boolean(v)); }
|
|
163
|
+
get new() { return this.#new.get(); }
|
|
164
|
+
set new(v) { this.#selfNew.set(Boolean(v)); }
|
|
165
|
+
get editable() { return this.#editable.get(); }
|
|
166
|
+
|
|
167
|
+
/** @readonly @type {Signal.State<boolean?>} */
|
|
168
|
+
#selfHidden
|
|
169
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
170
|
+
#hidden
|
|
171
|
+
get selfHidden() { return this.#selfHidden.get(); }
|
|
172
|
+
set selfHidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
|
|
173
|
+
get hidden() { return this.#hidden.get(); }
|
|
174
|
+
set hidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
|
|
175
|
+
|
|
176
|
+
/** @readonly @type {Signal.State<boolean?>} */
|
|
177
|
+
#selfClearable
|
|
178
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
179
|
+
#clearable
|
|
180
|
+
get selfClearable() { return this.#selfClearable.get(); }
|
|
181
|
+
set selfClearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
|
|
182
|
+
get clearable() { return this.#clearable.get(); }
|
|
183
|
+
set clearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
|
|
184
|
+
|
|
185
|
+
/** @readonly @type {Signal.State<boolean?>} */
|
|
186
|
+
#selfRequired
|
|
187
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
188
|
+
#required
|
|
189
|
+
get selfRequired() { return this.#selfRequired.get(); }
|
|
190
|
+
set selfRequired(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
|
|
191
|
+
get required() { return this.#required.get(); }
|
|
192
|
+
set required(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
|
|
193
|
+
|
|
194
|
+
/** @readonly @type {Signal.State<boolean?>} */
|
|
195
|
+
#selfDisabled
|
|
196
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
197
|
+
#disabled
|
|
198
|
+
get selfDisabled() { return this.#selfDisabled.get(); }
|
|
199
|
+
set selfDisabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
|
|
200
|
+
get disabled() { return this.#disabled.get(); }
|
|
201
|
+
set disabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
|
|
202
|
+
|
|
203
|
+
/** @readonly @type {Signal.State<boolean?>} */
|
|
204
|
+
#selfReadonly
|
|
205
|
+
/** @readonly @type {Signal.Computed<boolean>} */
|
|
206
|
+
#readonly
|
|
207
|
+
get selfReadonly() { return this.#selfReadonly.get(); }
|
|
208
|
+
set selfReadonly(v) { this.#selfReadonly.set(typeof v === 'boolean' ? v : null); }
|
|
209
|
+
get readonly() { return this.#readonly.get(); }
|
|
210
|
+
set readonly(v) { this.#selfReadonly.set(typeof v === 'boolean' ? v : null); }
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
/** @returns {IterableIterator<[key: string | number, value: Store]>} */
|
|
217
|
+
*[Symbol.iterator]() {}
|
|
218
|
+
/**
|
|
219
|
+
*
|
|
220
|
+
* @param {string | number} key
|
|
221
|
+
* @returns {Store?}
|
|
222
|
+
*/
|
|
223
|
+
child(key) { return null; }
|
|
224
|
+
|
|
225
|
+
#set = false;
|
|
226
|
+
/** @type {T?} */
|
|
227
|
+
#initValue = null;
|
|
228
|
+
#lastValue = this.#initValue;
|
|
229
|
+
#value = new Signal.State(this.#initValue);
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
#state = new Signal.State(/** @type {any} */(null));
|
|
233
|
+
#lastState = this.#state.get();
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
get changed() { return this.#value.get() === this.#lastValue; }
|
|
237
|
+
get saved() { return this.#value.get() === this.#initValue; }
|
|
238
|
+
|
|
239
|
+
get value() { return this.#value.get(); }
|
|
240
|
+
set value(v) {
|
|
241
|
+
if (this.#destroyed) { return; }
|
|
242
|
+
const val = this.#setValue?.(v) || v;
|
|
243
|
+
this.#value.set(val);
|
|
244
|
+
if (!this.#set) {
|
|
245
|
+
this.#set = true;
|
|
246
|
+
this.#initValue = v;
|
|
247
|
+
}
|
|
248
|
+
this.#onUpdate?.(this.#value.get(), this.#index.get());
|
|
249
|
+
this.#requestUpdate();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get state() { return this.#state.get(); }
|
|
253
|
+
set state(v) {
|
|
254
|
+
if (this.#destroyed) { return; }
|
|
255
|
+
const sta = this.#setState?.(v) || v;
|
|
256
|
+
this.#state.set(sta);
|
|
257
|
+
this.#set = true;
|
|
258
|
+
this.#onUpdateState?.(this.#state.get(), this.#index.get());
|
|
259
|
+
this.#requestUpdate();
|
|
260
|
+
}
|
|
261
|
+
#requestUpdate() {
|
|
262
|
+
if (this.#needUpdate) { return; }
|
|
263
|
+
this.#needUpdate = true;
|
|
264
|
+
queueMicrotask(() => {
|
|
265
|
+
const oldValue = this.#value.get();
|
|
266
|
+
const oldState = this.#state.get();
|
|
267
|
+
return this.#runUpdate(oldValue, oldState);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
#needUpdate = false;
|
|
273
|
+
/**
|
|
274
|
+
*
|
|
275
|
+
* @param {T} value
|
|
276
|
+
* @param {*} state
|
|
277
|
+
* @returns
|
|
278
|
+
*/
|
|
279
|
+
#toUpdate(value, state) {
|
|
280
|
+
if (this.#destroyed) { return value; }
|
|
281
|
+
const [val,sta] = this.#convert?.(value, state) || [value, state];
|
|
282
|
+
if(this.#value.get() === val && this.#state.get() === sta) { return [val,sta] }
|
|
283
|
+
this.#value.set(val);
|
|
284
|
+
this.#state.set(sta);
|
|
285
|
+
if (!this.#set) {
|
|
286
|
+
this.#set = true;
|
|
287
|
+
this.#initValue = val;
|
|
288
|
+
}
|
|
289
|
+
return this.#runUpdate(val, sta);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
*
|
|
293
|
+
* @param {*} val
|
|
294
|
+
* @param {*} sta
|
|
295
|
+
* @returns
|
|
296
|
+
*/
|
|
297
|
+
#runUpdate(val, sta) {
|
|
298
|
+
if (this.#destroyed) { return [val, sta]; }
|
|
299
|
+
this.#needUpdate = false;
|
|
300
|
+
if (val && typeof val === 'object') {
|
|
301
|
+
/** @type {T} */
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
let newValues = Array.isArray(val) ? [...val] : {...val};
|
|
304
|
+
let newStates = Array.isArray(val) ? Array.isArray(sta) ? [...sta] : [] : {...sta};
|
|
305
|
+
let updated = false;
|
|
306
|
+
for (const [key, field] of this) {
|
|
307
|
+
// @ts-ignore
|
|
308
|
+
const data = val[key];
|
|
309
|
+
const state = sta?.[key];
|
|
310
|
+
const [newData, newState] = field.#toUpdate(data, state);
|
|
311
|
+
if (data !== newData) {
|
|
312
|
+
// @ts-ignore
|
|
313
|
+
newValues[key] = newData;
|
|
314
|
+
updated = true;
|
|
315
|
+
}
|
|
316
|
+
if (state !== newState) {
|
|
317
|
+
newStates[key] = newState;
|
|
318
|
+
updated = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (updated) {
|
|
322
|
+
val = newValues;
|
|
323
|
+
sta = newStates;
|
|
324
|
+
this.#value.set(val);
|
|
325
|
+
this.#state.set(newStates);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (this.#lastValue === val && this.#lastState === sta) {
|
|
329
|
+
return [val, sta];
|
|
330
|
+
}
|
|
331
|
+
this.#lastValue = val;
|
|
332
|
+
this.#lastState = sta;
|
|
333
|
+
return [val, sta];
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
get destroyed() { return this.#destroyed; }
|
|
340
|
+
destroy() {
|
|
341
|
+
if (this.#destroyed) { return; }
|
|
342
|
+
this.#destroyed = true;
|
|
343
|
+
for (const [, field] of this) {
|
|
344
|
+
field.destroy();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class ObjectStore extends Store {
|
|
353
|
+
/** @type {Record<string, Store>} */
|
|
354
|
+
#children
|
|
355
|
+
*[Symbol.iterator]() {yield* Object.entries(this.#children);}
|
|
356
|
+
/**
|
|
357
|
+
*
|
|
358
|
+
* @param {string | number} key
|
|
359
|
+
* @returns {Store?}
|
|
360
|
+
*/
|
|
361
|
+
child(key) { return this.#children[key] || null; }
|
|
362
|
+
/**
|
|
363
|
+
* @param {Schema.Field} schema
|
|
364
|
+
* @param {object} [options]
|
|
365
|
+
* @param {Store?} [options.parent]
|
|
366
|
+
* @param {string | number} [options.index]
|
|
367
|
+
* @param {boolean} [options.new]
|
|
368
|
+
* @param {(value: any, index: any) => void} [options.onUpdate]
|
|
369
|
+
* @param {(value: any, index: any) => void} [options.onUpdateState]
|
|
370
|
+
*/
|
|
371
|
+
constructor(schema,{ parent, index, new: isNew, onUpdate, onUpdateState } = {}) {
|
|
372
|
+
super(schema, {
|
|
373
|
+
parent, index, new: isNew, onUpdate, onUpdateState,
|
|
374
|
+
setValue(v) {
|
|
375
|
+
if (typeof v !== 'object') { return {}; }
|
|
376
|
+
return v;
|
|
377
|
+
},
|
|
378
|
+
setState(v) {
|
|
379
|
+
if (typeof v !== 'object') { return {}; }
|
|
380
|
+
return v;
|
|
381
|
+
},
|
|
382
|
+
convert(v, state) {
|
|
383
|
+
return [
|
|
384
|
+
typeof v === 'object' ? v : {},
|
|
385
|
+
typeof state === 'object' ? state : {},
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
const children = Object.create(null);
|
|
390
|
+
const childCommonOptions = {
|
|
391
|
+
parent: this,
|
|
392
|
+
/** @param {*} value @param {*} index */
|
|
393
|
+
onUpdate: (value, index) => {
|
|
394
|
+
this.value = {...this.value, [index]: value};
|
|
395
|
+
},
|
|
396
|
+
/** @param {*} state @param {*} index */
|
|
397
|
+
onUpdateState: (state, index) => {
|
|
398
|
+
this.state = {...this.state, [index]: state};
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
for (const [index, field] of Object.entries(schema.props || {})) {
|
|
403
|
+
let child;
|
|
404
|
+
if (typeof field.type === 'string') {
|
|
405
|
+
if (field.array) {
|
|
406
|
+
child = new ArrayStore(field, {...childCommonOptions, index});
|
|
407
|
+
} else {
|
|
408
|
+
child = new Store(field, {...childCommonOptions, index});
|
|
409
|
+
}
|
|
410
|
+
} else if (field.array) {
|
|
411
|
+
child = new ArrayStore(field, {...childCommonOptions, index});
|
|
412
|
+
} else {
|
|
413
|
+
child = new ObjectStore(field, { ...childCommonOptions, index});
|
|
414
|
+
}
|
|
415
|
+
children[index] = child;
|
|
416
|
+
}
|
|
417
|
+
this.#children = children;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @template [T=any]
|
|
423
|
+
* @extends {Store<(T | null)[]>}
|
|
424
|
+
*/
|
|
425
|
+
class ArrayStore extends Store {
|
|
426
|
+
/** @type {(index: number, isNew?: boolean) => Store} */
|
|
427
|
+
#create = () => {throw new Error}
|
|
428
|
+
#children = new Signal.State(/** @type {Store[]} */([]));
|
|
429
|
+
get children() { return [...this.#children.get()]; }
|
|
430
|
+
*[Symbol.iterator]() { return yield*[...this.#children.get().entries()]; }
|
|
431
|
+
/**
|
|
432
|
+
*
|
|
433
|
+
* @param {string | number} key
|
|
434
|
+
* @returns {Store?}
|
|
435
|
+
*/
|
|
436
|
+
child(key) {
|
|
437
|
+
const children = this.#children.get();
|
|
438
|
+
if (typeof key === 'number' && key < 0) {
|
|
439
|
+
return children[children.length + key] || null;
|
|
440
|
+
}
|
|
441
|
+
return children[Number(key)] || null;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* @param {Schema.Field} schema
|
|
445
|
+
* @param {object} [options]
|
|
446
|
+
* @param {Store?} [options.parent]
|
|
447
|
+
* @param {string | number | null} [options.index]
|
|
448
|
+
* @param {boolean} [options.new]
|
|
449
|
+
* @param {(value: any, index: any) => void} [options.onUpdate]
|
|
450
|
+
* @param {(value: any, index: any) => void} [options.onUpdateState]
|
|
451
|
+
*/
|
|
452
|
+
constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew} = {}) {
|
|
453
|
+
// @ts-ignore
|
|
454
|
+
const updateChildren = (list) => {
|
|
455
|
+
if (this.destroyed) { return; }
|
|
456
|
+
const length = Array.isArray(list) && list.length || 0;
|
|
457
|
+
const children = [...this.#children.get()];
|
|
458
|
+
const oldLength = children.length;
|
|
459
|
+
for (let i = children.length; i < length; i++) {
|
|
460
|
+
children.push(this.#create(i));
|
|
461
|
+
}
|
|
462
|
+
for (const schema of children.splice(length)) {
|
|
463
|
+
schema.destroy();
|
|
464
|
+
}
|
|
465
|
+
if (oldLength !== length) {
|
|
466
|
+
this.length = children.length;
|
|
467
|
+
this.#children.set(children);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
};
|
|
471
|
+
super(schema, {
|
|
472
|
+
index, new: isNew, parent,
|
|
473
|
+
state: [],
|
|
474
|
+
setValue(v) { return Array.isArray(v) ? v : v == null ? [] : [v] },
|
|
475
|
+
setState(v) { return Array.isArray(v) ? v : v == null ? [] : [v] },
|
|
476
|
+
convert(v, state) {
|
|
477
|
+
const val = Array.isArray(v) ? v : v == null ? [] : [v];
|
|
478
|
+
updateChildren(val);
|
|
479
|
+
return [
|
|
480
|
+
val,
|
|
481
|
+
(Array.isArray(state) ? state : v == null ? [] : [state]),
|
|
482
|
+
];
|
|
483
|
+
},
|
|
484
|
+
onUpdate:(value, index) => {
|
|
485
|
+
updateChildren(value);
|
|
486
|
+
onUpdate?.(value, index);
|
|
487
|
+
},
|
|
488
|
+
onUpdateState,
|
|
489
|
+
});
|
|
490
|
+
const childCommonOptions = {
|
|
491
|
+
parent: this,
|
|
492
|
+
/** @param {*} value @param {*} index */
|
|
493
|
+
onUpdate: (value, index) => {
|
|
494
|
+
const val = [...this.value || []];
|
|
495
|
+
if (val.length < index) {
|
|
496
|
+
val.length = index;
|
|
497
|
+
}
|
|
498
|
+
val[index] = value;
|
|
499
|
+
this.value = val;
|
|
500
|
+
},
|
|
501
|
+
/** @param {*} state @param {*} index */
|
|
502
|
+
onUpdateState: (state, index) => {
|
|
503
|
+
const sta = [...this.state || []];
|
|
504
|
+
if (sta.length < index) {
|
|
505
|
+
sta.length = index;
|
|
506
|
+
}
|
|
507
|
+
sta[index] = state;
|
|
508
|
+
this.state = sta;
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
if (typeof schema.type === 'string') {
|
|
512
|
+
this.#create = (index, isNew) => {
|
|
513
|
+
const child = new Store(schema, {...childCommonOptions, index, new: isNew });
|
|
514
|
+
child.index = index;
|
|
515
|
+
return child
|
|
516
|
+
};
|
|
517
|
+
} else if (!Array.isArray(schema.props)) {
|
|
518
|
+
this.#create = (index, isNew) => {
|
|
519
|
+
const child = new ObjectStore(schema, { ...childCommonOptions, index, new: isNew});
|
|
520
|
+
child.index = index;
|
|
521
|
+
return child
|
|
522
|
+
};
|
|
523
|
+
} else {
|
|
524
|
+
throw new Error();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
*
|
|
530
|
+
* @param {number} index
|
|
531
|
+
* @param {T} value
|
|
532
|
+
* @param {boolean} [isNew]
|
|
533
|
+
* @returns
|
|
534
|
+
*/
|
|
535
|
+
insert(index, value, isNew) {
|
|
536
|
+
if (this.destroyed) { return false; }
|
|
537
|
+
const data = this.value;
|
|
538
|
+
if (!Array.isArray(data)) { return false; }
|
|
539
|
+
const children = [...this.#children.get()];
|
|
540
|
+
const insertIndex = Math.max(0, Math.min(Math.floor(index), children.length));
|
|
541
|
+
const item = this.#create(insertIndex, isNew);
|
|
542
|
+
item.new = true;
|
|
543
|
+
children.splice(insertIndex, 0, item);
|
|
544
|
+
for (let i = index + 1; i < children.length; i++) {
|
|
545
|
+
children[i].index = i;
|
|
546
|
+
}
|
|
547
|
+
const val = [...data];
|
|
548
|
+
val.splice(insertIndex, 0, value);
|
|
549
|
+
const state = this.state;
|
|
550
|
+
if (Array.isArray(state)) {
|
|
551
|
+
const sta = [...state];
|
|
552
|
+
sta.splice(insertIndex, 0, {});
|
|
553
|
+
this.state = sta;
|
|
554
|
+
}
|
|
555
|
+
this.value = val;
|
|
556
|
+
this.length = children.length;
|
|
557
|
+
this.#children.set(children);
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
*
|
|
562
|
+
* @param {T} value
|
|
563
|
+
* @returns
|
|
564
|
+
*/
|
|
565
|
+
add(value) {
|
|
566
|
+
return this.insert(this.#children.get().length, value);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
*
|
|
570
|
+
* @param {number} index
|
|
571
|
+
* @returns
|
|
572
|
+
*/
|
|
573
|
+
remove(index) {
|
|
574
|
+
if (this.destroyed) { return; }
|
|
575
|
+
const data = this.value;
|
|
576
|
+
if (!Array.isArray(data)) { return; }
|
|
577
|
+
const children = [...this.#children.get()];
|
|
578
|
+
const removeIndex = Math.max(0, Math.min(Math.floor(index), children.length));
|
|
579
|
+
const [item] = children.splice(removeIndex, 1);
|
|
580
|
+
if (!item) { return; }
|
|
581
|
+
for (let i = index; i < children.length; i++) {
|
|
582
|
+
children[i].index = i;
|
|
583
|
+
}
|
|
584
|
+
item.destroy();
|
|
585
|
+
const val = [...data];
|
|
586
|
+
const [value] = val.splice(removeIndex, 1);
|
|
587
|
+
const state = this.state;
|
|
588
|
+
if (Array.isArray(state)) {
|
|
589
|
+
const sta = [...this.state];
|
|
590
|
+
sta.splice(removeIndex, 1);
|
|
591
|
+
this.state = sta;
|
|
592
|
+
}
|
|
593
|
+
this.value = val;
|
|
594
|
+
this.length = children.length;
|
|
595
|
+
this.#children.set(children);
|
|
596
|
+
return value;
|
|
597
|
+
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
*
|
|
601
|
+
* @param {number} from
|
|
602
|
+
* @param {number} to
|
|
603
|
+
* @returns
|
|
604
|
+
*/
|
|
605
|
+
move(from, to) {
|
|
606
|
+
if (this.destroyed) { return false; }
|
|
607
|
+
const data = this.value;
|
|
608
|
+
if (!Array.isArray(data)) { return false; }
|
|
609
|
+
const children = [...this.#children.get()];
|
|
610
|
+
const [item] = children.splice(from, 1);
|
|
611
|
+
if (!item) { return false; }
|
|
612
|
+
children.splice(to, 0, item);
|
|
613
|
+
let lft = Math.min(from, to);
|
|
614
|
+
let rgt = Math.max(from, to);
|
|
615
|
+
for (let i = lft; i <= rgt; i++) {
|
|
616
|
+
children[i].index = i;
|
|
617
|
+
}
|
|
618
|
+
const val = [...data];
|
|
619
|
+
const [value] = val.splice(from, 1);
|
|
620
|
+
val.splice(to, 0, value);
|
|
621
|
+
const state = this.state;
|
|
622
|
+
if (Array.isArray(state)) {
|
|
623
|
+
const sta = [...state];
|
|
624
|
+
const [value = {}] = sta.splice(from, 1);
|
|
625
|
+
if (to <= sta.length) {
|
|
626
|
+
sta.splice(to, 0, value);
|
|
627
|
+
} else {
|
|
628
|
+
sta[to] = value;
|
|
629
|
+
}
|
|
630
|
+
this.state = sta;
|
|
631
|
+
}
|
|
632
|
+
this.value = val;
|
|
633
|
+
this.#children.set(children);
|
|
634
|
+
return true;
|
|
635
|
+
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
*
|
|
639
|
+
* @param {number} a
|
|
640
|
+
* @param {number} b
|
|
641
|
+
* @returns
|
|
642
|
+
*/
|
|
643
|
+
exchange(a, b) {
|
|
644
|
+
if (this.destroyed) { return false; }
|
|
645
|
+
const data = this.value;
|
|
646
|
+
if (!Array.isArray(data)) { return false; }
|
|
647
|
+
const children = [...this.#children.get()];
|
|
648
|
+
const aItem = children[a];
|
|
649
|
+
const bItem = children[b];
|
|
650
|
+
if (!aItem || !bItem) { return false; }
|
|
651
|
+
children[b] = aItem;
|
|
652
|
+
children[a] = bItem;
|
|
653
|
+
aItem.index = b;
|
|
654
|
+
bItem.index = a;
|
|
655
|
+
const val = [...data];
|
|
656
|
+
const aValue = val[a];
|
|
657
|
+
const bValue = val[b];
|
|
658
|
+
val[b] = aValue;
|
|
659
|
+
val[a] = bValue;
|
|
660
|
+
const state = this.state;
|
|
661
|
+
if (Array.isArray(state)) {
|
|
662
|
+
const sta = [...state];
|
|
663
|
+
const aValue = sta[a];
|
|
664
|
+
const bValue = sta[b];
|
|
665
|
+
sta[b] = aValue;
|
|
666
|
+
sta[a] = bValue;
|
|
667
|
+
this.state = sta;
|
|
668
|
+
}
|
|
669
|
+
this.value = val;
|
|
670
|
+
this.#children.set(children);
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const errors = {
|
|
676
|
+
CALC: 'no `creteCalc` option, no expression parsing support',
|
|
677
|
+
EVENT: 'no `creteEvent`, options, no event parsing support',
|
|
678
|
+
/** @param {string} endTag @param {string} startTag */
|
|
679
|
+
CLOSE: (endTag, startTag) => `end tag name: ${endTag} is not match the current start tagName: ${startTag}`,
|
|
680
|
+
/** @param {string} name */
|
|
681
|
+
UNCLOSE: (name) => `end tag name: ${name} maybe not complete`,
|
|
682
|
+
/** @param {string} char */
|
|
683
|
+
QUOTE: (char) => `attribute value no end \'${char}\' match`,
|
|
684
|
+
CLOSE_SYMBOL: `elements closed character '/' and '>' must be connected to`,
|
|
685
|
+
/** @param {string} endTag @param {string} [startTag] */
|
|
686
|
+
UNCOMPLETED: (endTag, startTag) => startTag
|
|
687
|
+
? `end tag name: ${endTag} is not complete: ${startTag}`
|
|
688
|
+
: `end tag name: ${endTag} maybe not complete`,
|
|
689
|
+
/** @param {string} entity */
|
|
690
|
+
ENTITY: entity => `entity not found: ${entity}`,
|
|
691
|
+
/** @param {string} symbol */
|
|
692
|
+
SYMBOL: symbol => `unexpected symbol: ${symbol}`,
|
|
693
|
+
/** @param {string} tag */
|
|
694
|
+
TAG: tag => `invalid tagName: ${tag}`,
|
|
695
|
+
/** @param {string} attr */
|
|
696
|
+
ATTR: attr => `invalid attribute: ${attr}`,
|
|
697
|
+
EQUAL: 'attribute equal must after attrName',
|
|
698
|
+
ATTR_VALUE: 'attribute value must after "="',
|
|
699
|
+
EOF: `unexpected end of file`,
|
|
700
|
+
};
|
|
701
|
+
class ParseError extends Error {
|
|
702
|
+
/**
|
|
703
|
+
* @param {keyof typeof errors} code
|
|
704
|
+
* @param {...*} p
|
|
705
|
+
*/
|
|
706
|
+
constructor(code, ...p) {
|
|
707
|
+
/** @type {*} */
|
|
708
|
+
const f = errors[code];
|
|
709
|
+
if (typeof f === 'function') {
|
|
710
|
+
super(f(...p));
|
|
711
|
+
|
|
712
|
+
} else {
|
|
713
|
+
super(f);
|
|
714
|
+
}
|
|
715
|
+
this.code = code;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** @import * as Layout from './index.mjs' */
|
|
720
|
+
|
|
721
|
+
const attrPattern = /^(?<decorator>[:@!+*\.]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
|
|
722
|
+
const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_Ideograph}_]*)?$/u;
|
|
723
|
+
/**
|
|
724
|
+
* @param {Layout.Node} node
|
|
725
|
+
* @param {Exclude<Layout.Options['creteCalc'], undefined>} creteCalc
|
|
726
|
+
* @param {Exclude<Layout.Options['creteEvent'], undefined>} creteEvent
|
|
727
|
+
*/
|
|
728
|
+
function createAttributeAdder(node, creteCalc, creteEvent) {
|
|
729
|
+
const { attrs, directives, events, classes, styles, vars, aliases } = node;
|
|
730
|
+
/**
|
|
731
|
+
* @param {string} qName
|
|
732
|
+
* @param {string} value
|
|
733
|
+
*/
|
|
734
|
+
function addAttribute(qName, value) {
|
|
735
|
+
const attr = attrPattern.exec(qName
|
|
736
|
+
.replace(/./g,'.')
|
|
737
|
+
.replace(/:/g,':')
|
|
738
|
+
.replace(/@/g,'@')
|
|
739
|
+
.replace(/+/g,'+')
|
|
740
|
+
.replace(/-/g,'-')
|
|
741
|
+
.replace(/[*×]/g,'*')
|
|
742
|
+
.replace(/!/g, '!'))?.groups;
|
|
743
|
+
if (!attr) { throw new ParseError('ATTR', qName); }
|
|
744
|
+
const { name } = attr;
|
|
745
|
+
const decorator = attr.decorator?.toLowerCase();
|
|
746
|
+
if (!decorator) {
|
|
747
|
+
attrs[name] = value;
|
|
748
|
+
} else if (decorator === ':') {
|
|
749
|
+
attrs[name] = nameRegex.test(value) ? {name: value} : creteCalc(value);
|
|
750
|
+
} else if (decorator === '.') {
|
|
751
|
+
classes[name] = !value ? true : nameRegex.test(value) ? value : creteCalc(value);
|
|
752
|
+
} else if (decorator === 'style:') {
|
|
753
|
+
styles[name] = nameRegex.test(value) ? value : creteCalc(value);
|
|
754
|
+
} else if (decorator === '@') {
|
|
755
|
+
events[name] = nameRegex.test(value) ? value : creteEvent(value);
|
|
756
|
+
} else if (decorator === '+') {
|
|
757
|
+
vars[name] = !value ? '' : nameRegex.test(value) ? value : creteCalc(value);
|
|
758
|
+
} else if (decorator === '*') {
|
|
759
|
+
aliases[name] = nameRegex.test(value) ? value : creteEvent(value);
|
|
760
|
+
} else if (decorator === '!') {
|
|
761
|
+
const key = name.toString();
|
|
762
|
+
switch (key) {
|
|
763
|
+
case 'fragment':
|
|
764
|
+
case 'else':
|
|
765
|
+
case 'enum':
|
|
766
|
+
directives[key] = true;
|
|
767
|
+
break;
|
|
768
|
+
case 'if':
|
|
769
|
+
case 'text':
|
|
770
|
+
case 'html':
|
|
771
|
+
directives[key] = nameRegex.test(value) ? value : creteCalc(value);
|
|
772
|
+
break;
|
|
773
|
+
case 'bind':
|
|
774
|
+
case 'value':
|
|
775
|
+
case 'comment':
|
|
776
|
+
directives[key] = value;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return addAttribute;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** @import * as Layout from './index.mjs' */
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* @param {string} name
|
|
788
|
+
* @param {string?} [is]
|
|
789
|
+
* @returns {Layout.Node}
|
|
790
|
+
*
|
|
791
|
+
*/
|
|
792
|
+
function createElement(name, is) {
|
|
793
|
+
return {
|
|
794
|
+
name,
|
|
795
|
+
is,
|
|
796
|
+
children: [],
|
|
797
|
+
attrs: Object.create(null),
|
|
798
|
+
events: Object.create(null),
|
|
799
|
+
directives: Object.create(null),
|
|
800
|
+
classes: Object.create(null),
|
|
801
|
+
styles: Object.create(null),
|
|
802
|
+
vars: Object.create(null),
|
|
803
|
+
aliases: Object.create(null),
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/** @type {Record<string, string>} */
|
|
808
|
+
var entityMap = {
|
|
809
|
+
lt: '<',
|
|
810
|
+
gt: '>',
|
|
811
|
+
amp: '&',
|
|
812
|
+
quot: '"',
|
|
813
|
+
apos: "'",
|
|
814
|
+
|
|
815
|
+
Agrave: 'À',
|
|
816
|
+
Aacute: 'Á',
|
|
817
|
+
Acirc: 'Â',
|
|
818
|
+
Atilde: 'Ã',
|
|
819
|
+
Auml: 'Ä',
|
|
820
|
+
Aring: 'Å',
|
|
821
|
+
AElig: 'Æ',
|
|
822
|
+
Ccedil: 'Ç',
|
|
823
|
+
Egrave: 'È',
|
|
824
|
+
Eacute: 'É',
|
|
825
|
+
Ecirc: 'Ê',
|
|
826
|
+
Euml: 'Ë',
|
|
827
|
+
Igrave: 'Ì',
|
|
828
|
+
Iacute: 'Í',
|
|
829
|
+
Icirc: 'Î',
|
|
830
|
+
Iuml: 'Ï',
|
|
831
|
+
ETH: 'Ð',
|
|
832
|
+
Ntilde: 'Ñ',
|
|
833
|
+
Ograve: 'Ò',
|
|
834
|
+
Oacute: 'Ó',
|
|
835
|
+
Ocirc: 'Ô',
|
|
836
|
+
Otilde: 'Õ',
|
|
837
|
+
Ouml: 'Ö',
|
|
838
|
+
Oslash: 'Ø',
|
|
839
|
+
Ugrave: 'Ù',
|
|
840
|
+
Uacute: 'Ú',
|
|
841
|
+
Ucirc: 'Û',
|
|
842
|
+
Uuml: 'Ü',
|
|
843
|
+
Yacute: 'Ý',
|
|
844
|
+
THORN: 'Þ',
|
|
845
|
+
szlig: 'ß',
|
|
846
|
+
agrave: 'à',
|
|
847
|
+
aacute: 'á',
|
|
848
|
+
acirc: 'â',
|
|
849
|
+
atilde: 'ã',
|
|
850
|
+
auml: 'ä',
|
|
851
|
+
aring: 'å',
|
|
852
|
+
aelig: 'æ',
|
|
853
|
+
ccedil: 'ç',
|
|
854
|
+
egrave: 'è',
|
|
855
|
+
eacute: 'é',
|
|
856
|
+
ecirc: 'ê',
|
|
857
|
+
euml: 'ë',
|
|
858
|
+
igrave: 'ì',
|
|
859
|
+
iacute: 'í',
|
|
860
|
+
icirc: 'î',
|
|
861
|
+
iuml: 'ï',
|
|
862
|
+
eth: 'ð',
|
|
863
|
+
ntilde: 'ñ',
|
|
864
|
+
ograve: 'ò',
|
|
865
|
+
oacute: 'ó',
|
|
866
|
+
ocirc: 'ô',
|
|
867
|
+
otilde: 'õ',
|
|
868
|
+
ouml: 'ö',
|
|
869
|
+
oslash: 'ø',
|
|
870
|
+
ugrave: 'ù',
|
|
871
|
+
uacute: 'ú',
|
|
872
|
+
ucirc: 'û',
|
|
873
|
+
uuml: 'ü',
|
|
874
|
+
yacute: 'ý',
|
|
875
|
+
thorn: 'þ',
|
|
876
|
+
yuml: 'ÿ',
|
|
877
|
+
nbsp: '\u00a0',
|
|
878
|
+
iexcl: '¡',
|
|
879
|
+
cent: '¢',
|
|
880
|
+
pound: '£',
|
|
881
|
+
curren: '¤',
|
|
882
|
+
yen: '¥',
|
|
883
|
+
brvbar: '¦',
|
|
884
|
+
sect: '§',
|
|
885
|
+
uml: '¨',
|
|
886
|
+
copy: '©',
|
|
887
|
+
ordf: 'ª',
|
|
888
|
+
laquo: '«',
|
|
889
|
+
not: '¬',
|
|
890
|
+
shy: '',
|
|
891
|
+
reg: '®',
|
|
892
|
+
macr: '¯',
|
|
893
|
+
deg: '°',
|
|
894
|
+
plusmn: '±',
|
|
895
|
+
sup2: '²',
|
|
896
|
+
sup3: '³',
|
|
897
|
+
acute: '´',
|
|
898
|
+
micro: 'µ',
|
|
899
|
+
para: '¶',
|
|
900
|
+
middot: '·',
|
|
901
|
+
cedil: '¸',
|
|
902
|
+
sup1: '¹',
|
|
903
|
+
ordm: 'º',
|
|
904
|
+
raquo: '»',
|
|
905
|
+
frac14: '¼',
|
|
906
|
+
frac12: '½',
|
|
907
|
+
frac34: '¾',
|
|
908
|
+
iquest: '¿',
|
|
909
|
+
times: '×',
|
|
910
|
+
divide: '÷',
|
|
911
|
+
forall: '∀',
|
|
912
|
+
part: '∂',
|
|
913
|
+
exist: '∃',
|
|
914
|
+
empty: '∅',
|
|
915
|
+
nabla: '∇',
|
|
916
|
+
isin: '∈',
|
|
917
|
+
notin: '∉',
|
|
918
|
+
ni: '∋',
|
|
919
|
+
prod: '∏',
|
|
920
|
+
sum: '∑',
|
|
921
|
+
minus: '−',
|
|
922
|
+
lowast: '∗',
|
|
923
|
+
radic: '√',
|
|
924
|
+
prop: '∝',
|
|
925
|
+
infin: '∞',
|
|
926
|
+
ang: '∠',
|
|
927
|
+
and: '∧',
|
|
928
|
+
or: '∨',
|
|
929
|
+
cap: '∩',
|
|
930
|
+
cup: '∪',
|
|
931
|
+
'int': '∫',
|
|
932
|
+
there4: '∴',
|
|
933
|
+
sim: '∼',
|
|
934
|
+
cong: '≅',
|
|
935
|
+
asymp: '≈',
|
|
936
|
+
ne: '≠',
|
|
937
|
+
equiv: '≡',
|
|
938
|
+
le: '≤',
|
|
939
|
+
ge: '≥',
|
|
940
|
+
sub: '⊂',
|
|
941
|
+
sup: '⊃',
|
|
942
|
+
nsub: '⊄',
|
|
943
|
+
sube: '⊆',
|
|
944
|
+
supe: '⊇',
|
|
945
|
+
oplus: '⊕',
|
|
946
|
+
otimes: '⊗',
|
|
947
|
+
perp: '⊥',
|
|
948
|
+
sdot: '⋅',
|
|
949
|
+
Alpha: 'Α',
|
|
950
|
+
Beta: 'Β',
|
|
951
|
+
Gamma: 'Γ',
|
|
952
|
+
Delta: 'Δ',
|
|
953
|
+
Epsilon: 'Ε',
|
|
954
|
+
Zeta: 'Ζ',
|
|
955
|
+
Eta: 'Η',
|
|
956
|
+
Theta: 'Θ',
|
|
957
|
+
Iota: 'Ι',
|
|
958
|
+
Kappa: 'Κ',
|
|
959
|
+
Lambda: 'Λ',
|
|
960
|
+
Mu: 'Μ',
|
|
961
|
+
Nu: 'Ν',
|
|
962
|
+
Xi: 'Ξ',
|
|
963
|
+
Omicron: 'Ο',
|
|
964
|
+
Pi: 'Π',
|
|
965
|
+
Rho: 'Ρ',
|
|
966
|
+
Sigma: 'Σ',
|
|
967
|
+
Tau: 'Τ',
|
|
968
|
+
Upsilon: 'Υ',
|
|
969
|
+
Phi: 'Φ',
|
|
970
|
+
Chi: 'Χ',
|
|
971
|
+
Psi: 'Ψ',
|
|
972
|
+
Omega: 'Ω',
|
|
973
|
+
alpha: 'α',
|
|
974
|
+
beta: 'β',
|
|
975
|
+
gamma: 'γ',
|
|
976
|
+
delta: 'δ',
|
|
977
|
+
epsilon: 'ε',
|
|
978
|
+
zeta: 'ζ',
|
|
979
|
+
eta: 'η',
|
|
980
|
+
theta: 'θ',
|
|
981
|
+
iota: 'ι',
|
|
982
|
+
kappa: 'κ',
|
|
983
|
+
lambda: 'λ',
|
|
984
|
+
mu: 'μ',
|
|
985
|
+
nu: 'ν',
|
|
986
|
+
xi: 'ξ',
|
|
987
|
+
omicron: 'ο',
|
|
988
|
+
pi: 'π',
|
|
989
|
+
rho: 'ρ',
|
|
990
|
+
sigmaf: 'ς',
|
|
991
|
+
sigma: 'σ',
|
|
992
|
+
tau: 'τ',
|
|
993
|
+
upsilon: 'υ',
|
|
994
|
+
phi: 'φ',
|
|
995
|
+
chi: 'χ',
|
|
996
|
+
psi: 'ψ',
|
|
997
|
+
omega: 'ω',
|
|
998
|
+
thetasym: 'ϑ',
|
|
999
|
+
upsih: 'ϒ',
|
|
1000
|
+
piv: 'ϖ',
|
|
1001
|
+
OElig: 'Œ',
|
|
1002
|
+
oelig: 'œ',
|
|
1003
|
+
Scaron: 'Š',
|
|
1004
|
+
scaron: 'š',
|
|
1005
|
+
Yuml: 'Ÿ',
|
|
1006
|
+
fnof: 'ƒ',
|
|
1007
|
+
circ: 'ˆ',
|
|
1008
|
+
tilde: '˜',
|
|
1009
|
+
ensp: ' ',
|
|
1010
|
+
emsp: ' ',
|
|
1011
|
+
thinsp: ' ',
|
|
1012
|
+
zwnj: '',
|
|
1013
|
+
zwj: '',
|
|
1014
|
+
lrm: '',
|
|
1015
|
+
rlm: '',
|
|
1016
|
+
ndash: '–',
|
|
1017
|
+
mdash: '—',
|
|
1018
|
+
lsquo: '‘',
|
|
1019
|
+
rsquo: '’',
|
|
1020
|
+
sbquo: '‚',
|
|
1021
|
+
ldquo: '“',
|
|
1022
|
+
rdquo: '”',
|
|
1023
|
+
bdquo: '„',
|
|
1024
|
+
dagger: '†',
|
|
1025
|
+
Dagger: '‡',
|
|
1026
|
+
bull: '•',
|
|
1027
|
+
hellip: '…',
|
|
1028
|
+
permil: '‰',
|
|
1029
|
+
prime: '′',
|
|
1030
|
+
Prime: '″',
|
|
1031
|
+
lsaquo: '‹',
|
|
1032
|
+
rsaquo: '›',
|
|
1033
|
+
oline: '‾',
|
|
1034
|
+
euro: '€',
|
|
1035
|
+
trade: '™',
|
|
1036
|
+
larr: '←',
|
|
1037
|
+
uarr: '↑',
|
|
1038
|
+
rarr: '→',
|
|
1039
|
+
darr: '↓',
|
|
1040
|
+
harr: '↔',
|
|
1041
|
+
crarr: '↵',
|
|
1042
|
+
lceil: '⌈',
|
|
1043
|
+
rceil: '⌉',
|
|
1044
|
+
lfloor: '⌊',
|
|
1045
|
+
rfloor: '⌋',
|
|
1046
|
+
loz: '◊',
|
|
1047
|
+
spades: '♠',
|
|
1048
|
+
clubs: '♣',
|
|
1049
|
+
hearts: '♥',
|
|
1050
|
+
diams: '♦'
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
/** @import * as Layout from './index.mjs' */
|
|
1054
|
+
|
|
1055
|
+
const tagNamePattern = /^(?<name>[\w\p{Unified_Ideograph}_][-\.\|:|d\w\p{Unified_Ideograph}_:]*)(?:|(?<is>[\w\p{Unified_Ideograph}_][-\.\|:|d\w\p{Unified_Ideograph}_]*))?$/u;
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
*
|
|
1059
|
+
* @param {string} c
|
|
1060
|
+
* @returns
|
|
1061
|
+
*/
|
|
1062
|
+
function isSpace(c) {
|
|
1063
|
+
return c === '0x80' || c <= ' ';
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
*
|
|
1067
|
+
* @param {string} c
|
|
1068
|
+
* @returns
|
|
1069
|
+
*/
|
|
1070
|
+
function isIdCode(c) {
|
|
1071
|
+
return c !== '=' && c !== '/' && c !== '>' && c && c !== '\'' && c !== '"' && !isSpace(c);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
*
|
|
1076
|
+
* @param {string} source
|
|
1077
|
+
* @param {number} elStartEnd
|
|
1078
|
+
* @param {string} name
|
|
1079
|
+
* @param {*} closeMap
|
|
1080
|
+
* @returns
|
|
1081
|
+
*/
|
|
1082
|
+
function fixSelfClosed(source, elStartEnd, name, closeMap) {
|
|
1083
|
+
let pos = closeMap[name];
|
|
1084
|
+
if (pos == null) {
|
|
1085
|
+
pos = source.lastIndexOf('</' + name + '>');
|
|
1086
|
+
if (pos < elStartEnd) {
|
|
1087
|
+
pos = source.lastIndexOf('</' + name);
|
|
1088
|
+
}
|
|
1089
|
+
closeMap[name] = pos;
|
|
1090
|
+
}
|
|
1091
|
+
return pos < elStartEnd;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* @param {ConstructorParameters<typeof ParseError>} p
|
|
1096
|
+
*/
|
|
1097
|
+
function error(...p) {
|
|
1098
|
+
console.error(new ParseError(...p));
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
*
|
|
1103
|
+
* @param {string} a
|
|
1104
|
+
* @returns {string}
|
|
1105
|
+
*/
|
|
1106
|
+
function entityReplacer(a) {
|
|
1107
|
+
const k = a.slice(1, -1);
|
|
1108
|
+
if (k.charAt(0) === '#') {
|
|
1109
|
+
return String.fromCodePoint(parseInt(k.substring(1).replace('x', '0x')));
|
|
1110
|
+
}
|
|
1111
|
+
if (k in entityMap) { return entityMap[k]; }
|
|
1112
|
+
error('ENTITY', a);
|
|
1113
|
+
return a;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
*
|
|
1117
|
+
* @param {string} source
|
|
1118
|
+
* @param {Layout.Options} [options]
|
|
1119
|
+
* @returns {(Layout.Node | string)[]}
|
|
1120
|
+
*/
|
|
1121
|
+
function parse(source, {
|
|
1122
|
+
creteCalc = () => { throw new ParseError('CALC'); },
|
|
1123
|
+
creteEvent = () => { throw new ParseError('EVENT'); },
|
|
1124
|
+
simpleTag = new Set,
|
|
1125
|
+
} = {}) {
|
|
1126
|
+
/** @type {(Layout.Node | string)[]} */
|
|
1127
|
+
const children = [];
|
|
1128
|
+
|
|
1129
|
+
const doc = { children };
|
|
1130
|
+
/** @type {(Layout.Node | null)[]} */
|
|
1131
|
+
const stack = [];
|
|
1132
|
+
/** @type {Layout.Node?} */
|
|
1133
|
+
let currentNode = null;
|
|
1134
|
+
/** @type {typeof doc | Layout.Node} */
|
|
1135
|
+
let current = doc;
|
|
1136
|
+
function endElement() {
|
|
1137
|
+
currentNode = stack.pop() || null;
|
|
1138
|
+
current = currentNode || doc;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
*
|
|
1142
|
+
* @param {string} chars
|
|
1143
|
+
* @returns
|
|
1144
|
+
*/
|
|
1145
|
+
function characters(chars) {
|
|
1146
|
+
chars = chars.replace(/^\n|(?<=\n)\t+|\n\t*$/g, '');
|
|
1147
|
+
if (!chars) { return; }
|
|
1148
|
+
current.children.push(chars);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
let closeMap = {};
|
|
1152
|
+
let index = 0;
|
|
1153
|
+
/**
|
|
1154
|
+
*
|
|
1155
|
+
* @param {number} end
|
|
1156
|
+
*/
|
|
1157
|
+
function appendText(end) {
|
|
1158
|
+
if (end <= index) { return; }
|
|
1159
|
+
const xt = source.substring(index, end).replace(/&#?\w+;/g, entityReplacer);
|
|
1160
|
+
characters(xt);
|
|
1161
|
+
index = end;
|
|
1162
|
+
}
|
|
1163
|
+
for (; ;) {
|
|
1164
|
+
const tagStart = source.indexOf('<', index);
|
|
1165
|
+
if (tagStart < 0) {
|
|
1166
|
+
const text = source.substring(index);
|
|
1167
|
+
if (!text.match(/^\s*$/)) {
|
|
1168
|
+
characters(text);
|
|
1169
|
+
}
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
if (tagStart > index) {
|
|
1173
|
+
appendText(tagStart);
|
|
1174
|
+
}
|
|
1175
|
+
if (source.charAt(tagStart + 1) === '/') {
|
|
1176
|
+
index = source.indexOf('>', tagStart + 3);
|
|
1177
|
+
let name = source.substring(tagStart + 2, index);
|
|
1178
|
+
if (index < 0) {
|
|
1179
|
+
name = source.substring(tagStart + 2).replace(/[\s<].*/, '');
|
|
1180
|
+
error('UNCOMPLETED', name, currentNode?.name);
|
|
1181
|
+
index = tagStart + 1 + name.length;
|
|
1182
|
+
} else if (name.match(/\s</)) {
|
|
1183
|
+
name = name.replace(/[\s<].*/, '');
|
|
1184
|
+
error('UNCOMPLETED', name);
|
|
1185
|
+
index = tagStart + 1 + name.length;
|
|
1186
|
+
}
|
|
1187
|
+
if (currentNode) {
|
|
1188
|
+
const currentName = currentNode.name;
|
|
1189
|
+
if (currentName === name) {
|
|
1190
|
+
endElement();
|
|
1191
|
+
} else if (currentName.toLowerCase() == name.toLowerCase()) {
|
|
1192
|
+
endElement();
|
|
1193
|
+
} else {
|
|
1194
|
+
throw new ParseError('CLOSE', name, currentNode.name);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
index++;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
index = tagStart + 1;
|
|
1201
|
+
/**
|
|
1202
|
+
*
|
|
1203
|
+
* @param {string} c
|
|
1204
|
+
* @returns
|
|
1205
|
+
*/
|
|
1206
|
+
function getQuote(c) {
|
|
1207
|
+
let start = index + 1;
|
|
1208
|
+
index = source.indexOf(c, start);
|
|
1209
|
+
if (index < 0) { throw new ParseError('QUOTE', c); }
|
|
1210
|
+
const value = source.slice(start, index).replace(/&#?\w+;/g, entityReplacer);
|
|
1211
|
+
index++;
|
|
1212
|
+
return value;
|
|
1213
|
+
}
|
|
1214
|
+
function skipSpace() {
|
|
1215
|
+
let c = source.charAt(index);
|
|
1216
|
+
for (; c <= ' ' || c === '\u0080'; c = source.charAt(++index)) { }
|
|
1217
|
+
return c;
|
|
1218
|
+
|
|
1219
|
+
}
|
|
1220
|
+
function getId() {
|
|
1221
|
+
let start = index;
|
|
1222
|
+
let c = source.charAt(index);
|
|
1223
|
+
while (isIdCode(c)) {
|
|
1224
|
+
index++;
|
|
1225
|
+
c = source.charAt(index);
|
|
1226
|
+
}
|
|
1227
|
+
return source.slice(start, index);
|
|
1228
|
+
}
|
|
1229
|
+
let c = source.charAt(index);
|
|
1230
|
+
switch (c) {
|
|
1231
|
+
case '=': throw new ParseError('EQUAL');
|
|
1232
|
+
case '"': case '\'': throw new ParseError('ATTR_VALUE');
|
|
1233
|
+
case '>': case '/': throw new ParseError('SYMBOL', c);
|
|
1234
|
+
case '': throw new ParseError('EOF');
|
|
1235
|
+
}
|
|
1236
|
+
const name = getId();
|
|
1237
|
+
const tagRes = tagNamePattern.exec(name)?.groups;
|
|
1238
|
+
if (!tagRes) { throw new ParseError('TAG', name); }
|
|
1239
|
+
stack.push(currentNode);
|
|
1240
|
+
currentNode = createElement(tagRes.name, tagRes.is);
|
|
1241
|
+
current.children.push(currentNode);
|
|
1242
|
+
current = currentNode;
|
|
1243
|
+
const addAttribute = createAttributeAdder(currentNode, creteCalc, creteEvent);
|
|
1244
|
+
|
|
1245
|
+
let run = true;
|
|
1246
|
+
let closed = false;
|
|
1247
|
+
parseAttr: for (; run;) {
|
|
1248
|
+
let c = skipSpace();
|
|
1249
|
+
switch (c) {
|
|
1250
|
+
case '': error('EOF'); index++; break parseAttr;
|
|
1251
|
+
case '>': index++; break parseAttr;
|
|
1252
|
+
case '/': closed = true; break parseAttr;
|
|
1253
|
+
case '"': case '\'': throw new ParseError('ATTR_VALUE');
|
|
1254
|
+
case '=': throw new ParseError('SYMBOL', c);
|
|
1255
|
+
}
|
|
1256
|
+
const id = getId();
|
|
1257
|
+
if (!id) { error('EOF'); index++; break parseAttr; }
|
|
1258
|
+
c = skipSpace();
|
|
1259
|
+
switch (c) {
|
|
1260
|
+
case '': error('EOF'); index++; break parseAttr;
|
|
1261
|
+
case '>': addAttribute(id, ''); index++; break parseAttr;
|
|
1262
|
+
case '/': addAttribute(id, ''); closed = true; break parseAttr;
|
|
1263
|
+
case '=': index++; break;
|
|
1264
|
+
case '\'': case '"': addAttribute(id, getQuote(c)); continue;
|
|
1265
|
+
default: addAttribute(id, ''); continue;
|
|
1266
|
+
}
|
|
1267
|
+
c = skipSpace();
|
|
1268
|
+
switch (c) {
|
|
1269
|
+
case '': addAttribute(id, ''); error('EOF'); index++; break parseAttr;
|
|
1270
|
+
case '>': addAttribute(id, ''); index++; break parseAttr;
|
|
1271
|
+
case '/': addAttribute(id, ''); closed = true; break parseAttr;
|
|
1272
|
+
case '\'': case '"': addAttribute(id, getQuote(c)); continue;
|
|
1273
|
+
}
|
|
1274
|
+
addAttribute(id, getId());
|
|
1275
|
+
}
|
|
1276
|
+
if (closed) {
|
|
1277
|
+
while (true) {
|
|
1278
|
+
index++;
|
|
1279
|
+
const c = source.charAt(index);
|
|
1280
|
+
if (c === '/') { continue; }
|
|
1281
|
+
if (c <= ' ' || c === '\u0080') { continue; }
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
const c = source.charAt(index);
|
|
1285
|
+
switch (c) {
|
|
1286
|
+
case '=': throw new ParseError('EQUAL');
|
|
1287
|
+
case '"': case '\'': throw new ParseError('ATTR_VALUE'); // No known test case
|
|
1288
|
+
case '': error('EOF'); break;
|
|
1289
|
+
case '>': index++; break;
|
|
1290
|
+
default: throw new ParseError('CLOSE_SYMBOL');
|
|
1291
|
+
}
|
|
1292
|
+
endElement();
|
|
1293
|
+
} else if (simpleTag.has(name) || fixSelfClosed(source, index, name, closeMap)) {
|
|
1294
|
+
endElement();
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return children;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/** @import * as Layout from './index.mjs' */
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
*
|
|
1304
|
+
* @param {*} c
|
|
1305
|
+
* @returns
|
|
1306
|
+
*/
|
|
1307
|
+
function _xmlEncoder(c) {
|
|
1308
|
+
return c == '<' && '<' ||
|
|
1309
|
+
c == '>' && '>' ||
|
|
1310
|
+
c == '&' && '&' ||
|
|
1311
|
+
c == '"' && '"' ||
|
|
1312
|
+
'&#' + c.charCodeAt() + ';';
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
*
|
|
1317
|
+
* @param {Layout.Node} node
|
|
1318
|
+
* @param {number} [level]
|
|
1319
|
+
* @returns {Iterable<string>}
|
|
1320
|
+
*/
|
|
1321
|
+
function* nodeToString(node, level = 0) {
|
|
1322
|
+
const { attrs, events, directives, children, is, name, classes, styles, aliases, vars } = node;
|
|
1323
|
+
const pad = level > 0 ? ''.padEnd(level, '\t') : '';
|
|
1324
|
+
|
|
1325
|
+
yield pad;
|
|
1326
|
+
yield* ['<', name || '-'];
|
|
1327
|
+
if (is) { yield* ['|', is]; }
|
|
1328
|
+
|
|
1329
|
+
for (const [name, value] of Object.entries(directives)) {
|
|
1330
|
+
if (value === false || value == null) { continue; }
|
|
1331
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1332
|
+
yield* [' !', name];
|
|
1333
|
+
if (val && typeof val === 'string') {
|
|
1334
|
+
yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
for (const [name, value] of Object.entries(aliases)) {
|
|
1338
|
+
if (value == null) { continue; }
|
|
1339
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1340
|
+
yield* [' *', name];
|
|
1341
|
+
if (val && typeof val === 'string') {
|
|
1342
|
+
yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
1346
|
+
if (value == null) { continue; }
|
|
1347
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1348
|
+
yield* [' +', name];
|
|
1349
|
+
if (val && typeof val === 'string') {
|
|
1350
|
+
yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
1354
|
+
if (value == null) { continue; }
|
|
1355
|
+
if (typeof value === 'string') {
|
|
1356
|
+
yield* [' ', name];
|
|
1357
|
+
if (value) {
|
|
1358
|
+
yield* ['="', value.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1359
|
+
}
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
const val = typeof value === 'function' ? String(value) : typeof value === 'object' ? value.name : value;
|
|
1363
|
+
if (val && typeof val === 'string') {
|
|
1364
|
+
yield* [' :', name, '="', val.replace(/[<&"]/g, _xmlEncoder) || '', '"'];
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
for (const [name, value] of Object.entries(events)) {
|
|
1368
|
+
if (value == null) { continue; }
|
|
1369
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1370
|
+
if (val && typeof val === 'string') {
|
|
1371
|
+
yield* [' @', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
for (const [name, value] of Object.entries(classes)) {
|
|
1375
|
+
if (value == null || value == false) { continue; }
|
|
1376
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1377
|
+
yield* [' .', name];
|
|
1378
|
+
if (val && typeof val === 'string') {
|
|
1379
|
+
yield* [' .', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
for (const [name, value] of Object.entries(styles)) {
|
|
1383
|
+
if (value == null) { continue; }
|
|
1384
|
+
const val = typeof value === 'function' ? String(value) : value;
|
|
1385
|
+
if (val && typeof val === 'string') {
|
|
1386
|
+
yield* [' style:', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (!children.length) {
|
|
1390
|
+
yield '/>';
|
|
1391
|
+
if (level >= 0) { yield '\n'; }
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
if (children.length === 1) {
|
|
1395
|
+
const [child] = children;
|
|
1396
|
+
if (typeof child === 'string' && child.length < 80 && !child.includes('\n')) {
|
|
1397
|
+
yield '>';
|
|
1398
|
+
yield* [child.replace(/[<&\t]/g, _xmlEncoder).replace(/]]>/g, ']]>')];
|
|
1399
|
+
yield* ['</', name, '>'];
|
|
1400
|
+
if (level >= 0) { yield '\n'; }
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
yield '>';
|
|
1405
|
+
if (level >= 0) { yield '\n'; }
|
|
1406
|
+
yield* listToString(children, level >= 0 ? level + 1 : -1);
|
|
1407
|
+
yield* [pad, '</', name, '>'];
|
|
1408
|
+
if (level >= 0) { yield '\n'; }
|
|
1409
|
+
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
*
|
|
1413
|
+
* @param {(Layout.Node |string)[]} nodes
|
|
1414
|
+
* @param {number} [level]
|
|
1415
|
+
* @returns {Iterable<string>}
|
|
1416
|
+
*/
|
|
1417
|
+
function* listToString(nodes, level = 0) {
|
|
1418
|
+
if (!nodes.length) { return ''; }
|
|
1419
|
+
|
|
1420
|
+
const pad = level > 0 ? ''.padEnd(level, '\t') : '';
|
|
1421
|
+
for (const child of nodes) {
|
|
1422
|
+
if (typeof child === 'string') {
|
|
1423
|
+
let text = child.replace(/[<&\t]/g, _xmlEncoder).replace(/]]>/g, ']]>');
|
|
1424
|
+
if (pad) {
|
|
1425
|
+
text = text.replace(/(?<=^|\n)/g, pad);
|
|
1426
|
+
}
|
|
1427
|
+
yield text;
|
|
1428
|
+
if (level >= 0) { yield '\n'; }
|
|
1429
|
+
} else {
|
|
1430
|
+
yield* nodeToString(child, level);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
*
|
|
1436
|
+
* @param {Layout.Node | (Layout.Node |string)[]} value
|
|
1437
|
+
* @param {boolean} [formable]
|
|
1438
|
+
* @returns {string}
|
|
1439
|
+
*/
|
|
1440
|
+
function toString(value, formable) {
|
|
1441
|
+
const level = formable ? 0 : -1;
|
|
1442
|
+
if (Array.isArray(value)) { return [...listToString(value, level)].join(''); }
|
|
1443
|
+
return [nodeToString(value, level)].join();
|
|
1444
|
+
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* @typedef {object} Directives
|
|
1449
|
+
*
|
|
1450
|
+
* @property {boolean} [fragment]
|
|
1451
|
+
*
|
|
1452
|
+
* @property {string | Function} [if]
|
|
1453
|
+
* @property {boolean} [else]
|
|
1454
|
+
*
|
|
1455
|
+
* @property {string} [value] 值关联(关联为列表)
|
|
1456
|
+
* @property {boolean} [enum] 列表属性枚举
|
|
1457
|
+
*
|
|
1458
|
+
* @property {string} [bind]
|
|
1459
|
+
* @property {string | Function} [text]
|
|
1460
|
+
* @property {string | Function} [html]
|
|
1461
|
+
*
|
|
1462
|
+
* @property {string | Function} [comment] 注释
|
|
1463
|
+
*/
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* @typedef {object} Options
|
|
1469
|
+
* @property {(t: string) => (vars: Record<string, any>) => any} [options.creteCalc]
|
|
1470
|
+
* @property {(t: string) => ($event: any, vars: Record<string, any>) => any} [options.creteEvent]
|
|
1471
|
+
* @property {Set<string>} [options.simpleTag]
|
|
1472
|
+
*/
|
|
1473
|
+
/**
|
|
1474
|
+
* @typedef {object} Node
|
|
1475
|
+
* @property {string} name
|
|
1476
|
+
* @property {string?} [is]
|
|
1477
|
+
* @property {string} [id]
|
|
1478
|
+
* @property {Record<string, string | {name: string} | ((global: any) => void)>} attrs
|
|
1479
|
+
* @property {Record<string, string | boolean | ((global: any) => void)>} classes
|
|
1480
|
+
* @property {Record<string, string | ((global: any) => void)>} styles
|
|
1481
|
+
* @property {Record<string, string | (($event: any, global: any) => void)>} events
|
|
1482
|
+
* @property {Record<string, string | ((global: any) => void)>} vars
|
|
1483
|
+
* @property {Record<string, string | Function>} aliases
|
|
1484
|
+
* @property {Directives} directives
|
|
1485
|
+
* @property {(Node | string)[]} children
|
|
1486
|
+
*/
|
|
1487
|
+
|
|
1488
|
+
var index$1 = /*#__PURE__*/Object.freeze({
|
|
1489
|
+
__proto__: null,
|
|
1490
|
+
parse: parse,
|
|
1491
|
+
stringify: toString
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
let needsEnqueue = true;
|
|
1495
|
+
|
|
1496
|
+
const w = new Signal.subtle.Watcher(() => {
|
|
1497
|
+
if (needsEnqueue) {
|
|
1498
|
+
needsEnqueue = false;
|
|
1499
|
+
queueMicrotask(processPending);
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
function processPending() {
|
|
1504
|
+
needsEnqueue = true;
|
|
1505
|
+
|
|
1506
|
+
for (const s of w.getPending()) {
|
|
1507
|
+
s.get();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
w.watch();
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* 创建可赋值计算值
|
|
1515
|
+
* @template T
|
|
1516
|
+
* @param {() => T} getter 取值方法
|
|
1517
|
+
* @param {(value: T) => void} callback 取值方法
|
|
1518
|
+
* @returns {() => void}
|
|
1519
|
+
*/
|
|
1520
|
+
function watch(getter, callback) {
|
|
1521
|
+
|
|
1522
|
+
let run = false;
|
|
1523
|
+
/** @type {any} */
|
|
1524
|
+
let value;
|
|
1525
|
+
const computed = new Signal.Computed(() => {
|
|
1526
|
+
const val = getter();
|
|
1527
|
+
if (run && Object.is(val, value)) { return; }
|
|
1528
|
+
value = val;
|
|
1529
|
+
run = true;
|
|
1530
|
+
callback(val);
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
w.watch(computed);
|
|
1534
|
+
computed.get();
|
|
1535
|
+
|
|
1536
|
+
return () => { w.unwatch(computed); };
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
/** @typedef {{get(): any; set?(v: any): void; exec?: null; store?: Store; calc?: null; }} ValueDefine */
|
|
1540
|
+
/** @typedef {{get?: null; exec(...p: any[]): any; calc?: null}} ExecDefine */
|
|
1541
|
+
/** @typedef {{get?: null; calc(...p: any[]): any; exec?: null;}} CalcDefine */
|
|
1542
|
+
/**
|
|
1543
|
+
*
|
|
1544
|
+
* @param {Store} val
|
|
1545
|
+
* @param {string | number} [key]
|
|
1546
|
+
* @returns {Iterable<[string, ValueDefine | ExecDefine | CalcDefine]>}
|
|
1547
|
+
*/
|
|
1548
|
+
function *toItem(val, key = '', sign = '$') {
|
|
1549
|
+
yield [`${key}`, {get: () => val.value, set: v => val.value = v, store: val}];
|
|
1550
|
+
yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
|
|
1551
|
+
yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
|
|
1552
|
+
yield [`${key}${sign}null`, {get: () => val.null}];
|
|
1553
|
+
yield [`${key}${sign}index`, {get: () => val.index}];
|
|
1554
|
+
yield [`${key}${sign}no`, {get: () => val.no}];
|
|
1555
|
+
yield [`${key}${sign}length`, {get: () => val.length}];
|
|
1556
|
+
yield [`${key}${sign}creatable`, {get: () => val.creatable}];
|
|
1557
|
+
yield [`${key}${sign}immutable`, {get: () => val.immutable}];
|
|
1558
|
+
yield [`${key}${sign}new`, {get: () => val.new}];
|
|
1559
|
+
yield [`${key}${sign}editable`, {get: () => val.editable}];
|
|
1560
|
+
yield [`${key}${sign}hidden`, {get: () => val.hidden}];
|
|
1561
|
+
yield [`${key}${sign}clearable`, {get: () => val.clearable}];
|
|
1562
|
+
yield [`${key}${sign}required`, {get: () => val.required}];
|
|
1563
|
+
yield [`${key}${sign}disabled`, {get: () => val.disabled}];
|
|
1564
|
+
yield [`${key}${sign}readonly`, {get: () => val.readonly}];
|
|
1565
|
+
if (!(val instanceof ArrayStore)) { return; }
|
|
1566
|
+
yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
|
|
1567
|
+
yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
|
|
1568
|
+
yield [`${key}${sign}remove`, {exec: (index) => val.remove(index)}];
|
|
1569
|
+
yield [`${key}${sign}move`, {exec: (from, to) => val.move(from, to)}];
|
|
1570
|
+
yield [`${key}${sign}exchange`, {exec: (a, b) => val.exchange(a, b)}];
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
*
|
|
1574
|
+
* @param {Store?} parent
|
|
1575
|
+
* @param {Store} val
|
|
1576
|
+
* @param {string | number} [key]
|
|
1577
|
+
* @returns {Iterable<[string, ValueDefine | ExecDefine | CalcDefine]>}
|
|
1578
|
+
*/
|
|
1579
|
+
function *toParentItem(parent, val, key = '', sign = '$') {
|
|
1580
|
+
if (!(parent instanceof ArrayStore)) {
|
|
1581
|
+
yield [`${key}${sign}upMovable`, {get: () => false}];
|
|
1582
|
+
yield [`${key}${sign}downMovable`, {get: () => false}];
|
|
1583
|
+
return
|
|
1584
|
+
}
|
|
1585
|
+
yield [`${key}${sign}upMovable`, {get: () => {
|
|
1586
|
+
const s = val.index;
|
|
1587
|
+
if (typeof s !== 'number') { return false; }
|
|
1588
|
+
if (s <= 0) { return false; }
|
|
1589
|
+
return true;
|
|
1590
|
+
}}];
|
|
1591
|
+
yield [`${key}${sign}downMovable`, {get: () => {
|
|
1592
|
+
const s = val.index;
|
|
1593
|
+
if (typeof s !== 'number') { return false; }
|
|
1594
|
+
if (s >= parent.length - 1) { return false; }
|
|
1595
|
+
return true;
|
|
1596
|
+
}}];
|
|
1597
|
+
yield [`${key}${sign}remove`, {exec: () => parent.remove(Number(val.index))}];
|
|
1598
|
+
yield [`${key}${sign}upMove`, {exec: () => {
|
|
1599
|
+
const s = val.index;
|
|
1600
|
+
if (typeof s !== 'number') { return; }
|
|
1601
|
+
if (s <= 0) { return; }
|
|
1602
|
+
parent.move(s, s - 1);
|
|
1603
|
+
}}];
|
|
1604
|
+
yield [`${key}${sign}downMove`, {exec: () => {
|
|
1605
|
+
const s = val.index;
|
|
1606
|
+
if (typeof s !== 'number') { return; }
|
|
1607
|
+
if (s >= parent.length - 1) { return; }
|
|
1608
|
+
parent.move(s, s + 1);
|
|
1609
|
+
}}];
|
|
1610
|
+
}
|
|
1611
|
+
class Environment {
|
|
1612
|
+
/**
|
|
1613
|
+
* @param {string | Function} value
|
|
1614
|
+
*/
|
|
1615
|
+
exec(value) {
|
|
1616
|
+
if (typeof value === 'string') {
|
|
1617
|
+
const item = this.#items[value];
|
|
1618
|
+
if (typeof item?.get !== 'function') { return }
|
|
1619
|
+
return item.get();
|
|
1620
|
+
}
|
|
1621
|
+
if (typeof value === 'function') {
|
|
1622
|
+
return value(this.getters);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* @param {string | Function} value
|
|
1627
|
+
* @param {(value: any) => void} cb
|
|
1628
|
+
*/
|
|
1629
|
+
watch(value, cb) { return watch(() => this.exec(value), cb); }
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* @param {string} name
|
|
1633
|
+
* @param {string} type
|
|
1634
|
+
* @param {(value: any) => void} cb
|
|
1635
|
+
*/
|
|
1636
|
+
bind(name, type, cb) {
|
|
1637
|
+
const item = this.#items[name];
|
|
1638
|
+
if (!item?.get) { return; }
|
|
1639
|
+
const { store } = item;
|
|
1640
|
+
if (!store) { return; }
|
|
1641
|
+
switch(type) {
|
|
1642
|
+
case 'value': return watch(() => store.value, cb);
|
|
1643
|
+
case 'state': return watch(() => store.state, cb);
|
|
1644
|
+
case 'required': return watch(() => store.required, cb);
|
|
1645
|
+
case 'clearable': return watch(() => store.clearable, cb);
|
|
1646
|
+
case 'hidden': return watch(() => store.hidden, cb);
|
|
1647
|
+
case 'disabled': return watch(() => store.disabled, cb);
|
|
1648
|
+
case 'readonly': return watch(() => store.readonly || !store.editable, cb);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* @param {string} name
|
|
1653
|
+
* @returns {Record<string, ((cb: (value: any) => void) => () => void) | void> | void}
|
|
1654
|
+
*/
|
|
1655
|
+
bindAll(name) {
|
|
1656
|
+
const item = this.#items[name];
|
|
1657
|
+
if (!item?.get) { return; }
|
|
1658
|
+
const { store } = item;
|
|
1659
|
+
if (!store) {
|
|
1660
|
+
const get = item.get;
|
|
1661
|
+
if (typeof get !== 'function') { return; }
|
|
1662
|
+
return { '$value': cb => watch(get, cb) }
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
'$value': cb => watch(() => store.value, cb),
|
|
1666
|
+
'$state': cb => watch(() => store.state, cb),
|
|
1667
|
+
'$required': cb => watch(() => store.required, cb),
|
|
1668
|
+
'$clearable': cb => watch(() => store.clearable, cb),
|
|
1669
|
+
'$hidden': cb => watch(() => store.hidden, cb),
|
|
1670
|
+
'$disabled': cb => watch(() => store.disabled, cb),
|
|
1671
|
+
'$readonly': cb => watch(() => store.readonly || !store.editable, cb),
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* @param {string} name
|
|
1676
|
+
* @param {string} type
|
|
1677
|
+
* @returns {((value: any) => void) | void}
|
|
1678
|
+
*/
|
|
1679
|
+
bindSet(name, type) {
|
|
1680
|
+
const item = this.#items[name];
|
|
1681
|
+
if (!item?.get) { return; }
|
|
1682
|
+
const { store } = item;
|
|
1683
|
+
if (!store) { return; }
|
|
1684
|
+
switch(type) {
|
|
1685
|
+
case 'value': return v => {store.value = v; };
|
|
1686
|
+
case 'state': return v => {store.state = v; };
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* @param {string} name
|
|
1691
|
+
* @returns {Record<string, ((value: any) => void) | void> | void}
|
|
1692
|
+
*/
|
|
1693
|
+
bindStateAllSet(name) {
|
|
1694
|
+
const item = this.#items[name];
|
|
1695
|
+
if (!item?.get) { return; }
|
|
1696
|
+
const { store } = item;
|
|
1697
|
+
if (!store) {
|
|
1698
|
+
const set = item.set;
|
|
1699
|
+
if (typeof set !== 'function') { return; }
|
|
1700
|
+
return { '$value': set }
|
|
1701
|
+
}
|
|
1702
|
+
return {
|
|
1703
|
+
'$value': v => {store.value = v; },
|
|
1704
|
+
'$state': v => {store.state = v; },
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* @param {string | (($event: any, global: any) => any)} event
|
|
1710
|
+
* @returns {(($event: any, global: any) => any)?}
|
|
1711
|
+
*/
|
|
1712
|
+
getEvent(event) {
|
|
1713
|
+
if (typeof event === 'function') { return event }
|
|
1714
|
+
const item = this.#items[event];
|
|
1715
|
+
if (!item) { return null }
|
|
1716
|
+
const {exec, calc} = item;
|
|
1717
|
+
if (typeof exec === 'function') { return exec }
|
|
1718
|
+
if (typeof calc === 'function') { return calc }
|
|
1719
|
+
return null
|
|
1720
|
+
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
*
|
|
1724
|
+
* @param {Environment | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>?} [global]
|
|
1725
|
+
*/
|
|
1726
|
+
constructor(global) {
|
|
1727
|
+
if (global instanceof Environment) {
|
|
1728
|
+
this.#global = global.#global;
|
|
1729
|
+
const schemaItems = this.#schemaItems;
|
|
1730
|
+
for (const [k, v] of Object.entries(global.#schemaItems)) {
|
|
1731
|
+
schemaItems[k] = v;
|
|
1732
|
+
}
|
|
1733
|
+
const explicit = this.#explicit;
|
|
1734
|
+
for (const [k, v] of Object.entries(global.#explicit)) {
|
|
1735
|
+
explicit[k] = v;
|
|
1736
|
+
}
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
const items = Object.create(null);
|
|
1740
|
+
this.#global = items;
|
|
1741
|
+
if (!global) { return }
|
|
1742
|
+
if (typeof global !== 'object') { return; }
|
|
1743
|
+
for (const [key, value] of Object.entries(global)) {
|
|
1744
|
+
if (!key || key.includes('$')) { continue; }
|
|
1745
|
+
if (!value || typeof value !== 'object') { return; }
|
|
1746
|
+
if (value instanceof Store) {
|
|
1747
|
+
for (const [k, v] of toItem(value, key)) {
|
|
1748
|
+
items[k] = v;
|
|
1749
|
+
}
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const {get,set,exec,calc} = value;
|
|
1753
|
+
if (typeof get === 'function') {
|
|
1754
|
+
items[key] = typeof set === 'function' ? {get,set} : {get};
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
if (typeof calc === 'function') {
|
|
1758
|
+
items[key] = {calc};
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
if (typeof exec === 'function') {
|
|
1762
|
+
items[key] = {exec};
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
/** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
|
|
1768
|
+
#global
|
|
1769
|
+
/** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
|
|
1770
|
+
#schemaItems = Object.create(null);
|
|
1771
|
+
/** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
|
|
1772
|
+
#explicit = Object.create(null);
|
|
1773
|
+
/** @type {Store?} */
|
|
1774
|
+
#store = null
|
|
1775
|
+
/** @type {Store?} */
|
|
1776
|
+
#parent = null
|
|
1777
|
+
/** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>?} */
|
|
1778
|
+
#allItems = null
|
|
1779
|
+
get #items() {
|
|
1780
|
+
const ai = this.#allItems;
|
|
1781
|
+
if (ai) { return ai; }
|
|
1782
|
+
/** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
|
|
1783
|
+
const ais = Object.create(null, Object.getOwnPropertyDescriptors({
|
|
1784
|
+
...this.#schemaItems,
|
|
1785
|
+
...this.#global,
|
|
1786
|
+
...this.#explicit,
|
|
1787
|
+
}));
|
|
1788
|
+
const store = this.#store;
|
|
1789
|
+
const parent = this.#parent;
|
|
1790
|
+
if (store) {
|
|
1791
|
+
for (const [key, item] of toItem(store)) {
|
|
1792
|
+
ais[key] = item;
|
|
1793
|
+
}
|
|
1794
|
+
for (const [key, item] of toParentItem(parent, store)) {
|
|
1795
|
+
ais[key] = item;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
this.#allItems = ais;
|
|
1799
|
+
return ais;
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
*
|
|
1803
|
+
* @param {Store} store
|
|
1804
|
+
* @param {Store} [parent]
|
|
1805
|
+
*/
|
|
1806
|
+
setValue(store, parent) {
|
|
1807
|
+
const cloned = new Environment(this);
|
|
1808
|
+
cloned.#store = store;
|
|
1809
|
+
if (parent) { cloned.#parent = parent; }
|
|
1810
|
+
if (store instanceof ArrayStore) { return cloned; }
|
|
1811
|
+
const items = cloned.#schemaItems;
|
|
1812
|
+
for (const [name, val] of store) {
|
|
1813
|
+
for (const [b, x] of toItem(val, name)) {
|
|
1814
|
+
items[b] = x;
|
|
1815
|
+
}
|
|
1816
|
+
for (const [b, x] of toItem(val, name, '$$')) {
|
|
1817
|
+
items[b] = x;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
return cloned;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
*
|
|
1824
|
+
* @param {Record<string, string | Function>} aliases
|
|
1825
|
+
* @param {Record<string, any>} vars
|
|
1826
|
+
*/
|
|
1827
|
+
set(aliases, vars) {
|
|
1828
|
+
if (Object.keys(aliases).length + Object.keys(vars).length === 0) { return this; }
|
|
1829
|
+
const cloned = new Environment(this);
|
|
1830
|
+
cloned.#store = this.#store;
|
|
1831
|
+
cloned.#parent = this.#parent;
|
|
1832
|
+
const explicit = cloned.#explicit;
|
|
1833
|
+
const items = cloned.#items;
|
|
1834
|
+
for (const [key, name] of Object.entries(aliases)) {
|
|
1835
|
+
if (typeof name === 'function') {
|
|
1836
|
+
const getters = cloned.getters;
|
|
1837
|
+
cloned.#getters = null;
|
|
1838
|
+
const val = new Signal.Computed(() => name(getters));
|
|
1839
|
+
explicit[key] = items[key] = {
|
|
1840
|
+
get: () => { return val.get(); },
|
|
1841
|
+
};
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
const item = items[name];
|
|
1845
|
+
if (!item) { continue; }
|
|
1846
|
+
if (!item.get || !item.store) {
|
|
1847
|
+
explicit[key] = items[key] = item;
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
for (const [k, it] of toItem(item.store, key)) {
|
|
1851
|
+
explicit[k] = items[k] = it;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
for (const [k,v] of Object.entries(vars)) {
|
|
1855
|
+
|
|
1856
|
+
const val = new Signal.State(/** @type {any} */(null));
|
|
1857
|
+
if (typeof v === 'function') {
|
|
1858
|
+
const settable = cloned.settable;
|
|
1859
|
+
cloned.#settable = null;
|
|
1860
|
+
val.set(v(settable));
|
|
1861
|
+
} else if (v && typeof v === 'string') {
|
|
1862
|
+
const item = items[v];
|
|
1863
|
+
if (!item?.get) { continue }
|
|
1864
|
+
val.set(item.get());
|
|
1865
|
+
}
|
|
1866
|
+
explicit[k] = items[k] = {
|
|
1867
|
+
get: () => { return val.get(); },
|
|
1868
|
+
set: (v) => { val.set(v); },
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
return cloned;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/** @type {Record<string, any>?} */
|
|
1875
|
+
#all = null;
|
|
1876
|
+
get all() {
|
|
1877
|
+
const gt = this.#all;
|
|
1878
|
+
if (gt) { return gt; }
|
|
1879
|
+
/** @type {Record<string, any>} */
|
|
1880
|
+
const ngt = {};
|
|
1881
|
+
for (const [key, item] of Object.entries(this.#items)) {
|
|
1882
|
+
if (item.get) {
|
|
1883
|
+
Object.defineProperty(ngt, key, {
|
|
1884
|
+
get: item.get,
|
|
1885
|
+
set: item.set,
|
|
1886
|
+
configurable: true,
|
|
1887
|
+
enumerable: true,
|
|
1888
|
+
});
|
|
1889
|
+
} else if (item.calc) {
|
|
1890
|
+
Object.defineProperty(ngt, key, {
|
|
1891
|
+
value: item.calc,
|
|
1892
|
+
writable: false,
|
|
1893
|
+
configurable: true,
|
|
1894
|
+
enumerable: false,
|
|
1895
|
+
});
|
|
1896
|
+
} else {
|
|
1897
|
+
Object.defineProperty(ngt, key, {
|
|
1898
|
+
value: item.exec,
|
|
1899
|
+
writable: false,
|
|
1900
|
+
configurable: true,
|
|
1901
|
+
enumerable: false,
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
this.#all = ngt;
|
|
1906
|
+
return ngt;
|
|
1907
|
+
}
|
|
1908
|
+
/** @type {Record<string, any>?} */
|
|
1909
|
+
#settable = null;
|
|
1910
|
+
get settable() {
|
|
1911
|
+
const gt = this.#settable;
|
|
1912
|
+
if (gt) { return gt; }
|
|
1913
|
+
/** @type {Record<string, any>} */
|
|
1914
|
+
const ngt = {};
|
|
1915
|
+
for (const [key, item] of Object.entries(this.#items)) {
|
|
1916
|
+
if (item.get) {
|
|
1917
|
+
Object.defineProperty(ngt, key, {
|
|
1918
|
+
get: item.get,
|
|
1919
|
+
set: item.set,
|
|
1920
|
+
configurable: true,
|
|
1921
|
+
enumerable: true,
|
|
1922
|
+
});
|
|
1923
|
+
} else if (item.calc) {
|
|
1924
|
+
Object.defineProperty(ngt, key, {
|
|
1925
|
+
value: item.calc,
|
|
1926
|
+
writable: false,
|
|
1927
|
+
configurable: true,
|
|
1928
|
+
enumerable: false,
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
this.#settable = ngt;
|
|
1933
|
+
return ngt;
|
|
1934
|
+
}
|
|
1935
|
+
/** @type {Record<string, any>?} */
|
|
1936
|
+
#getters = null;
|
|
1937
|
+
get getters() {
|
|
1938
|
+
const gt = this.#getters;
|
|
1939
|
+
if (gt) { return gt; }
|
|
1940
|
+
/** @type {Record<string, any>} */
|
|
1941
|
+
const ngt = {};
|
|
1942
|
+
for (const [key, item] of Object.entries(this.#items)) {
|
|
1943
|
+
if (item.get) {
|
|
1944
|
+
Object.defineProperty(ngt, key, {
|
|
1945
|
+
get: item.get,
|
|
1946
|
+
configurable: true,
|
|
1947
|
+
enumerable: true,
|
|
1948
|
+
});
|
|
1949
|
+
} else if (item.calc) {
|
|
1950
|
+
Object.defineProperty(ngt, key, {
|
|
1951
|
+
value: item.calc,
|
|
1952
|
+
writable: false,
|
|
1953
|
+
configurable: true,
|
|
1954
|
+
enumerable: false,
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
this.#getters = ngt;
|
|
1959
|
+
return ngt;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/** @import { Component } from '../types.mjs' */
|
|
1964
|
+
|
|
1965
|
+
/**
|
|
1966
|
+
* @param {Component.Handler} handler
|
|
1967
|
+
* @param {Environment} envs
|
|
1968
|
+
* @param {Record<string, string | {name: string} | ((...any: any) => void)>} attrs
|
|
1969
|
+
* @param {Record<string, Component.Attr>} componentAttrs
|
|
1970
|
+
* @param {string?} [bindValue]
|
|
1971
|
+
*/
|
|
1972
|
+
function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
|
|
1973
|
+
|
|
1974
|
+
let bk = new Set();
|
|
1975
|
+
for (const [name, attr] of Object.entries(componentAttrs)) {
|
|
1976
|
+
const attrValue = attrs[name];
|
|
1977
|
+
if (name in attrs) {
|
|
1978
|
+
if (typeof attrValue !== 'function' && typeof attrValue !== 'object') {
|
|
1979
|
+
handler.set(name, attrValue);
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
const attrSchema = typeof attrValue === 'function' ? attrValue : attrValue.name;
|
|
1983
|
+
if (attr.immutable) {
|
|
1984
|
+
handler.set(name, envs.exec(attrSchema));
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
bk.add(envs.watch(attrSchema, v => handler.set(name, v)));
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
const bind = attr.bind;
|
|
1991
|
+
if (!bindValue || !bind) {
|
|
1992
|
+
handler.set(name, attr.default);
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
if (typeof bind === 'string') {
|
|
1996
|
+
const r = envs.bind(bindValue, bind, v => handler.set(name, v));
|
|
1997
|
+
if (r) {
|
|
1998
|
+
bk.add(r);
|
|
1999
|
+
} else {
|
|
2000
|
+
handler.set(name, attr.default);
|
|
2001
|
+
}
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
if (!Array.isArray(bind)) {
|
|
2005
|
+
handler.set(name, attr.default);
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
const [event, set, isState] = bind;
|
|
2009
|
+
if (!event || typeof set !== 'function') {
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
if (!isState) {
|
|
2013
|
+
bk.add(envs.watch(bindValue, v => handler.set(name, v)));
|
|
2014
|
+
handler.addEvent(event, (...args) => { envs.all[bindValue] = set(...args);});
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
const r = envs.bind(bindValue, 'state', v => handler.set(name, v));
|
|
2018
|
+
if (!r) { continue; }
|
|
2019
|
+
bk.add(r);
|
|
2020
|
+
const s = envs.bindSet(bindValue, 'state');
|
|
2021
|
+
if (!s) { continue; }
|
|
2022
|
+
handler.addEvent(event, (...args) => { s(set(...args));});
|
|
2023
|
+
}
|
|
2024
|
+
// TODO: 创建组件
|
|
2025
|
+
return ()=> {
|
|
2026
|
+
const list = bk;
|
|
2027
|
+
bk = new Set();
|
|
2028
|
+
for (const s of list) {
|
|
2029
|
+
s();
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/** @import { Component } from '../types.mjs' */
|
|
2035
|
+
|
|
2036
|
+
/**
|
|
2037
|
+
* @param {Component.Handler} handler
|
|
2038
|
+
* @param {Environment} envs
|
|
2039
|
+
* @param {Record<string, string | {name: string} | ((...any: any) => void)>} attrs
|
|
2040
|
+
* @param {string?} [bindValue]
|
|
2041
|
+
*/
|
|
2042
|
+
function bindBaseAttrs(handler, envs, attrs, bindValue) {
|
|
2043
|
+
const tag = handler.tag;
|
|
2044
|
+
let bk = new Set();
|
|
2045
|
+
for (const [name, attr] of Object.entries(attrs)) {
|
|
2046
|
+
if (typeof attr !== 'function' && typeof attr !== 'object') {
|
|
2047
|
+
handler.set(name, attr);
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
const attrSchema = typeof attr === 'function' ? attr : attr.name;
|
|
2051
|
+
if (typeof tag === 'string' && tag.toLocaleLowerCase() === 'input' && name.toLocaleLowerCase() === 'type') {
|
|
2052
|
+
const value = envs.exec(attrSchema);
|
|
2053
|
+
handler.set(name, value);
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
bk.add(envs.watch(attrSchema, val => handler.set(name, val)));
|
|
2057
|
+
}
|
|
2058
|
+
if (bindValue) {
|
|
2059
|
+
for (const [key, effect] of Object.entries(envs.bindAll(bindValue) || {})) {
|
|
2060
|
+
if (typeof effect !== 'function') { continue; }
|
|
2061
|
+
bk.add(effect(val => handler.set(key, val)));
|
|
2062
|
+
}
|
|
2063
|
+
for (const [key, setter] of Object.entries(envs.bindStateAllSet(bindValue) || {})) {
|
|
2064
|
+
if (typeof setter !== 'function') { continue; }
|
|
2065
|
+
handler.addEvent(key, $event => setter($event));
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
return ()=> {
|
|
2070
|
+
const list = bk;
|
|
2071
|
+
bk = new Set();
|
|
2072
|
+
for (const s of list) {
|
|
2073
|
+
s();
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* @param {Node} node
|
|
2080
|
+
* @param {Environment} envs
|
|
2081
|
+
* @param {Record<string, string | boolean | ((...any: any) => void)>} classes
|
|
2082
|
+
*/
|
|
2083
|
+
function bindClasses(node, classes, envs) {
|
|
2084
|
+
if (!(node instanceof Element)) {
|
|
2085
|
+
return () => {};
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/** @type {Set<() => void>?} */
|
|
2089
|
+
let bk = new Set();
|
|
2090
|
+
for (const [name, attr] of Object.entries(classes)) {
|
|
2091
|
+
if (!attr) { continue; }
|
|
2092
|
+
if (attr === true) {
|
|
2093
|
+
node.classList.add(name);
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
bk.add(watch(() => Boolean(envs.exec(attr)), value => {
|
|
2097
|
+
if (value) {
|
|
2098
|
+
node.classList.add(name);
|
|
2099
|
+
} else {
|
|
2100
|
+
node.classList.remove(name);
|
|
2101
|
+
}
|
|
2102
|
+
}));
|
|
2103
|
+
}
|
|
2104
|
+
// TODO: 创建组件
|
|
2105
|
+
return ()=> {
|
|
2106
|
+
if (!bk) { return; }
|
|
2107
|
+
const list = bk;
|
|
2108
|
+
bk = null;
|
|
2109
|
+
for (const s of list) {
|
|
2110
|
+
s();
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/** @type {Record<string, string>} */
|
|
2116
|
+
const unit = {
|
|
2117
|
+
'width': 'px',
|
|
2118
|
+
'height': 'px',
|
|
2119
|
+
'top': 'px',
|
|
2120
|
+
'right': 'px',
|
|
2121
|
+
'bottom': 'px',
|
|
2122
|
+
'left': 'px',
|
|
2123
|
+
'border': 'px',
|
|
2124
|
+
'border-top': 'px',
|
|
2125
|
+
'border-right': 'px',
|
|
2126
|
+
'border-left': 'px',
|
|
2127
|
+
'border-bottom': 'px',
|
|
2128
|
+
'border-width': 'px',
|
|
2129
|
+
'border-top-width': 'px',
|
|
2130
|
+
'border-right-width': 'px',
|
|
2131
|
+
'border-left-width': 'px',
|
|
2132
|
+
'border-bottom-width': 'px',
|
|
2133
|
+
'border-radius': 'px',
|
|
2134
|
+
'border-top-left-radius': 'px',
|
|
2135
|
+
'border-top-right-radius': 'px',
|
|
2136
|
+
'border-bottom-left-radius': 'px',
|
|
2137
|
+
'border-bottom-right-radius': 'px',
|
|
2138
|
+
'padding': 'px',
|
|
2139
|
+
'padding-top': 'px',
|
|
2140
|
+
'padding-right': 'px',
|
|
2141
|
+
'padding-left': 'px',
|
|
2142
|
+
'padding-bottom': 'px',
|
|
2143
|
+
'margin': 'px',
|
|
2144
|
+
'margin-top': 'px',
|
|
2145
|
+
'margin-right': 'px',
|
|
2146
|
+
'margin-left': 'px',
|
|
2147
|
+
'margin-bottom': 'px',
|
|
2148
|
+
};
|
|
2149
|
+
/**
|
|
2150
|
+
*
|
|
2151
|
+
* @param {string} name
|
|
2152
|
+
* @param {any} value
|
|
2153
|
+
* @returns {[string, 'important' | undefined]?}
|
|
2154
|
+
*/
|
|
2155
|
+
function toStyle(name, value) {
|
|
2156
|
+
let important = Array.isArray(value) ? Boolean(value[1]) : false;
|
|
2157
|
+
let style = '';
|
|
2158
|
+
/** @typedef {[string, 'important' | undefined]} Result */
|
|
2159
|
+
if (typeof value === 'string') {
|
|
2160
|
+
style = value.replace(/!important\s*$/, '');
|
|
2161
|
+
if (!style) { return null; }
|
|
2162
|
+
important = style !== value;
|
|
2163
|
+
return [style, important ? 'important' : undefined];
|
|
2164
|
+
}
|
|
2165
|
+
const val = Array.isArray(value) ? value[0] : value;
|
|
2166
|
+
if (typeof val === 'number' || typeof val === 'bigint') {
|
|
2167
|
+
style = val && name in unit ? `${ val }${ unit[name] }` : `${ val }`;
|
|
2168
|
+
} else if (typeof val === 'string') {
|
|
2169
|
+
style = val;
|
|
2170
|
+
}
|
|
2171
|
+
if (!style) { return null; }
|
|
2172
|
+
return [style, important ? 'important' : undefined];
|
|
2173
|
+
|
|
2174
|
+
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* @param {Element} node
|
|
2180
|
+
* @param {Environment} envs
|
|
2181
|
+
* @param {Record<string, string | ((...any: any) => void)>} classes
|
|
2182
|
+
*/
|
|
2183
|
+
function bindStyles(node, classes, envs) {
|
|
2184
|
+
if (!(node instanceof HTMLElement) && !(node instanceof SVGElement)) {
|
|
2185
|
+
return () => {};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/** @type {Set<() => void>?} */
|
|
2189
|
+
let bk = new Set();
|
|
2190
|
+
for (const [name, attr] of Object.entries(classes)) {
|
|
2191
|
+
bk.add(watch(() => toStyle(name, envs.exec(attr)), value => {
|
|
2192
|
+
if (value) {
|
|
2193
|
+
node.style.setProperty(name, ...value);
|
|
2194
|
+
} else {
|
|
2195
|
+
node.style.removeProperty(name);
|
|
2196
|
+
}
|
|
2197
|
+
}));
|
|
2198
|
+
}
|
|
2199
|
+
// TODO: 创建组件
|
|
2200
|
+
return ()=> {
|
|
2201
|
+
if (!bk) { return; }
|
|
2202
|
+
const list = bk;
|
|
2203
|
+
bk = null;
|
|
2204
|
+
for (const s of list) {
|
|
2205
|
+
s();
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
/**
|
|
2211
|
+
* @template {Record<string, any[]>} T
|
|
2212
|
+
*/
|
|
2213
|
+
class EventEmitter {
|
|
2214
|
+
/** @type {Map<keyof T, Set<(...p: any[]) => void>>} */
|
|
2215
|
+
#events = new Map()
|
|
2216
|
+
/**
|
|
2217
|
+
*
|
|
2218
|
+
* @template {keyof T} K
|
|
2219
|
+
* @param {K} event
|
|
2220
|
+
* @param {T[K]} p
|
|
2221
|
+
*/
|
|
2222
|
+
emit(event, ...p) {
|
|
2223
|
+
const key = typeof event === 'number' ? String(event) : event;
|
|
2224
|
+
const events = this.#events;
|
|
2225
|
+
for (const d of [...events.get(key) || []]) {
|
|
2226
|
+
d(...p);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
*
|
|
2231
|
+
* @template {keyof T} K
|
|
2232
|
+
* @param {K} event
|
|
2233
|
+
* @param {(...p: T[K]) => void} listener
|
|
2234
|
+
* @returns {() => void}
|
|
2235
|
+
*/
|
|
2236
|
+
listen(event, listener) {
|
|
2237
|
+
/** @type {any} */
|
|
2238
|
+
const fn = listener.bind(this);
|
|
2239
|
+
const events = this.#events;
|
|
2240
|
+
const key = typeof event === 'number' ? String(event) : event;
|
|
2241
|
+
let set = events.get(key);
|
|
2242
|
+
if (!set) {
|
|
2243
|
+
set = new Set();
|
|
2244
|
+
events.set(key, set);
|
|
2245
|
+
}
|
|
2246
|
+
set.add(fn);
|
|
2247
|
+
return () => { set?.delete(fn); }
|
|
2248
|
+
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/** @import { Component } from '../types.mjs' */
|
|
2253
|
+
/** @import Environment from './Environment.mjs' */
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
/** @type {Record<string, (evt: any, param: string[], global: any) => boolean | null | void>} */
|
|
2257
|
+
const eventFilters = {
|
|
2258
|
+
stop(evt) {
|
|
2259
|
+
if (evt instanceof Event) { evt.stopPropagation(); }
|
|
2260
|
+
},
|
|
2261
|
+
prevent(evt) {
|
|
2262
|
+
if (evt instanceof Event) { evt.preventDefault(); }
|
|
2263
|
+
},
|
|
2264
|
+
self(evt) {
|
|
2265
|
+
if (evt instanceof Event) { return evt.target === evt.currentTarget; }
|
|
2266
|
+
},
|
|
2267
|
+
enter(evt) {
|
|
2268
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Enter'; }
|
|
2269
|
+
},
|
|
2270
|
+
tab(evt) {
|
|
2271
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Tab'; }
|
|
2272
|
+
},
|
|
2273
|
+
esc(evt) {
|
|
2274
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Escape'; }
|
|
2275
|
+
},
|
|
2276
|
+
space(evt) {
|
|
2277
|
+
if (evt instanceof KeyboardEvent) { return evt.key === ' '; }
|
|
2278
|
+
},
|
|
2279
|
+
backspace(evt) {
|
|
2280
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Backspace'; }
|
|
2281
|
+
},
|
|
2282
|
+
delete(evt) {
|
|
2283
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Delete'; }
|
|
2284
|
+
},
|
|
2285
|
+
delBack(evt) {
|
|
2286
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Delete' || evt.key === 'Backspace'; }
|
|
2287
|
+
},
|
|
2288
|
+
'del-back'(evt) {
|
|
2289
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Delete' || evt.key === 'Backspace'; }
|
|
2290
|
+
},
|
|
2291
|
+
insert(evt) {
|
|
2292
|
+
if (evt instanceof KeyboardEvent) { return evt.key === 'Insert'; }
|
|
2293
|
+
},
|
|
2294
|
+
repeat(evt) {
|
|
2295
|
+
if (evt instanceof KeyboardEvent) { return evt.repeat; }
|
|
2296
|
+
},
|
|
2297
|
+
|
|
2298
|
+
key(evt, param) {
|
|
2299
|
+
if (evt instanceof KeyboardEvent) {
|
|
2300
|
+
const key = evt.code.toLowerCase().replace(/-/g, '');
|
|
2301
|
+
for (const k of param) {
|
|
2302
|
+
if (key === k.toLowerCase().replace(/-/g, '')) { return true }
|
|
2303
|
+
}
|
|
2304
|
+
return false;
|
|
2305
|
+
}
|
|
2306
|
+
},
|
|
2307
|
+
main(evt) {
|
|
2308
|
+
if (evt instanceof MouseEvent) { return evt.button === 0; }
|
|
2309
|
+
},
|
|
2310
|
+
auxiliary(evt) {
|
|
2311
|
+
if (evt instanceof MouseEvent) { return evt.button === 1; }
|
|
2312
|
+
},
|
|
2313
|
+
secondary(evt) {
|
|
2314
|
+
if (evt instanceof MouseEvent) { return evt.button === 2; }
|
|
2315
|
+
},
|
|
2316
|
+
left(evt) {
|
|
2317
|
+
if (evt instanceof MouseEvent) { return evt.button === 0; }
|
|
2318
|
+
},
|
|
2319
|
+
middle(evt) {
|
|
2320
|
+
if (evt instanceof MouseEvent) { return evt.button === 1; }
|
|
2321
|
+
},
|
|
2322
|
+
right(evt) {
|
|
2323
|
+
if (evt instanceof MouseEvent) { return evt.button === 2; }
|
|
2324
|
+
},
|
|
2325
|
+
primary(evt) {
|
|
2326
|
+
if (evt instanceof PointerEvent) { return evt.isPrimary; }
|
|
2327
|
+
},
|
|
2328
|
+
mouse(evt) {
|
|
2329
|
+
if (evt instanceof PointerEvent) { return evt.pointerType === 'mouse'; }
|
|
2330
|
+
},
|
|
2331
|
+
pen(evt) {
|
|
2332
|
+
if (evt instanceof PointerEvent) { return evt.pointerType === 'pen'; }
|
|
2333
|
+
},
|
|
2334
|
+
touch(evt) {
|
|
2335
|
+
if (evt instanceof PointerEvent) { return evt.pointerType === 'touch'; }
|
|
2336
|
+
},
|
|
2337
|
+
pointer(evt, param) {
|
|
2338
|
+
if (evt instanceof PointerEvent) {
|
|
2339
|
+
const pointerType = evt.pointerType.toLowerCase().replace(/-/g, '');
|
|
2340
|
+
for (const k of param) {
|
|
2341
|
+
if (pointerType === k.toLowerCase().replace(/-/g, '')) { return true }
|
|
2342
|
+
}
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
},
|
|
2346
|
+
|
|
2347
|
+
ctrl(evt) {
|
|
2348
|
+
if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
|
|
2349
|
+
return evt.ctrlKey;
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
alt(evt) {
|
|
2353
|
+
if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
|
|
2354
|
+
return evt.altKey;
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
shift(evt) {
|
|
2358
|
+
if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
|
|
2359
|
+
return evt.shiftKey;
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
meta(evt) {
|
|
2363
|
+
if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
|
|
2364
|
+
return evt.metaKey;
|
|
2365
|
+
}
|
|
2366
|
+
},
|
|
2367
|
+
cmd(evt) {
|
|
2368
|
+
if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
|
|
2369
|
+
return evt.ctrlKey || evt.metaKey;
|
|
2370
|
+
}
|
|
2371
|
+
},
|
|
2372
|
+
};
|
|
2373
|
+
/**
|
|
2374
|
+
*
|
|
2375
|
+
* @param {Component | string} component
|
|
2376
|
+
* @param {Environment} env
|
|
2377
|
+
* @returns
|
|
2378
|
+
*/
|
|
2379
|
+
function createContext(component, env) {
|
|
2380
|
+
const tag = typeof component === 'string' ? component : component.tag;
|
|
2381
|
+
const { attrs, events } = typeof component !== 'string' && component || {attrs: null, events: null };
|
|
2382
|
+
|
|
2383
|
+
let destroyed = false;
|
|
2384
|
+
let init = false;
|
|
2385
|
+
const tagAttrs = Object.create(null);
|
|
2386
|
+
|
|
2387
|
+
/** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
|
|
2388
|
+
const allEvents = [];
|
|
2389
|
+
const stateEmitter = new EventEmitter();
|
|
2390
|
+
/** @type {EventEmitter<Record<string, [any, any, string]>>} */
|
|
2391
|
+
const attrEmitter = new EventEmitter();
|
|
2392
|
+
/** @type {Component.Context} */
|
|
2393
|
+
const context = {
|
|
2394
|
+
events: allEvents,
|
|
2395
|
+
props: attrs ? new Set(Object.entries(attrs).filter(([,a]) => a.isProp).map(([e]) => e)) : null,
|
|
2396
|
+
tagAttrs,
|
|
2397
|
+
watchAttr(name, fn) { return attrEmitter.listen(name, fn); },
|
|
2398
|
+
get destroyed() { return destroyed},
|
|
2399
|
+
get init() { return init},
|
|
2400
|
+
listen(name, listener) { return stateEmitter.listen(name, listener); },
|
|
2401
|
+
};
|
|
2402
|
+
/** @type {Component.Handler} */
|
|
2403
|
+
const handler = {
|
|
2404
|
+
tag,
|
|
2405
|
+
set(name, value) {
|
|
2406
|
+
if (attrs && !(name in attrs)) { return; }
|
|
2407
|
+
if (!(name in tagAttrs)) { tagAttrs[name] = void 0; }
|
|
2408
|
+
const old = tagAttrs[name];
|
|
2409
|
+
if (old === value) { return; }
|
|
2410
|
+
tagAttrs[name] = value;
|
|
2411
|
+
attrEmitter.emit(name, value, old, name);
|
|
2412
|
+
},
|
|
2413
|
+
addEvent(name, fn) {
|
|
2414
|
+
if (typeof fn !== 'function') { return; }
|
|
2415
|
+
const [e, ...fs] = name.split('.').filter(Boolean);
|
|
2416
|
+
const filters = events ? events[e].filters : {};
|
|
2417
|
+
if (!filters) { return; }
|
|
2418
|
+
/** @type {AddEventListenerOptions} */
|
|
2419
|
+
const options = {};
|
|
2420
|
+
/** @type {[($event: any, param: string[], env: any) => boolean | null | void, string[], boolean][]} */
|
|
2421
|
+
const filterFns = [];
|
|
2422
|
+
if (filters) for (let f = fs.shift();f;f = fs.shift()) {
|
|
2423
|
+
const paramIndex = f.indexOf(':');
|
|
2424
|
+
const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
|
|
2425
|
+
const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
|
|
2426
|
+
const filterName = noParamName.replace(/^-+/, '');
|
|
2427
|
+
const sub = (noParamName.length - filterName.length) % 2 === 1;
|
|
2428
|
+
let filter = filters[filterName] || filterName;
|
|
2429
|
+
switch(filter) {
|
|
2430
|
+
case 'once':
|
|
2431
|
+
options.once = !sub;
|
|
2432
|
+
break;
|
|
2433
|
+
case 'passive':
|
|
2434
|
+
options.passive = !sub;
|
|
2435
|
+
break;
|
|
2436
|
+
case 'capture':
|
|
2437
|
+
options.capture = !sub;
|
|
2438
|
+
break;
|
|
2439
|
+
default:
|
|
2440
|
+
if (typeof filter === 'string') {
|
|
2441
|
+
filter = eventFilters[filter];
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
if (typeof filter !== 'function') { continue; }
|
|
2445
|
+
filterFns.push([filter, param, sub]);
|
|
2446
|
+
}
|
|
2447
|
+
allEvents.push([e, $event => {
|
|
2448
|
+
const global = env.all;
|
|
2449
|
+
for (const [filter, param, sub] of filterFns) {
|
|
2450
|
+
if (filter($event, param, global) === sub) { return}
|
|
2451
|
+
}
|
|
2452
|
+
fn($event, global);
|
|
2453
|
+
}, options]);
|
|
2454
|
+
},
|
|
2455
|
+
destroy() {
|
|
2456
|
+
if (destroyed) { return }
|
|
2457
|
+
destroyed = true;
|
|
2458
|
+
stateEmitter.emit('destroy');
|
|
2459
|
+
},
|
|
2460
|
+
init() {
|
|
2461
|
+
if (init) { return }
|
|
2462
|
+
init = true;
|
|
2463
|
+
stateEmitter.emit('init', {events: allEvents});
|
|
2464
|
+
},
|
|
2465
|
+
|
|
2466
|
+
};
|
|
2467
|
+
return { context, handler };
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/** @import { Component } from '../types.mjs' */
|
|
2471
|
+
/**
|
|
2472
|
+
*
|
|
2473
|
+
* @param {any} val
|
|
2474
|
+
* @returns
|
|
2475
|
+
*/
|
|
2476
|
+
function toAttrValue(val) {
|
|
2477
|
+
if (typeof val === 'number') {
|
|
2478
|
+
return String(val);
|
|
2479
|
+
}
|
|
2480
|
+
if (typeof val === 'bigint') {
|
|
2481
|
+
return String(val);
|
|
2482
|
+
}
|
|
2483
|
+
if (typeof val === 'boolean') {
|
|
2484
|
+
return val ? '' : null;
|
|
2485
|
+
}
|
|
2486
|
+
if (typeof val === 'string') {
|
|
2487
|
+
return val;
|
|
2488
|
+
}
|
|
2489
|
+
if ((val ?? null) === null) {
|
|
2490
|
+
return null;
|
|
2491
|
+
}
|
|
2492
|
+
return String(val);
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
*
|
|
2496
|
+
* @param {any} val
|
|
2497
|
+
* @returns
|
|
2498
|
+
*/
|
|
2499
|
+
function toText$1(val) {
|
|
2500
|
+
if ((val ?? null) === null) {
|
|
2501
|
+
return '';
|
|
2502
|
+
}
|
|
2503
|
+
return String(val);
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
|
|
2507
|
+
/**
|
|
2508
|
+
*
|
|
2509
|
+
* @param {Element} el
|
|
2510
|
+
* @param {string} attr
|
|
2511
|
+
*/
|
|
2512
|
+
function getAttrs(el, attr) {
|
|
2513
|
+
if (el instanceof HTMLInputElement && 'checked' === attr) {
|
|
2514
|
+
switch (el.type.toLowerCase()) {
|
|
2515
|
+
case 'checkbox':
|
|
2516
|
+
case 'radio':
|
|
2517
|
+
return Boolean;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
if ((
|
|
2521
|
+
el instanceof HTMLSelectElement
|
|
2522
|
+
|| el instanceof HTMLInputElement
|
|
2523
|
+
|| el instanceof HTMLTextAreaElement
|
|
2524
|
+
) && 'value' === attr) {
|
|
2525
|
+
return toText$1;
|
|
2526
|
+
}
|
|
2527
|
+
if ((el instanceof HTMLDetailsElement) && 'open' === attr) {
|
|
2528
|
+
return Boolean;
|
|
2529
|
+
}
|
|
2530
|
+
if (el instanceof HTMLMediaElement) {
|
|
2531
|
+
if ('muted' === attr) {
|
|
2532
|
+
return Boolean;
|
|
2533
|
+
}
|
|
2534
|
+
if ('paused' === attr) {
|
|
2535
|
+
return Boolean;
|
|
2536
|
+
}
|
|
2537
|
+
if ('currentTime' === attr) {
|
|
2538
|
+
return true;
|
|
2539
|
+
}
|
|
2540
|
+
if ('playbackRate' === attr) {
|
|
2541
|
+
return true;
|
|
2542
|
+
}
|
|
2543
|
+
if ('volume' === attr) {
|
|
2544
|
+
return true;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return false;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
/**
|
|
2551
|
+
* @typedef {object} TagBind
|
|
2552
|
+
* @property {Record<string, (value: any, el: any) => void>} attrs
|
|
2553
|
+
* @property {Record<string, [name: string, set: (event: any, el: any) => any]>} events
|
|
2554
|
+
*/
|
|
2555
|
+
/** @type {Record<string, TagBind>} */
|
|
2556
|
+
const tagBindMap = {
|
|
2557
|
+
input: {
|
|
2558
|
+
attrs: {
|
|
2559
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2560
|
+
$disabled: (v, e) => {e.disabled = v;},
|
|
2561
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2562
|
+
$readonly: (v, e) => {e.readOnly = v;},
|
|
2563
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2564
|
+
$required: (v, e) => {e.required = v;},
|
|
2565
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2566
|
+
$value: (v, e) => {
|
|
2567
|
+
switch(e.type) {
|
|
2568
|
+
case 'checkbox':
|
|
2569
|
+
case 'radio':
|
|
2570
|
+
e.checked = Boolean(v);
|
|
2571
|
+
break;
|
|
2572
|
+
}
|
|
2573
|
+
e.value = toText$1(v);
|
|
2574
|
+
},
|
|
2575
|
+
},
|
|
2576
|
+
events: {
|
|
2577
|
+
$value: ['input', (v, e) => {
|
|
2578
|
+
switch(e.type) {
|
|
2579
|
+
case 'checkbox':
|
|
2580
|
+
case 'radio':
|
|
2581
|
+
return e.checked;
|
|
2582
|
+
case 'number':
|
|
2583
|
+
return Number(e.value);
|
|
2584
|
+
}
|
|
2585
|
+
return e.value;
|
|
2586
|
+
}],
|
|
2587
|
+
},
|
|
2588
|
+
},
|
|
2589
|
+
textarea: {
|
|
2590
|
+
attrs: {
|
|
2591
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2592
|
+
$disabled: (v, e) => {e.disabled = v;},
|
|
2593
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2594
|
+
$readonly: (v, e) => {e.readOnly = v;},
|
|
2595
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2596
|
+
$required: (v, e) => {e.required = v;},
|
|
2597
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2598
|
+
$value: (v, e) => { e.value = toText$1(v); },
|
|
2599
|
+
},
|
|
2600
|
+
events: {
|
|
2601
|
+
$value: ['input', (v, e) => { return e.value; }],
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
select: {
|
|
2605
|
+
attrs: {
|
|
2606
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2607
|
+
$disabled: (v, e) => {e.disabled = v;},
|
|
2608
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2609
|
+
$readonly: (v, e) => {e.readOnly = v;},
|
|
2610
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2611
|
+
$required: (v, e) => {e.required = v;},
|
|
2612
|
+
/** @param {any} v @param {HTMLInputElement} e */
|
|
2613
|
+
$value: (v, e) => { e.value = toText$1(v); },
|
|
2614
|
+
},
|
|
2615
|
+
events: {
|
|
2616
|
+
$value: ['change', (v, e) => { return e.value; }],
|
|
2617
|
+
},
|
|
2618
|
+
},
|
|
2619
|
+
};
|
|
2620
|
+
|
|
2621
|
+
/**
|
|
2622
|
+
*
|
|
2623
|
+
* @param {Component.Context} context
|
|
2624
|
+
* @param {string} name
|
|
2625
|
+
* @param {string?} [is]
|
|
2626
|
+
*/
|
|
2627
|
+
function createTagComponent (context, name, is) {
|
|
2628
|
+
const node = document.createElement(name, {is: is || undefined});
|
|
2629
|
+
const { watchAttr, props } = context;
|
|
2630
|
+
|
|
2631
|
+
context.listen('init', ({events})=> {
|
|
2632
|
+
const e = tagBindMap[name.toLowerCase()];
|
|
2633
|
+
const eAttrs = e?.attrs || {};
|
|
2634
|
+
const eEvents = e?.events || {};
|
|
2635
|
+
for (const [type, listener, options] of events) {
|
|
2636
|
+
if (type[0] === '$') {
|
|
2637
|
+
const e = eEvents[type];
|
|
2638
|
+
if (e) {
|
|
2639
|
+
const [evt, set] = e;
|
|
2640
|
+
node.addEventListener(evt, e => listener(set(e, node)), options);
|
|
2641
|
+
}
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
node.addEventListener(type, listener, options);
|
|
2645
|
+
}
|
|
2646
|
+
if (props) {
|
|
2647
|
+
for (const [name, attr] of Object.entries(context.tagAttrs)) {
|
|
2648
|
+
watchAttr(name, v => {
|
|
2649
|
+
// @ts-ignore
|
|
2650
|
+
if (props.has(name)) { node[name] = v; } else {
|
|
2651
|
+
const val = toAttrValue(v);
|
|
2652
|
+
if (val == null) {
|
|
2653
|
+
node.removeAttribute(name);
|
|
2654
|
+
} else {
|
|
2655
|
+
node.setAttribute(name, val);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
// @ts-ignore
|
|
2660
|
+
if (props.has(name)) { node[name] = attr; } else {
|
|
2661
|
+
const val = toAttrValue(attr);
|
|
2662
|
+
if (val !== null) {
|
|
2663
|
+
node.setAttribute(name, val);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return;
|
|
2668
|
+
|
|
2669
|
+
}
|
|
2670
|
+
for (const [name, attr] of Object.entries(context.tagAttrs)) {
|
|
2671
|
+
if (node instanceof HTMLInputElement && name.toLocaleLowerCase() === 'type') {
|
|
2672
|
+
const value = toAttrValue(attr);
|
|
2673
|
+
if (value !== null) {
|
|
2674
|
+
node.setAttribute(name, value);
|
|
2675
|
+
}
|
|
2676
|
+
continue;
|
|
2677
|
+
}
|
|
2678
|
+
if (name === '$hidden') {
|
|
2679
|
+
if (attr) { node.hidden = attr; }
|
|
2680
|
+
watchAttr(name, (val) => { node.hidden = val; });
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
if (name[0] === '$') {
|
|
2685
|
+
const e = eAttrs[name];
|
|
2686
|
+
if (e) {
|
|
2687
|
+
e(attr, node);
|
|
2688
|
+
watchAttr(name, (attr) => e(attr, node));
|
|
2689
|
+
}
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
const prop = getAttrs(node, name);
|
|
2693
|
+
if (typeof prop === 'function') {
|
|
2694
|
+
// @ts-ignore
|
|
2695
|
+
node[name] = prop(attr);
|
|
2696
|
+
watchAttr(name, (attr) => {
|
|
2697
|
+
// @ts-ignore
|
|
2698
|
+
node[name] = prop(attr);
|
|
2699
|
+
});
|
|
2700
|
+
continue;
|
|
2701
|
+
}
|
|
2702
|
+
if (prop) {
|
|
2703
|
+
// @ts-ignore
|
|
2704
|
+
node[name] = attr;
|
|
2705
|
+
watchAttr(name, (attr) => {
|
|
2706
|
+
// @ts-ignore
|
|
2707
|
+
node[name] = attr;
|
|
2708
|
+
});
|
|
2709
|
+
continue;
|
|
2710
|
+
}
|
|
2711
|
+
let value = toAttrValue(attr);
|
|
2712
|
+
if (value !== null) {
|
|
2713
|
+
node.setAttribute(name, value);
|
|
2714
|
+
}
|
|
2715
|
+
watchAttr(name, (val) => {
|
|
2716
|
+
const value = toAttrValue(val);
|
|
2717
|
+
if (value === null) {
|
|
2718
|
+
node.removeAttribute(name);
|
|
2719
|
+
} else {
|
|
2720
|
+
node.setAttribute(name, value);
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
return node;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/** @import * as Layout from '../Layout/index.mjs' */
|
|
2729
|
+
/** @import Store, { ArrayStore } from '../Store/index.mjs' */
|
|
2730
|
+
|
|
2731
|
+
/**
|
|
2732
|
+
*
|
|
2733
|
+
* @param {Layout.Node} layout
|
|
2734
|
+
* @param {Element} parent
|
|
2735
|
+
* @param {Node?} next
|
|
2736
|
+
* @param {ArrayStore} store
|
|
2737
|
+
* @param {Environment} env
|
|
2738
|
+
* @param {(layout: Layout.Node, parent: Element, next: Node | null, store: Store, env: any) => () => void} renderItem
|
|
2739
|
+
*/
|
|
2740
|
+
function renderArray(layout, parent, next, store, env, renderItem) {
|
|
2741
|
+
const start = parent.insertBefore(document.createComment(''), next);
|
|
2742
|
+
/** @type {Map<Store, [Comment, Comment, () => void]>} */
|
|
2743
|
+
let seMap = new Map();
|
|
2744
|
+
/** @param {Map<Store, [Comment, Comment, () => void]>} map */
|
|
2745
|
+
function destroyMap(map) {
|
|
2746
|
+
for (const [s, e, d] of map.values()) {
|
|
2747
|
+
d();
|
|
2748
|
+
s.remove();
|
|
2749
|
+
e.remove();
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
}
|
|
2753
|
+
const childrenResult = watch(() => store.children, function render(children) {
|
|
2754
|
+
if (!start.parentNode) { return; }
|
|
2755
|
+
let nextNode = start.nextSibling;
|
|
2756
|
+
const oldSeMap = seMap;
|
|
2757
|
+
seMap = new Map();
|
|
2758
|
+
for (let child of children) {
|
|
2759
|
+
const old = oldSeMap.get(child);
|
|
2760
|
+
if (!old) {
|
|
2761
|
+
const ItemStart = parent.insertBefore(document.createComment(''), nextNode);
|
|
2762
|
+
const itemEnd = parent.insertBefore(document.createComment(''), nextNode);
|
|
2763
|
+
const d = renderItem(layout, parent, itemEnd, child, env.setValue(child, store));
|
|
2764
|
+
seMap.set(child, [ItemStart, itemEnd, d]);
|
|
2765
|
+
continue;
|
|
2766
|
+
}
|
|
2767
|
+
oldSeMap.delete(child);
|
|
2768
|
+
seMap.set(child, old);
|
|
2769
|
+
if (nextNode === old[0]) {
|
|
2770
|
+
nextNode = old[1].nextSibling;
|
|
2771
|
+
continue;
|
|
2772
|
+
}
|
|
2773
|
+
/** @type {Node?} */
|
|
2774
|
+
let c = old[0];
|
|
2775
|
+
while (c && c !== old[1]) {
|
|
2776
|
+
const o = c;
|
|
2777
|
+
c = c.nextSibling;
|
|
2778
|
+
parent.insertBefore(o, nextNode);
|
|
2779
|
+
}
|
|
2780
|
+
parent.insertBefore(old[1], nextNode);
|
|
2781
|
+
}
|
|
2782
|
+
destroyMap(oldSeMap);
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
return () => {
|
|
2786
|
+
start.remove();
|
|
2787
|
+
childrenResult();
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
/** @import * as Layout from '../Layout/index.mjs' */
|
|
2792
|
+
/**
|
|
2793
|
+
*
|
|
2794
|
+
* @param {any} val
|
|
2795
|
+
* @returns
|
|
2796
|
+
*/
|
|
2797
|
+
function toText(val) {
|
|
2798
|
+
if ((val ?? null) === null) {
|
|
2799
|
+
return '';
|
|
2800
|
+
}
|
|
2801
|
+
return String(val);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
|
|
2805
|
+
/**
|
|
2806
|
+
* @param {Element} parent
|
|
2807
|
+
* @param {Node?} next
|
|
2808
|
+
* @param {Environment} envs
|
|
2809
|
+
* @param {Layout.Directives} layout
|
|
2810
|
+
*/
|
|
2811
|
+
function renderFillDirectives(parent, next, envs, { text, html }) {
|
|
2812
|
+
if (text != null) {
|
|
2813
|
+
const node = parent.insertBefore(document.createTextNode(''), next);
|
|
2814
|
+
const stop = envs.watch(text, val => node.textContent = toText(val));
|
|
2815
|
+
return () => {
|
|
2816
|
+
node.remove();
|
|
2817
|
+
stop();
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
if (html == null) { return; }
|
|
2821
|
+
const start = parent.insertBefore(document.createComment(''), next);
|
|
2822
|
+
const end = parent.insertBefore(document.createComment(''), next);
|
|
2823
|
+
const div = document.createElement('div');
|
|
2824
|
+
/** @param {string} html */
|
|
2825
|
+
function add(html) {
|
|
2826
|
+
div.innerHTML = html;
|
|
2827
|
+
for (let node = div.firstChild; node; node = start.firstChild) {
|
|
2828
|
+
parent.insertBefore(node, end);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
function remove() {
|
|
2832
|
+
for (let node = start.nextSibling; node && node !== end; node = start.nextSibling) {
|
|
2833
|
+
node.remove();
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
const result = envs.watch(html, val => {
|
|
2837
|
+
remove();
|
|
2838
|
+
add(toText(val));
|
|
2839
|
+
});
|
|
2840
|
+
return () => {
|
|
2841
|
+
result();
|
|
2842
|
+
remove();
|
|
2843
|
+
start.remove();
|
|
2844
|
+
end.remove();
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
/** @import * as Layout from '../Layout/index.mjs' */
|
|
2849
|
+
|
|
2850
|
+
/**
|
|
2851
|
+
* @param {(Layout.Node | string)[]} layouts
|
|
2852
|
+
* @param {Element} parent
|
|
2853
|
+
* @param {Node?} next
|
|
2854
|
+
* @param {Environment} envs
|
|
2855
|
+
* @param {(layout: Layout.Node) => () => void} renderItem
|
|
2856
|
+
* @returns {() => void}
|
|
2857
|
+
*/
|
|
2858
|
+
function renderList(layouts, parent, next, envs, renderItem) {
|
|
2859
|
+
|
|
2860
|
+
/** @type {Set<() => void>?} */
|
|
2861
|
+
let bkList = new Set();
|
|
2862
|
+
/** @type {[string | Function | null, Layout.Node][]} */
|
|
2863
|
+
let ifList = [];
|
|
2864
|
+
|
|
2865
|
+
/** @param {[string | Function | null, Layout.Node][]} list */
|
|
2866
|
+
function renderIf(list) {
|
|
2867
|
+
if (!list.length || !bkList) { return; }
|
|
2868
|
+
const end = parent.insertBefore(document.createComment(''), next);
|
|
2869
|
+
|
|
2870
|
+
|
|
2871
|
+
let lastIndex = -1;
|
|
2872
|
+
let destroy = () => { };
|
|
2873
|
+
/**
|
|
2874
|
+
*
|
|
2875
|
+
* @param {number} index
|
|
2876
|
+
* @returns
|
|
2877
|
+
*/
|
|
2878
|
+
function renderIndex(index) {
|
|
2879
|
+
const layout = list[index]?.[1];
|
|
2880
|
+
if (!layout) { return; }
|
|
2881
|
+
destroy = renderItem(layout);
|
|
2882
|
+
}
|
|
2883
|
+
bkList.add(() => {
|
|
2884
|
+
destroy();
|
|
2885
|
+
destroy = () => { };
|
|
2886
|
+
end.remove();
|
|
2887
|
+
});
|
|
2888
|
+
bkList.add(watch(
|
|
2889
|
+
() => list.findIndex(([ifv]) => ifv === null || envs.exec(ifv)),
|
|
2890
|
+
index => {
|
|
2891
|
+
if (index === lastIndex) { return; }
|
|
2892
|
+
lastIndex = index;
|
|
2893
|
+
destroy();
|
|
2894
|
+
destroy = () => { };
|
|
2895
|
+
renderIndex(lastIndex);
|
|
2896
|
+
},
|
|
2897
|
+
));
|
|
2898
|
+
}
|
|
2899
|
+
for (const layout of layouts) {
|
|
2900
|
+
if (typeof layout === 'string') {
|
|
2901
|
+
renderIf(ifList);
|
|
2902
|
+
ifList = [];
|
|
2903
|
+
const node = document.createTextNode(layout);
|
|
2904
|
+
parent.insertBefore(node, next);
|
|
2905
|
+
bkList.add(() => node.remove());
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (ifList.length && layout.directives.else) {
|
|
2909
|
+
const ifv = layout.directives.if || null;
|
|
2910
|
+
ifList.push([ifv, layout]);
|
|
2911
|
+
if (!ifv) {
|
|
2912
|
+
renderIf(ifList);
|
|
2913
|
+
ifList = [];
|
|
2914
|
+
}
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
renderIf(ifList);
|
|
2918
|
+
ifList = [];
|
|
2919
|
+
const ifv = layout.directives.if;
|
|
2920
|
+
if (ifv) {
|
|
2921
|
+
ifList.push([ifv, layout]);
|
|
2922
|
+
continue;
|
|
2923
|
+
}
|
|
2924
|
+
bkList.add(
|
|
2925
|
+
renderItem(layout)
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
return () => {
|
|
2930
|
+
if (!bkList) { return; }
|
|
2931
|
+
const list = bkList;
|
|
2932
|
+
bkList = null;
|
|
2933
|
+
for (const s of list) {
|
|
2934
|
+
s();
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
/** @import Store from '../Store/index.mjs' */
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* @param {Layout.Node} layout
|
|
2943
|
+
* @param {Element} parent
|
|
2944
|
+
* @param {Node?} next
|
|
2945
|
+
* @param {Environment} env
|
|
2946
|
+
* @param {(layout: Layout.Node) => () => void} renderItem
|
|
2947
|
+
* @returns {() => void}
|
|
2948
|
+
*/
|
|
2949
|
+
function renderFragment(layout, parent, next, env, renderItem) {
|
|
2950
|
+
return renderFillDirectives(parent, next, env, layout.directives) ||
|
|
2951
|
+
renderList(layout.children || [], parent, next, env, renderItem);
|
|
2952
|
+
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* @param {Layout.Node} layout
|
|
2956
|
+
* @param {Element} parent
|
|
2957
|
+
* @param {Node?} next
|
|
2958
|
+
* @param {Store} store
|
|
2959
|
+
* @param {Environment} env
|
|
2960
|
+
* @param {string[]} componentPath
|
|
2961
|
+
* @param {((path: string[]) => Component?)?} [getComponent]
|
|
2962
|
+
*/
|
|
2963
|
+
function renderItem(layout, parent, next, store, env, componentPath, getComponent) {
|
|
2964
|
+
env = env.set(layout.aliases, layout.vars);
|
|
2965
|
+
if (!layout.name || layout.directives.fragment) {
|
|
2966
|
+
return renderFragment(layout, parent, next, env, l => {
|
|
2967
|
+
return render(l, parent, next, store, env, componentPath, getComponent);
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
const path = [...componentPath, layout.name];
|
|
2971
|
+
const component = getComponent?.(path);
|
|
2972
|
+
if (getComponent && !component) { return () => { }; }
|
|
2973
|
+
const { context, handler } = createContext(component ? component : layout.name, env);
|
|
2974
|
+
|
|
2975
|
+
|
|
2976
|
+
const componentAttrs = component?.attrs;
|
|
2977
|
+
const attrs = componentAttrs
|
|
2978
|
+
? bindAttrs(handler, env, layout.attrs, componentAttrs, layout.directives.bind)
|
|
2979
|
+
: bindBaseAttrs(handler, env, layout.attrs, layout.directives.bind);
|
|
2980
|
+
|
|
2981
|
+
for (const [name, event] of Object.entries(layout.events)) {
|
|
2982
|
+
const fn = env.getEvent(event);
|
|
2983
|
+
if (fn) { handler.addEvent(name, fn); }
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
const r = component ?
|
|
2987
|
+
typeof component.tag === 'function'
|
|
2988
|
+
? component.tag(context)
|
|
2989
|
+
: createTagComponent(context, component.tag, component.is)
|
|
2990
|
+
: createTagComponent(context, layout.name, layout.is);
|
|
2991
|
+
const root = Array.isArray(r) ? r[0] : r;
|
|
2992
|
+
const slot = Array.isArray(r) && r[1] || root;
|
|
2993
|
+
parent.insertBefore(root, next);
|
|
2994
|
+
const children =
|
|
2995
|
+
renderFillDirectives(slot, null, env, layout.directives)
|
|
2996
|
+
|| renderList(layout.children || [], slot, null, env, l => {
|
|
2997
|
+
return render(l, slot, null, store, env, componentPath, getComponent);
|
|
2998
|
+
});
|
|
2999
|
+
|
|
3000
|
+
|
|
3001
|
+
bindClasses(root, layout.classes, env);
|
|
3002
|
+
bindStyles(root, layout.styles, env);
|
|
3003
|
+
|
|
3004
|
+
handler.init();
|
|
3005
|
+
// TODO: 创建组件
|
|
3006
|
+
return () => {
|
|
3007
|
+
root.remove();
|
|
3008
|
+
handler.destroy();
|
|
3009
|
+
attrs();
|
|
3010
|
+
children();
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
*
|
|
3015
|
+
* @param {Layout.Node} layout
|
|
3016
|
+
* @param {Element} parent
|
|
3017
|
+
* @param {Node?} next
|
|
3018
|
+
* @param {Store} store
|
|
3019
|
+
* @param {Environment} env
|
|
3020
|
+
* @param {string[]} componentPath
|
|
3021
|
+
* @param {((path: string[]) => Component?)?} [getComponent]
|
|
3022
|
+
* @returns {() => void}
|
|
3023
|
+
*/
|
|
3024
|
+
function render(layout, parent, next, store, env, componentPath, getComponent) {
|
|
3025
|
+
const { directives } = layout;
|
|
3026
|
+
const { value } = directives;
|
|
3027
|
+
if (value) {
|
|
3028
|
+
const newStore = store.child(value);
|
|
3029
|
+
if (!newStore) { return () => {}; }
|
|
3030
|
+
store = newStore;
|
|
3031
|
+
env = env.setValue(store);
|
|
3032
|
+
}
|
|
3033
|
+
if (!directives.enum) {
|
|
3034
|
+
return renderItem(layout, parent, next, store, env, componentPath, getComponent);
|
|
3035
|
+
}
|
|
3036
|
+
if (!(store instanceof ArrayStore)) { return () => { }; }
|
|
3037
|
+
return renderArray(layout, parent, next, store, env, (a, b, c, store, env) => {
|
|
3038
|
+
return renderItem(a, b, c, store, env, componentPath, getComponent);
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
/**
|
|
3043
|
+
* @overload
|
|
3044
|
+
* @param {Store} store
|
|
3045
|
+
* @param {(Layout.Node | string)[]} layouts
|
|
3046
|
+
* @param {Element} parent
|
|
3047
|
+
* @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [global]
|
|
3048
|
+
* @param {(path: string[]) => Component?} [components]
|
|
3049
|
+
* @returns {() => void}
|
|
3050
|
+
*/
|
|
3051
|
+
/**
|
|
3052
|
+
* @overload
|
|
3053
|
+
* @param {Store} store
|
|
3054
|
+
* @param {(Layout.Node | string)[]} layouts
|
|
3055
|
+
* @param {Element} parent
|
|
3056
|
+
* @param {((path: string[]) => Component?)?} [components]
|
|
3057
|
+
* @returns {() => void}
|
|
3058
|
+
*/
|
|
3059
|
+
/**
|
|
3060
|
+
* @param {Store} store
|
|
3061
|
+
* @param {(Layout.Node | string)[]} layouts
|
|
3062
|
+
* @param {Element} parent
|
|
3063
|
+
* @param {((path: string[]) => Component?) | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt1]
|
|
3064
|
+
* @param {((path: string[]) => Component?) | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt2]
|
|
3065
|
+
*/
|
|
3066
|
+
function index (store, layouts, parent, opt1, opt2) {
|
|
3067
|
+
const options = [opt1, opt2];
|
|
3068
|
+
const components = options.find(v => typeof v === 'function');
|
|
3069
|
+
const global = options.find(v => typeof v === 'object');
|
|
3070
|
+
const env = new Environment(global).setValue(store);
|
|
3071
|
+
return renderList(layouts, parent, null, env, l => {
|
|
3072
|
+
return render(l, parent, null, store, env, [], components);
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
export { index$1 as Layout, Store, index as render };
|