@multiplekex/shallot 0.1.7 → 0.1.10
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/package.json +2 -2
- package/src/core/component.ts +7 -17
- package/src/core/index.ts +2 -1
- package/src/core/math.ts +30 -5
- package/src/core/state.ts +4 -2
- package/src/core/types.ts +2 -2
- package/src/core/xml.ts +83 -33
- package/src/extras/arrows/index.ts +79 -96
- package/src/extras/gradient/index.ts +1050 -0
- package/src/extras/index.ts +1 -0
- package/src/extras/lines/index.ts +103 -57
- package/src/extras/orbit/index.ts +1 -1
- package/src/extras/text/index.ts +249 -82
- package/src/standard/compute/graph.ts +10 -12
- package/src/standard/compute/index.ts +27 -2
- package/src/standard/compute/inspect.ts +49 -53
- package/src/standard/compute/pass.ts +23 -0
- package/src/standard/render/camera.ts +14 -2
- package/src/standard/render/forward.ts +104 -43
- package/src/standard/render/index.ts +85 -12
- package/src/standard/render/mesh/index.ts +255 -28
- package/src/standard/render/pass.ts +63 -0
- package/src/standard/render/postprocess.ts +250 -58
- package/src/standard/render/scene.ts +98 -2
- package/src/standard/render/surface/compile.ts +74 -0
- package/src/standard/render/surface/index.ts +116 -0
- package/src/standard/render/surface/structs.ts +50 -0
- package/src/standard/render/transparent.ts +91 -0
- package/src/standard/tween/sequence.ts +2 -2
- package/src/standard/tween/tween.ts +31 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplekex/shallot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"@napi-rs/canvas": "^0.1.88",
|
|
26
26
|
"@types/bun": "latest",
|
|
27
27
|
"@types/three": "^0.182.0",
|
|
28
|
+
"bun-webgpu": "^0.1.4",
|
|
28
29
|
"three": "^0.182.0",
|
|
29
30
|
"troika-three-text": "^0.52.4",
|
|
30
|
-
"webgpu": "^0.3.8",
|
|
31
31
|
"wgpu-matrix": "^3.4.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/core/component.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import type { State } from "./state";
|
|
2
1
|
import { toKebabCase } from "./strings";
|
|
3
2
|
|
|
4
3
|
export type ComponentArray = number[] | Float32Array | Uint32Array;
|
|
5
4
|
export type ComponentData = Record<string, ComponentArray>;
|
|
6
|
-
|
|
7
|
-
export interface ParseContext {
|
|
8
|
-
readonly currentEid: number;
|
|
9
|
-
getEntityByName(name: string): number | null;
|
|
10
|
-
setName(name: string, eid: number): void;
|
|
11
|
-
}
|
|
5
|
+
export type ComponentLike = Record<string, unknown>;
|
|
12
6
|
|
|
13
7
|
export interface FieldAccessor {
|
|
14
8
|
get(eid: number): number;
|
|
@@ -17,33 +11,29 @@ export interface FieldAccessor {
|
|
|
17
11
|
|
|
18
12
|
export interface ComponentTraits {
|
|
19
13
|
defaults?: () => Record<string, number>;
|
|
20
|
-
adapter?: (
|
|
21
|
-
attrs: Record<string, string>,
|
|
22
|
-
state: State,
|
|
23
|
-
context: ParseContext
|
|
24
|
-
) => Record<string, number>;
|
|
14
|
+
adapter?: (attrs: Record<string, string>, eid: number) => Record<string, number>;
|
|
25
15
|
accessors?: Record<string, FieldAccessor>;
|
|
26
16
|
}
|
|
27
17
|
|
|
28
|
-
const traitsMap = new WeakMap<
|
|
18
|
+
const traitsMap = new WeakMap<ComponentLike, ComponentTraits>();
|
|
29
19
|
|
|
30
|
-
export function setTraits(component:
|
|
20
|
+
export function setTraits(component: ComponentLike, traits: ComponentTraits): void {
|
|
31
21
|
traitsMap.set(component, traits);
|
|
32
22
|
}
|
|
33
23
|
|
|
34
|
-
export function getTraits(component:
|
|
24
|
+
export function getTraits(component: ComponentLike): ComponentTraits | undefined {
|
|
35
25
|
return traitsMap.get(component);
|
|
36
26
|
}
|
|
37
27
|
|
|
38
28
|
export interface RegisteredComponent {
|
|
39
|
-
readonly component:
|
|
29
|
+
readonly component: ComponentLike;
|
|
40
30
|
readonly name: string;
|
|
41
31
|
readonly traits?: ComponentTraits;
|
|
42
32
|
}
|
|
43
33
|
|
|
44
34
|
const registry = new Map<string, RegisteredComponent>();
|
|
45
35
|
|
|
46
|
-
export function registerComponent(name: string, component:
|
|
36
|
+
export function registerComponent(name: string, component: ComponentLike): void {
|
|
47
37
|
const kebabName = toKebabCase(name);
|
|
48
38
|
const traits = traitsMap.get(component);
|
|
49
39
|
registry.set(kebabName, { component, name: kebabName, traits });
|
package/src/core/index.ts
CHANGED
|
@@ -43,9 +43,9 @@ export {
|
|
|
43
43
|
getTraits,
|
|
44
44
|
setTraits,
|
|
45
45
|
directAccessor,
|
|
46
|
-
type ParseContext,
|
|
47
46
|
type ComponentTraits,
|
|
48
47
|
type ComponentData,
|
|
48
|
+
type ComponentLike,
|
|
49
49
|
type RegisteredComponent,
|
|
50
50
|
type FieldAccessor,
|
|
51
51
|
} from "./component";
|
|
@@ -89,4 +89,5 @@ export {
|
|
|
89
89
|
type ParseError,
|
|
90
90
|
type LoadResult,
|
|
91
91
|
type PostLoadHook,
|
|
92
|
+
type PostLoadContext,
|
|
92
93
|
} from "./xml";
|
package/src/core/math.ts
CHANGED
|
@@ -20,6 +20,12 @@ export function slerp(
|
|
|
20
20
|
toW: number,
|
|
21
21
|
t: number
|
|
22
22
|
): { x: number; y: number; z: number; w: number } {
|
|
23
|
+
if (!Number.isFinite(t) || !Number.isFinite(fromW) || !Number.isFinite(toW)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`slerp received NaN: from=[${fromX},${fromY},${fromZ},${fromW}], to=[${toX},${toY},${toZ},${toW}], t=${t}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
let dot = fromX * toX + fromY * toY + fromZ * toZ + fromW * toW;
|
|
24
30
|
|
|
25
31
|
if (dot < 0) {
|
|
@@ -154,6 +160,19 @@ export function lookAt(
|
|
|
154
160
|
upY = 1,
|
|
155
161
|
upZ = 0
|
|
156
162
|
): { x: number; y: number; z: number; w: number } {
|
|
163
|
+
if (
|
|
164
|
+
!Number.isFinite(eyeX) ||
|
|
165
|
+
!Number.isFinite(eyeY) ||
|
|
166
|
+
!Number.isFinite(eyeZ) ||
|
|
167
|
+
!Number.isFinite(targetX) ||
|
|
168
|
+
!Number.isFinite(targetY) ||
|
|
169
|
+
!Number.isFinite(targetZ)
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`lookAt received NaN: eye=[${eyeX},${eyeY},${eyeZ}], target=[${targetX},${targetY},${targetZ}]`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
157
176
|
let zx = eyeX - targetX;
|
|
158
177
|
let zy = eyeY - targetY;
|
|
159
178
|
let zz = eyeZ - targetZ;
|
|
@@ -173,7 +192,7 @@ export function lookAt(
|
|
|
173
192
|
let xz = upX * zy - upY * zx;
|
|
174
193
|
let xLen = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
175
194
|
|
|
176
|
-
if (xLen
|
|
195
|
+
if (xLen < 1e-6) {
|
|
177
196
|
if (Math.abs(zz) > Math.abs(zx)) {
|
|
178
197
|
upX += 1e-4;
|
|
179
198
|
} else {
|
|
@@ -185,10 +204,16 @@ export function lookAt(
|
|
|
185
204
|
xLen = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
186
205
|
}
|
|
187
206
|
|
|
188
|
-
xLen
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
if (xLen < 1e-6) {
|
|
208
|
+
xx = 1;
|
|
209
|
+
xy = 0;
|
|
210
|
+
xz = 0;
|
|
211
|
+
} else {
|
|
212
|
+
xLen = 1 / xLen;
|
|
213
|
+
xx *= xLen;
|
|
214
|
+
xy *= xLen;
|
|
215
|
+
xz *= xLen;
|
|
216
|
+
}
|
|
192
217
|
|
|
193
218
|
const yx = zy * xz - zz * xy;
|
|
194
219
|
const yy = zz * xx - zx * xz;
|
package/src/core/state.ts
CHANGED
|
@@ -37,6 +37,7 @@ export class State {
|
|
|
37
37
|
readonly world: World;
|
|
38
38
|
readonly scheduler = new Scheduler();
|
|
39
39
|
readonly canvas: HTMLCanvasElement | null;
|
|
40
|
+
maxEid = 0;
|
|
40
41
|
|
|
41
42
|
private _resources = new Map<symbol, unknown>();
|
|
42
43
|
private _disposed = false;
|
|
@@ -140,6 +141,7 @@ export class State {
|
|
|
140
141
|
if (eid >= MAX_ENTITIES) {
|
|
141
142
|
throw new Error(`Entity limit exceeded: ${eid} >= ${MAX_ENTITIES}`);
|
|
142
143
|
}
|
|
144
|
+
if (eid > this.maxEid) this.maxEid = eid;
|
|
143
145
|
return eid;
|
|
144
146
|
}
|
|
145
147
|
|
|
@@ -268,9 +270,9 @@ export class State {
|
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
const array = registered.component[camelPath];
|
|
271
|
-
if (array == null) return null;
|
|
273
|
+
if (array == null || !(ArrayBuffer.isView(array) || Array.isArray(array))) return null;
|
|
272
274
|
|
|
273
|
-
const accessor = directAccessor(array, 1, 0);
|
|
275
|
+
const accessor = directAccessor(array as number[], 1, 0);
|
|
274
276
|
this.ensureFieldAccessors().set(bindingId, accessor);
|
|
275
277
|
return accessor;
|
|
276
278
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { System } from "./scheduler";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentLike } from "./component";
|
|
3
3
|
import type { RelationDef } from "./relation";
|
|
4
4
|
import type { State } from "./state";
|
|
5
5
|
|
|
6
6
|
export interface Plugin {
|
|
7
7
|
readonly systems?: readonly System[];
|
|
8
|
-
readonly components?: Record<string,
|
|
8
|
+
readonly components?: Record<string, ComponentLike>;
|
|
9
9
|
readonly relations?: readonly RelationDef[];
|
|
10
10
|
readonly dependencies?: readonly Plugin[];
|
|
11
11
|
readonly initialize?: (state: State) => void | Promise<void>;
|
package/src/core/xml.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { XMLParser } from "fast-xml-parser";
|
|
2
2
|
import { addComponent, Pair } from "bitecs";
|
|
3
3
|
import type { State } from "./state";
|
|
4
|
-
import {
|
|
5
|
-
getRegisteredComponent,
|
|
6
|
-
type ParseContext,
|
|
7
|
-
type ComponentData,
|
|
8
|
-
type RegisteredComponent,
|
|
9
|
-
} from "./component";
|
|
4
|
+
import { getRegisteredComponent, type ComponentLike, type RegisteredComponent } from "./component";
|
|
10
5
|
import { getRelationDef, ChildOf } from "./relation";
|
|
11
6
|
import { toKebabCase, toCamelCase } from "./strings";
|
|
12
7
|
|
|
@@ -100,6 +95,13 @@ export interface ParseError {
|
|
|
100
95
|
readonly path?: string;
|
|
101
96
|
}
|
|
102
97
|
|
|
98
|
+
export interface PendingFieldRef {
|
|
99
|
+
readonly eid: number;
|
|
100
|
+
readonly component: ComponentLike;
|
|
101
|
+
readonly field: string;
|
|
102
|
+
readonly targetName: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
103
105
|
export function parseXml(xml: string): ParseResult {
|
|
104
106
|
const errors: ParseError[] = [];
|
|
105
107
|
const warnings: string[] = [];
|
|
@@ -276,7 +278,11 @@ export interface LoadResult {
|
|
|
276
278
|
readonly errors: readonly ParseError[];
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
export
|
|
281
|
+
export interface PostLoadContext {
|
|
282
|
+
getEntityByName(name: string): number | null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export type PostLoadHook = (state: State, context: PostLoadContext) => void;
|
|
280
286
|
|
|
281
287
|
const postLoadHooks: PostLoadHook[] = [];
|
|
282
288
|
|
|
@@ -289,17 +295,8 @@ export function unregisterPostLoadHook(hook: PostLoadHook): void {
|
|
|
289
295
|
if (idx !== -1) postLoadHooks.splice(idx, 1);
|
|
290
296
|
}
|
|
291
297
|
|
|
292
|
-
class LoadParseContext implements
|
|
298
|
+
class LoadParseContext implements PostLoadContext {
|
|
293
299
|
private readonly nameToEntity = new Map<string, number>();
|
|
294
|
-
private _currentEid = 0;
|
|
295
|
-
|
|
296
|
-
get currentEid(): number {
|
|
297
|
-
return this._currentEid;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
setCurrentEid(eid: number): void {
|
|
301
|
-
this._currentEid = eid;
|
|
302
|
-
}
|
|
303
300
|
|
|
304
301
|
getEntityByName(name: string): number | null {
|
|
305
302
|
return this.nameToEntity.get(name) ?? null;
|
|
@@ -343,6 +340,7 @@ function instantiateScene(
|
|
|
343
340
|
const errors: ParseError[] = [...parseErrors];
|
|
344
341
|
const queue: QueuedEntity[] = [];
|
|
345
342
|
const roots: number[] = [];
|
|
343
|
+
const pendingFieldRefs: PendingFieldRef[] = [];
|
|
346
344
|
|
|
347
345
|
for (const entityDef of entityDefs) {
|
|
348
346
|
const eid = createEntityTree(state, entityDef, context, undefined, queue);
|
|
@@ -359,10 +357,19 @@ function instantiateScene(
|
|
|
359
357
|
}
|
|
360
358
|
|
|
361
359
|
for (const compDef of def.components) {
|
|
362
|
-
applyComponent(state, eid, compDef, context, errors);
|
|
360
|
+
applyComponent(state, eid, compDef, context, errors, pendingFieldRefs);
|
|
363
361
|
}
|
|
364
362
|
}
|
|
365
363
|
|
|
364
|
+
for (const ref of pendingFieldRefs) {
|
|
365
|
+
const targetEid = context.getEntityByName(ref.targetName);
|
|
366
|
+
if (targetEid === null) {
|
|
367
|
+
errors.push({ message: `Unknown entity: "@${ref.targetName}"` });
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
setFieldValue(ref.component, ref.field, ref.eid, targetEid);
|
|
371
|
+
}
|
|
372
|
+
|
|
366
373
|
for (const hook of postLoadHooks) {
|
|
367
374
|
hook(state, context);
|
|
368
375
|
}
|
|
@@ -423,7 +430,8 @@ function applyComponent(
|
|
|
423
430
|
eid: number,
|
|
424
431
|
compDef: ComponentDef,
|
|
425
432
|
context: LoadParseContext,
|
|
426
|
-
errors: ParseError[]
|
|
433
|
+
errors: ParseError[],
|
|
434
|
+
pendingFieldRefs: PendingFieldRef[]
|
|
427
435
|
): void {
|
|
428
436
|
const { def, attrs } = compDef;
|
|
429
437
|
const { component, name, traits } = def;
|
|
@@ -436,13 +444,14 @@ function applyComponent(
|
|
|
436
444
|
}
|
|
437
445
|
|
|
438
446
|
let values: Record<string, number>;
|
|
447
|
+
let entityRefs: FieldEntityRef[] = [];
|
|
439
448
|
|
|
440
|
-
context.setCurrentEid(eid);
|
|
441
449
|
if (traits?.adapter) {
|
|
442
|
-
values = traits.adapter(attrs,
|
|
450
|
+
values = traits.adapter(attrs, eid);
|
|
443
451
|
} else {
|
|
444
452
|
const result = parseAttrs(def, attrs);
|
|
445
453
|
values = result.values;
|
|
454
|
+
entityRefs = result.entityRefs;
|
|
446
455
|
for (const err of result.errors) {
|
|
447
456
|
errors.push({ message: `<${name}> ${err}` });
|
|
448
457
|
}
|
|
@@ -451,19 +460,33 @@ function applyComponent(
|
|
|
451
460
|
for (const [field, value] of Object.entries(values)) {
|
|
452
461
|
setFieldValue(component, field, eid, value);
|
|
453
462
|
}
|
|
463
|
+
|
|
464
|
+
for (const ref of entityRefs) {
|
|
465
|
+
pendingFieldRefs.push({
|
|
466
|
+
eid,
|
|
467
|
+
component,
|
|
468
|
+
field: ref.field,
|
|
469
|
+
targetName: ref.targetName,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
interface ParseAttrsResult {
|
|
475
|
+
values: Record<string, number>;
|
|
476
|
+
entityRefs: FieldEntityRef[];
|
|
477
|
+
errors: string[];
|
|
454
478
|
}
|
|
455
479
|
|
|
456
|
-
function parseAttrs(
|
|
457
|
-
def: RegisteredComponent,
|
|
458
|
-
attrs: Record<string, string>
|
|
459
|
-
): { values: Record<string, number>; errors: string[] } {
|
|
480
|
+
function parseAttrs(def: RegisteredComponent, attrs: Record<string, string>): ParseAttrsResult {
|
|
460
481
|
const allValues: Record<string, number> = {};
|
|
482
|
+
const allEntityRefs: FieldEntityRef[] = [];
|
|
461
483
|
const allErrors: string[] = [];
|
|
462
484
|
|
|
463
485
|
if (attrs._value) {
|
|
464
486
|
if (isCSSAttrSyntax(attrs._value)) {
|
|
465
487
|
const result = parsePropertyString(def.name, attrs._value, def.component);
|
|
466
488
|
Object.assign(allValues, result.values);
|
|
489
|
+
allEntityRefs.push(...result.entityRefs);
|
|
467
490
|
allErrors.push(...result.errors);
|
|
468
491
|
}
|
|
469
492
|
}
|
|
@@ -475,6 +498,7 @@ function parseAttrs(
|
|
|
475
498
|
if (isCSSAttrSyntax(attrValue)) {
|
|
476
499
|
const result = parsePropertyString(def.name, attrValue, def.component);
|
|
477
500
|
Object.assign(allValues, result.values);
|
|
501
|
+
allEntityRefs.push(...result.entityRefs);
|
|
478
502
|
allErrors.push(...result.errors);
|
|
479
503
|
} else {
|
|
480
504
|
const result = parsePropertyString(
|
|
@@ -483,34 +507,41 @@ function parseAttrs(
|
|
|
483
507
|
def.component
|
|
484
508
|
);
|
|
485
509
|
Object.assign(allValues, result.values);
|
|
510
|
+
allEntityRefs.push(...result.entityRefs);
|
|
486
511
|
allErrors.push(...result.errors);
|
|
487
512
|
}
|
|
488
513
|
}
|
|
489
514
|
|
|
490
|
-
return { values: allValues, errors: allErrors };
|
|
515
|
+
return { values: allValues, entityRefs: allEntityRefs, errors: allErrors };
|
|
491
516
|
}
|
|
492
517
|
|
|
493
|
-
function setFieldValue(component:
|
|
518
|
+
function setFieldValue(component: ComponentLike, field: string, eid: number, value: number): void {
|
|
494
519
|
const arr = component[field];
|
|
495
520
|
if (arr != null && (ArrayBuffer.isView(arr) || Array.isArray(arr))) {
|
|
496
|
-
arr[eid] = value;
|
|
521
|
+
(arr as number[])[eid] = value;
|
|
497
522
|
}
|
|
498
523
|
}
|
|
499
524
|
|
|
525
|
+
interface FieldEntityRef {
|
|
526
|
+
readonly field: string;
|
|
527
|
+
readonly targetName: string;
|
|
528
|
+
}
|
|
529
|
+
|
|
500
530
|
interface PropertyParseResult {
|
|
501
531
|
readonly values: Record<string, number>;
|
|
532
|
+
readonly entityRefs: readonly FieldEntityRef[];
|
|
502
533
|
readonly errors: readonly string[];
|
|
503
534
|
}
|
|
504
535
|
|
|
505
|
-
function detectVec2(component:
|
|
536
|
+
function detectVec2(component: ComponentLike, base: string): boolean {
|
|
506
537
|
return `${base}X` in component && `${base}Y` in component;
|
|
507
538
|
}
|
|
508
539
|
|
|
509
|
-
function detectVec3(component:
|
|
540
|
+
function detectVec3(component: ComponentLike, base: string): boolean {
|
|
510
541
|
return detectVec2(component, base) && `${base}Z` in component;
|
|
511
542
|
}
|
|
512
543
|
|
|
513
|
-
function detectVec4(component:
|
|
544
|
+
function detectVec4(component: ComponentLike, base: string): boolean {
|
|
514
545
|
return detectVec3(component, base) && `${base}W` in component;
|
|
515
546
|
}
|
|
516
547
|
|
|
@@ -565,9 +596,10 @@ function splitProperties(str: string): string[] {
|
|
|
565
596
|
function parsePropertyString(
|
|
566
597
|
componentName: string,
|
|
567
598
|
propertyString: string,
|
|
568
|
-
component:
|
|
599
|
+
component: ComponentLike
|
|
569
600
|
): PropertyParseResult {
|
|
570
601
|
const values: Record<string, number> = {};
|
|
602
|
+
const entityRefs: FieldEntityRef[] = [];
|
|
571
603
|
const errors: string[] = [];
|
|
572
604
|
|
|
573
605
|
const properties = splitProperties(propertyString);
|
|
@@ -588,6 +620,24 @@ function parsePropertyString(
|
|
|
588
620
|
}
|
|
589
621
|
|
|
590
622
|
const name = toCamelCase(rawName);
|
|
623
|
+
|
|
624
|
+
if (valueStr.startsWith("@") && valueStr.length > 1) {
|
|
625
|
+
if (name in component) {
|
|
626
|
+
entityRefs.push({ field: name, targetName: valueStr.slice(1) });
|
|
627
|
+
} else {
|
|
628
|
+
const fieldNames = Object.keys(component);
|
|
629
|
+
const suggestion = findClosestMatch(rawName, fieldNames);
|
|
630
|
+
if (suggestion) {
|
|
631
|
+
errors.push(
|
|
632
|
+
`${componentName}: unknown field "${rawName}", did you mean "${toKebabCase(suggestion)}"?`
|
|
633
|
+
);
|
|
634
|
+
} else {
|
|
635
|
+
errors.push(`${componentName}: unknown field "${rawName}"`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
591
641
|
const parsed = parseValues(valueStr);
|
|
592
642
|
|
|
593
643
|
if (parsed.some((v) => v === null)) {
|
|
@@ -668,7 +718,7 @@ function parsePropertyString(
|
|
|
668
718
|
}
|
|
669
719
|
}
|
|
670
720
|
|
|
671
|
-
return { values, errors };
|
|
721
|
+
return { values, entityRefs, errors };
|
|
672
722
|
}
|
|
673
723
|
|
|
674
724
|
function isCSSAttrSyntax(value: string): boolean {
|