@rasamjs/core 0.2.0 → 0.3.1
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/API.md +14 -4
- package/CHANGELOG.md +71 -0
- package/COMPONENTS.md +229 -0
- package/README.md +46 -6
- package/RELEASING.md +4 -2
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/catalog/fragment.d.ts +2 -2
- package/dist/catalog/fragment.d.ts.map +1 -1
- package/dist/catalog/fragment.js.map +1 -1
- package/dist/changes/derived.d.ts +10 -0
- package/dist/changes/derived.d.ts.map +1 -0
- package/dist/changes/derived.js +14 -0
- package/dist/changes/derived.js.map +1 -0
- package/dist/changes/restore.d.ts.map +1 -1
- package/dist/changes/restore.js +76 -28
- package/dist/changes/restore.js.map +1 -1
- package/dist/changes/service.d.ts +5 -0
- package/dist/changes/service.d.ts.map +1 -1
- package/dist/changes/service.js +18 -4
- package/dist/changes/service.js.map +1 -1
- package/dist/changes/state.d.ts.map +1 -1
- package/dist/changes/state.js +33 -27
- package/dist/changes/state.js.map +1 -1
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +20 -4
- package/dist/changes.js.map +1 -1
- package/dist/components/changes.d.ts +34 -0
- package/dist/components/changes.d.ts.map +1 -0
- package/dist/components/changes.js +119 -0
- package/dist/components/changes.js.map +1 -0
- package/dist/components/component.d.ts +62 -0
- package/dist/components/component.d.ts.map +1 -0
- package/dist/components/component.js +355 -0
- package/dist/components/component.js.map +1 -0
- package/dist/components/decorators.d.ts +7 -0
- package/dist/components/decorators.d.ts.map +1 -0
- package/dist/components/decorators.js +83 -0
- package/dist/components/decorators.js.map +1 -0
- package/dist/components/metadata.d.ts +10 -0
- package/dist/components/metadata.d.ts.map +1 -0
- package/dist/components/metadata.js +85 -0
- package/dist/components/metadata.js.map +1 -0
- package/dist/components/registry.d.ts +24 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/registry.js +78 -0
- package/dist/components/registry.js.map +1 -0
- package/dist/components/types.d.ts +54 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +2 -0
- package/dist/components/types.js.map +1 -0
- package/dist/components/view.d.ts +9 -0
- package/dist/components/view.d.ts.map +1 -0
- package/dist/components/view.js +104 -0
- package/dist/components/view.js.map +1 -0
- package/dist/components.d.ts +6 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +5 -0
- package/dist/components.js.map +1 -0
- package/dist/core/node.d.ts +7 -0
- package/dist/core/node.d.ts.map +1 -1
- package/dist/core/node.js +30 -0
- package/dist/core/node.js.map +1 -1
- package/dist/interchange/clone.d.ts.map +1 -1
- package/dist/interchange/clone.js +3 -2
- package/dist/interchange/clone.js.map +1 -1
- package/dist/interchange/reconstruct.d.ts.map +1 -1
- package/dist/interchange/reconstruct.js +16 -12
- package/dist/interchange/reconstruct.js.map +1 -1
- package/dist/interchange/references.d.ts.map +1 -1
- package/dist/interchange/references.js +10 -4
- package/dist/interchange/references.js.map +1 -1
- package/dist/mixins/parent-internals.d.ts +6 -0
- package/dist/mixins/parent-internals.d.ts.map +1 -1
- package/dist/mixins/parent-internals.js +34 -0
- package/dist/mixins/parent-internals.js.map +1 -1
- package/dist/mixins/parent.d.ts.map +1 -1
- package/dist/mixins/parent.js +6 -1
- package/dist/mixins/parent.js.map +1 -1
- package/dist/mixins/types.d.ts +2 -2
- package/dist/mixins/types.d.ts.map +1 -1
- package/dist/resources/references.d.ts +2 -0
- package/dist/resources/references.d.ts.map +1 -1
- package/dist/resources/references.js +3 -3
- package/dist/resources/references.js.map +1 -1
- package/dist/services/catalog.d.ts.map +1 -1
- package/dist/services/catalog.js +5 -3
- package/dist/services/catalog.js.map +1 -1
- package/dist/services/claims.d.ts +7 -0
- package/dist/services/claims.d.ts.map +1 -0
- package/dist/services/claims.js +11 -0
- package/dist/services/claims.js.map +1 -0
- package/dist/services/defs-manager.d.ts +2 -0
- package/dist/services/defs-manager.d.ts.map +1 -1
- package/dist/services/defs-manager.js +4 -0
- package/dist/services/defs-manager.js.map +1 -1
- package/dist/services/element-key.d.ts +4 -0
- package/dist/services/element-key.d.ts.map +1 -0
- package/dist/services/element-key.js +12 -0
- package/dist/services/element-key.js.map +1 -0
- package/dist/services/initialization.d.ts +7 -0
- package/dist/services/initialization.d.ts.map +1 -0
- package/dist/services/initialization.js +27 -0
- package/dist/services/initialization.js.map +1 -0
- package/dist/services/registry.d.ts +8 -0
- package/dist/services/registry.d.ts.map +1 -1
- package/dist/services/registry.js +47 -10
- package/dist/services/registry.js.map +1 -1
- package/dist/services/types.d.ts +17 -0
- package/dist/services/types.d.ts.map +1 -1
- package/dist/template/parts.d.ts +12 -6
- package/dist/template/parts.d.ts.map +1 -1
- package/dist/template/parts.js +36 -29
- package/dist/template/parts.js.map +1 -1
- package/dist/template/render.d.ts +6 -7
- package/dist/template/render.d.ts.map +1 -1
- package/dist/template/render.js +188 -44
- package/dist/template/render.js.map +1 -1
- package/dist/template/svg-tag.d.ts +5 -1
- package/dist/template/svg-tag.d.ts.map +1 -1
- package/dist/template/svg-tag.js.map +1 -1
- package/dist/text/internals.d.ts +1 -0
- package/dist/text/internals.d.ts.map +1 -1
- package/dist/types/common.d.ts +27 -0
- package/dist/types/common.d.ts.map +1 -1
- package/package.json +10 -1
- package/reports/adoption.json +20 -20
- package/reports/animation-benchmark.json +613 -613
- package/reports/benchmark.json +27 -27
- package/reports/change-groups-benchmark.json +471 -0
- package/reports/components-benchmark-before.json +10 -0
- package/reports/components-benchmark.json +10 -0
- package/reports/creation.json +26 -26
- package/reports/interchange-benchmark.json +85 -85
- package/reports/phase-3-core-after.json +40 -0
- package/reports/phase-3-core-before.json +40 -0
- package/reports/public-api-contract.json +335 -17
- package/reports/size-components.json +103 -0
- package/reports/size-parity.json +50 -34
- package/reports/size.json +7 -4
- package/reports/svgjs-comparison.json +3049 -3049
- package/reports/svgjs-comparison.md +15 -15
- package/reports/svgjs-parity-current.json +133 -46
- package/src/advanced.ts +1 -1
- package/src/catalog/fragment.ts +2 -2
- package/src/changes/derived.ts +19 -0
- package/src/changes/restore.ts +78 -36
- package/src/changes/service.ts +23 -8
- package/src/changes/state.ts +37 -28
- package/src/changes.ts +24 -5
- package/src/components/changes.ts +131 -0
- package/src/components/component.ts +401 -0
- package/src/components/decorators.ts +106 -0
- package/src/components/metadata.ts +111 -0
- package/src/components/registry.ts +79 -0
- package/src/components/types.ts +66 -0
- package/src/components/view.ts +123 -0
- package/src/components.ts +15 -0
- package/src/core/node.ts +32 -1
- package/src/interchange/clone.ts +3 -2
- package/src/interchange/reconstruct.ts +16 -12
- package/src/interchange/references.ts +12 -4
- package/src/mixins/parent-internals.ts +37 -0
- package/src/mixins/parent.ts +13 -4
- package/src/mixins/types.ts +2 -2
- package/src/resources/references.ts +5 -5
- package/src/services/catalog.ts +5 -3
- package/src/services/claims.ts +14 -0
- package/src/services/defs-manager.ts +5 -0
- package/src/services/element-key.ts +14 -0
- package/src/services/initialization.ts +25 -0
- package/src/services/registry.ts +49 -10
- package/src/services/types.ts +21 -0
- package/src/template/parts.ts +54 -39
- package/src/template/render.ts +211 -54
- package/src/template/svg-tag.ts +6 -1
- package/src/text/internals.ts +1 -0
- package/src/types/common.ts +29 -0
package/dist/changes.js
CHANGED
|
@@ -29,6 +29,7 @@ export function beginChangeGroup(drawing, metadata = {}) {
|
|
|
29
29
|
const owner = drawing.context.root;
|
|
30
30
|
let id;
|
|
31
31
|
let changes = [];
|
|
32
|
+
const targets = new Set();
|
|
32
33
|
let invalidated = false;
|
|
33
34
|
// Subscribe before groupstart so edits made by start observers are captured too.
|
|
34
35
|
const unsubscribe = observeChanges(drawing, (event) => {
|
|
@@ -36,8 +37,21 @@ export function beginChangeGroup(drawing, metadata = {}) {
|
|
|
36
37
|
invalidated = true;
|
|
37
38
|
if (id === undefined && event.type === 'groupstart')
|
|
38
39
|
id = event.group;
|
|
39
|
-
if (event.type
|
|
40
|
-
|
|
40
|
+
if (event.type !== 'change')
|
|
41
|
+
return;
|
|
42
|
+
if (event.group === id && !event.temporary) {
|
|
43
|
+
for (const change of event.changes) {
|
|
44
|
+
changes.push(change);
|
|
45
|
+
targets.add(change.target);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (event.changes.some((change) => targets.has(change.target))) {
|
|
49
|
+
// A net-zero field must release its old baseline before an outside edit.
|
|
50
|
+
changes = [...combineChanges(changes)];
|
|
51
|
+
targets.clear();
|
|
52
|
+
for (const change of changes)
|
|
53
|
+
targets.add(change.target);
|
|
54
|
+
}
|
|
41
55
|
});
|
|
42
56
|
let group;
|
|
43
57
|
try {
|
|
@@ -58,6 +72,7 @@ export function beginChangeGroup(drawing, metadata = {}) {
|
|
|
58
72
|
const finish = (cancelled, restored = false) => {
|
|
59
73
|
active = false;
|
|
60
74
|
changes = [];
|
|
75
|
+
targets.clear();
|
|
61
76
|
unsubscribe();
|
|
62
77
|
group.end(cancelled, restored);
|
|
63
78
|
};
|
|
@@ -89,8 +104,9 @@ export function beginChangeGroup(drawing, metadata = {}) {
|
|
|
89
104
|
const failures = [];
|
|
90
105
|
try {
|
|
91
106
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
const combined = combineChanges(changes);
|
|
108
|
+
if (combined.length)
|
|
109
|
+
restoreChanges(owner, combined, 'undo');
|
|
94
110
|
}
|
|
95
111
|
catch (error) {
|
|
96
112
|
if (error instanceof ChangeObserverError)
|
package/dist/changes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AnyNode } from '../core/types.js';
|
|
2
|
+
export interface Binding {
|
|
3
|
+
readonly node: AnyNode;
|
|
4
|
+
derive(): void;
|
|
5
|
+
replay(): void;
|
|
6
|
+
paint(): void;
|
|
7
|
+
}
|
|
8
|
+
interface Discovery {
|
|
9
|
+
added(node: AnyNode): void;
|
|
10
|
+
identityChanged(node: AnyNode): void;
|
|
11
|
+
}
|
|
12
|
+
/** Routes work by changed node, independent of the number of components in a drawing. */
|
|
13
|
+
export declare class ComponentChanges {
|
|
14
|
+
private readonly bindings;
|
|
15
|
+
private readonly discovery;
|
|
16
|
+
private bindingCount;
|
|
17
|
+
private readonly dirty;
|
|
18
|
+
private cancelFrame?;
|
|
19
|
+
private readonly stopDerive;
|
|
20
|
+
private readonly stopObserve;
|
|
21
|
+
private readonly context;
|
|
22
|
+
static for(node: AnyNode): ComponentChanges;
|
|
23
|
+
private constructor();
|
|
24
|
+
bind(binding: Binding): () => void;
|
|
25
|
+
discover(discovery: Discovery): () => void;
|
|
26
|
+
private release;
|
|
27
|
+
/** Live state from every instance renders in one shared frame, as one temporary change. */
|
|
28
|
+
schedule(binding: Binding): void;
|
|
29
|
+
/** Renders without an undo step; failures surface after every other instance rendered. */
|
|
30
|
+
paint(bindings: Iterable<Binding>): void;
|
|
31
|
+
private dispatch;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=changes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/components/changes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,MAAM,IAAI,IAAI,CAAC;IACf,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACf;AACD,UAAU,SAAS;IACjB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CACtC;AAGD,yFAAyF;AACzF,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IAEzC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAO1C;IAED,OAAO,eAQN;IAED,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,IAAI,CASjC;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,IAAI,CAGzC;IAED,OAAO,CAAC,OAAO;IAef,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAM/B;IAED,0FAA0F;IAC1F,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAevC;IAED,OAAO,CAAC,QAAQ;CA0BjB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { deriveChanges, observeChanges, withChanges } from '../changes.js';
|
|
2
|
+
const drawings = new WeakMap();
|
|
3
|
+
/** Routes work by changed node, independent of the number of components in a drawing. */
|
|
4
|
+
export class ComponentChanges {
|
|
5
|
+
bindings = new WeakMap();
|
|
6
|
+
discovery = new Set();
|
|
7
|
+
bindingCount = 0;
|
|
8
|
+
dirty = new Set();
|
|
9
|
+
cancelFrame;
|
|
10
|
+
stopDerive;
|
|
11
|
+
stopObserve;
|
|
12
|
+
context;
|
|
13
|
+
static for(node) {
|
|
14
|
+
let changes = drawings.get(node.context);
|
|
15
|
+
if (!changes) {
|
|
16
|
+
changes = new ComponentChanges(node);
|
|
17
|
+
drawings.set(node.context, changes);
|
|
18
|
+
}
|
|
19
|
+
return changes;
|
|
20
|
+
}
|
|
21
|
+
constructor(drawing) {
|
|
22
|
+
this.context = drawing.context;
|
|
23
|
+
this.stopDerive = deriveChanges(drawing, ({ nodes }) => {
|
|
24
|
+
for (const node of nodes)
|
|
25
|
+
this.bindings.get(node)?.derive();
|
|
26
|
+
});
|
|
27
|
+
this.stopObserve = observeChanges(drawing, (event) => {
|
|
28
|
+
if (event.type === 'change')
|
|
29
|
+
this.dispatch(event);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
bind(binding) {
|
|
33
|
+
if (this.bindings.has(binding.node))
|
|
34
|
+
throw new Error('Component is already observing changes');
|
|
35
|
+
this.bindings.set(binding.node, binding);
|
|
36
|
+
this.bindingCount++;
|
|
37
|
+
return this.release(() => {
|
|
38
|
+
this.bindings.delete(binding.node);
|
|
39
|
+
this.dirty.delete(binding);
|
|
40
|
+
this.bindingCount--;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
discover(discovery) {
|
|
44
|
+
this.discovery.add(discovery);
|
|
45
|
+
return this.release(() => this.discovery.delete(discovery));
|
|
46
|
+
}
|
|
47
|
+
release(cleanup) {
|
|
48
|
+
let active = true;
|
|
49
|
+
return () => {
|
|
50
|
+
if (!active)
|
|
51
|
+
return;
|
|
52
|
+
active = false;
|
|
53
|
+
cleanup();
|
|
54
|
+
if (!this.bindingCount && !this.discovery.size) {
|
|
55
|
+
this.cancelFrame?.();
|
|
56
|
+
this.stopDerive();
|
|
57
|
+
this.stopObserve();
|
|
58
|
+
drawings.delete(this.context);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Live state from every instance renders in one shared frame, as one temporary change. */
|
|
63
|
+
schedule(binding) {
|
|
64
|
+
this.dirty.add(binding);
|
|
65
|
+
this.cancelFrame ??= this.context.dom.requestFrame(() => {
|
|
66
|
+
this.cancelFrame = undefined;
|
|
67
|
+
this.paint(this.dirty);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Renders without an undo step; failures surface after every other instance rendered. */
|
|
71
|
+
paint(bindings) {
|
|
72
|
+
const list = [...bindings].filter((binding) => !binding.node.isDestroyed);
|
|
73
|
+
for (const binding of list)
|
|
74
|
+
this.dirty.delete(binding);
|
|
75
|
+
if (!list.length)
|
|
76
|
+
return;
|
|
77
|
+
const failures = [];
|
|
78
|
+
withChanges(list[0].node, { source: 'components', temporary: true }, () => {
|
|
79
|
+
for (const binding of list)
|
|
80
|
+
try {
|
|
81
|
+
binding.paint();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
failures.push(error);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (failures.length > 1)
|
|
88
|
+
throw new AggregateError(failures, 'Component renders failed');
|
|
89
|
+
if (failures.length)
|
|
90
|
+
throw failures[0];
|
|
91
|
+
}
|
|
92
|
+
dispatch(event) {
|
|
93
|
+
// Replay skips rendered children, so restored and recreated instances redraw together.
|
|
94
|
+
const replayed = new Set();
|
|
95
|
+
for (const change of event.changes) {
|
|
96
|
+
const node = change.target.node, binding = this.bindings.get(node);
|
|
97
|
+
if (binding &&
|
|
98
|
+
event.source === 'replay' &&
|
|
99
|
+
((change.domain === 'property' && change.attribute === 'data-rasam-props') ||
|
|
100
|
+
(change.domain === 'existence' && change.after)))
|
|
101
|
+
replayed.add(binding);
|
|
102
|
+
if (!this.discovery.size)
|
|
103
|
+
continue;
|
|
104
|
+
if ((change.domain === 'placement' && change.after.parent && !change.before.parent) ||
|
|
105
|
+
(change.domain === 'existence' && change.after))
|
|
106
|
+
for (const subscriber of this.discovery)
|
|
107
|
+
subscriber.added(node);
|
|
108
|
+
else if (change.domain === 'property' && change.attribute === 'data-rasam-component')
|
|
109
|
+
for (const subscriber of this.discovery)
|
|
110
|
+
subscriber.identityChanged(node);
|
|
111
|
+
}
|
|
112
|
+
if (!replayed.size)
|
|
113
|
+
return;
|
|
114
|
+
for (const binding of replayed)
|
|
115
|
+
binding.replay();
|
|
116
|
+
this.paint(replayed);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../../src/components/changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Group } from '../elements/group.js';
|
|
2
|
+
import type { DrawingContext } from '../services/context.js';
|
|
3
|
+
import type { SvgTemplate } from '../template/svg-tag.js';
|
|
4
|
+
import { derivesChildren } from '../changes/derived.js';
|
|
5
|
+
import { initializeElement } from '../services/initialization.js';
|
|
6
|
+
import type { AttributeValue, Length, PortDefinition } from '../types/common.js';
|
|
7
|
+
import type { creationOptions } from '../services/types.js';
|
|
8
|
+
import type { ActionDefinition, ComponentDefinition, ComponentProps, PropDefinition, PropOptions, ComponentFields } from './types.js';
|
|
9
|
+
export declare const installField: unique symbol;
|
|
10
|
+
export declare const assertField: unique symbol;
|
|
11
|
+
export declare const editComponent: unique symbol;
|
|
12
|
+
export declare const registerAction: unique symbol;
|
|
13
|
+
/** A closed rasam group whose children are rendered from decorated props and live state. */
|
|
14
|
+
export declare abstract class Component extends Group {
|
|
15
|
+
#private;
|
|
16
|
+
readonly [creationOptions]: ComponentFields<this>;
|
|
17
|
+
static readonly ownsChildren = true;
|
|
18
|
+
/** @internal History restores props; the component redraws its children after replay. */
|
|
19
|
+
static readonly [derivesChildren] = true;
|
|
20
|
+
constructor(context: DrawingContext, input?: ComponentProps, existing?: SVGGElement);
|
|
21
|
+
get componentDefinition(): ComponentDefinition;
|
|
22
|
+
get editRules(): Readonly<{
|
|
23
|
+
rotate: NonNullable<number | "free" | false>;
|
|
24
|
+
flip: boolean;
|
|
25
|
+
resize: NonNullable<"props" | "scale" | false>;
|
|
26
|
+
}>;
|
|
27
|
+
get propDefinitions(): readonly PropDefinition[];
|
|
28
|
+
/** `@action` methods with their undo labels, in declaration order; overrides relabel. */
|
|
29
|
+
get actionDefinitions(): readonly ActionDefinition[];
|
|
30
|
+
/** @internal The `@action` decorator records each method once per instance. */
|
|
31
|
+
[registerAction](name: string, label: string): void;
|
|
32
|
+
render(): SvgTemplate;
|
|
33
|
+
/**
|
|
34
|
+
* Override to declare connection points, in local coordinates at 0°, from props and
|
|
35
|
+
* the saved size. Live state never moves a port. Defaults to none.
|
|
36
|
+
*/
|
|
37
|
+
ports(): readonly PortDefinition[];
|
|
38
|
+
/** Validated, frozen `ports()`, computed again only when the saved props change. */
|
|
39
|
+
get portDefinitions(): readonly PortDefinition[];
|
|
40
|
+
created(): void;
|
|
41
|
+
destroyed(): void;
|
|
42
|
+
changed(_name: string): void;
|
|
43
|
+
validateAttribute(name: string, value: AttributeValue): void;
|
|
44
|
+
setAttributeValue(name: string, value: AttributeValue): this;
|
|
45
|
+
attrs(values: Parameters<Group['attrs']>[0]): this;
|
|
46
|
+
width(): Length | null;
|
|
47
|
+
width(value: Length): this;
|
|
48
|
+
height(): Length | null;
|
|
49
|
+
height(value: Length): this;
|
|
50
|
+
/** For `resize: 'props'`, one undo step that redraws at once, so callers read new bounds. */
|
|
51
|
+
size(width: Length, height?: Length): this;
|
|
52
|
+
resetState(): void;
|
|
53
|
+
/** @internal Called after decorated fields and reconstructed children are available. */
|
|
54
|
+
[initializeElement](): void;
|
|
55
|
+
/** @internal Field decorators install accessors after their initializers execute. */
|
|
56
|
+
[assertField](name: string): void;
|
|
57
|
+
/** @internal Field decorators install accessors after their initializers execute. */
|
|
58
|
+
[installField](name: string, kind: 'prop' | 'state', options: PropOptions, initial: unknown): void;
|
|
59
|
+
/** @internal Actions share one change group across nested calls and instances. */
|
|
60
|
+
[editComponent]<Result>(label: string, callback: () => Result): Result;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/components/component.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { Group } from '../elements/group.js';
|
|
2
|
+
import { beginChangeGroup } from '../changes.js';
|
|
3
|
+
import { derivesChildren, markDerived } from '../changes/derived.js';
|
|
4
|
+
import { initializeElement } from '../services/initialization.js';
|
|
5
|
+
import { definition, isSize, validatePorts, validateProp } from './metadata.js';
|
|
6
|
+
import { ComponentView } from './view.js';
|
|
7
|
+
import { ComponentChanges } from './changes.js';
|
|
8
|
+
import { Attributes } from '../dom/attributes.js';
|
|
9
|
+
export const installField = Symbol('installComponentField');
|
|
10
|
+
export const assertField = Symbol('assertComponentField');
|
|
11
|
+
export const editComponent = Symbol('editComponent');
|
|
12
|
+
export const registerAction = Symbol('registerComponentAction');
|
|
13
|
+
const restoredHooks = new WeakSet();
|
|
14
|
+
const sizes = ['width', 'height'];
|
|
15
|
+
const copy = (value) => value && typeof value === 'object' ? structuredClone(value) : value;
|
|
16
|
+
/** A closed rasam group whose children are rendered from decorated props and live state. */
|
|
17
|
+
export class Component extends Group {
|
|
18
|
+
static ownsChildren = true;
|
|
19
|
+
/** @internal History restores props; the component redraws its children after replay. */
|
|
20
|
+
static [derivesChildren] = true;
|
|
21
|
+
#fields = new Map();
|
|
22
|
+
#view = new ComponentView(this);
|
|
23
|
+
#initialized = false;
|
|
24
|
+
#constructing = true;
|
|
25
|
+
#schedule;
|
|
26
|
+
#observedProps = {};
|
|
27
|
+
#propsCache;
|
|
28
|
+
#propDefinitions;
|
|
29
|
+
#actions = new Map();
|
|
30
|
+
#ports;
|
|
31
|
+
#readingPorts = false;
|
|
32
|
+
#input;
|
|
33
|
+
#reserved;
|
|
34
|
+
constructor(context, input = {}, existing) {
|
|
35
|
+
super(context, {}, existing);
|
|
36
|
+
this.#input = input;
|
|
37
|
+
this.#reserved = new Set(Object.getOwnPropertyNames(this));
|
|
38
|
+
}
|
|
39
|
+
get componentDefinition() {
|
|
40
|
+
return definition(this.constructor);
|
|
41
|
+
}
|
|
42
|
+
get editRules() {
|
|
43
|
+
const { rotate, flip, resize } = this.componentDefinition;
|
|
44
|
+
return Object.freeze({ rotate, flip, resize });
|
|
45
|
+
}
|
|
46
|
+
get propDefinitions() {
|
|
47
|
+
return (this.#propDefinitions ??= Object.freeze([...this.#fields]
|
|
48
|
+
.filter(([, field]) => field.kind === 'prop')
|
|
49
|
+
.map(([name, field]) => Object.freeze({ ...field.options, name, initial: field.initial }))));
|
|
50
|
+
}
|
|
51
|
+
/** `@action` methods with their undo labels, in declaration order; overrides relabel. */
|
|
52
|
+
get actionDefinitions() {
|
|
53
|
+
return Object.freeze([...this.#actions].map(([name, label]) => Object.freeze({ name, label })));
|
|
54
|
+
}
|
|
55
|
+
/** @internal The `@action` decorator records each method once per instance. */
|
|
56
|
+
[registerAction](name, label) {
|
|
57
|
+
this.#actions.set(name, label);
|
|
58
|
+
}
|
|
59
|
+
render() {
|
|
60
|
+
throw new TypeError('Components must implement render()');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Override to declare connection points, in local coordinates at 0°, from props and
|
|
64
|
+
* the saved size. Live state never moves a port. Defaults to none.
|
|
65
|
+
*/
|
|
66
|
+
ports() {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
/** Validated, frozen `ports()`, computed again only when the saved props change. */
|
|
70
|
+
get portDefinitions() {
|
|
71
|
+
const props = this.#readProps();
|
|
72
|
+
if (this.#ports?.props === props)
|
|
73
|
+
return this.#ports.ports;
|
|
74
|
+
this.#readingPorts = true;
|
|
75
|
+
try {
|
|
76
|
+
const ports = validatePorts(this.ports());
|
|
77
|
+
this.#ports = { props, ports };
|
|
78
|
+
return ports;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.#readingPorts = false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
created() { }
|
|
85
|
+
destroyed() { }
|
|
86
|
+
changed(_name) { }
|
|
87
|
+
validateAttribute(name, value) {
|
|
88
|
+
super.validateAttribute(name, value);
|
|
89
|
+
if (Attributes.normalizeName(name) === 'data-rasam-props' &&
|
|
90
|
+
#initialized in this &&
|
|
91
|
+
this.#initialized)
|
|
92
|
+
this.#assertProps(parseProps(value));
|
|
93
|
+
}
|
|
94
|
+
setAttributeValue(name, value) {
|
|
95
|
+
if (restoredHooks.has(this) && Attributes.normalizeName(name) === 'data-rasam-props')
|
|
96
|
+
return this;
|
|
97
|
+
return super.setAttributeValue(name, value);
|
|
98
|
+
}
|
|
99
|
+
attrs(values) {
|
|
100
|
+
if (restoredHooks.has(this))
|
|
101
|
+
values = Object.fromEntries(Object.entries(values).filter(([name]) => Attributes.normalizeName(name) !== 'data-rasam-props'));
|
|
102
|
+
return super.attrs(values);
|
|
103
|
+
}
|
|
104
|
+
/** `resize: 'props'` components keep their size as saved `width` and `height` props. */
|
|
105
|
+
get #sized() {
|
|
106
|
+
return this.componentDefinition.resize === 'props';
|
|
107
|
+
}
|
|
108
|
+
width(value) {
|
|
109
|
+
if (!this.#sized)
|
|
110
|
+
return value === undefined ? super.width() : super.width(value);
|
|
111
|
+
return value === undefined ? this.#size('width') : this.size(value, this.#size('height'));
|
|
112
|
+
}
|
|
113
|
+
height(value) {
|
|
114
|
+
if (!this.#sized)
|
|
115
|
+
return value === undefined ? super.height() : super.height(value);
|
|
116
|
+
return value === undefined ? this.#size('height') : this.size(this.#size('width'), value);
|
|
117
|
+
}
|
|
118
|
+
/** For `resize: 'props'`, one undo step that redraws at once, so callers read new bounds. */
|
|
119
|
+
size(width, height) {
|
|
120
|
+
if (!this.#sized)
|
|
121
|
+
return super.size(width, height);
|
|
122
|
+
this.assertLive();
|
|
123
|
+
const next = {
|
|
124
|
+
width: toSize(width),
|
|
125
|
+
height: height === undefined ? undefined : toSize(height),
|
|
126
|
+
};
|
|
127
|
+
const target = {
|
|
128
|
+
width: next.width,
|
|
129
|
+
height: next.height ?? (next.width * this.#size('height')) / this.#size('width'),
|
|
130
|
+
};
|
|
131
|
+
if (restoredHooks.has(this))
|
|
132
|
+
return this;
|
|
133
|
+
if (target.width === this.#size('width') && target.height === this.#size('height'))
|
|
134
|
+
return this;
|
|
135
|
+
this[editComponent](`Resize ${this.componentDefinition.name}`, () => {
|
|
136
|
+
this.setAttributeValue('data-rasam-props', JSON.stringify({ ...this.#readProps(), ...target }));
|
|
137
|
+
});
|
|
138
|
+
if (this.#initialized && this.#notifyProps())
|
|
139
|
+
this.#redraw();
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
#size(name) {
|
|
143
|
+
const saved = this.#readProps()[name] ?? this.#input[name];
|
|
144
|
+
return isSize(saved) ? saved : this.componentDefinition[name];
|
|
145
|
+
}
|
|
146
|
+
resetState() {
|
|
147
|
+
this.assertLive();
|
|
148
|
+
for (const [name, field] of this.#fields)
|
|
149
|
+
if (field.kind === 'state')
|
|
150
|
+
Reflect.set(this, name, copy(field.initial));
|
|
151
|
+
}
|
|
152
|
+
/** @internal Called after decorated fields and reconstructed children are available. */
|
|
153
|
+
[initializeElement]() {
|
|
154
|
+
if (this.#initialized)
|
|
155
|
+
return;
|
|
156
|
+
const metadata = this.componentDefinition;
|
|
157
|
+
const saved = this.#readProps();
|
|
158
|
+
const rawVersion = this.getAttributeValue('data-rasam-version');
|
|
159
|
+
const version = rawVersion === undefined ? metadata.version : Number(rawVersion);
|
|
160
|
+
if (!Number.isInteger(version) || version < 1 || version > metadata.version)
|
|
161
|
+
throw new TypeError(`Unsupported ${metadata.type} component version: ${rawVersion}`);
|
|
162
|
+
if (version < metadata.version && !metadata.migrate)
|
|
163
|
+
throw new TypeError(`No migration for ${metadata.type} version ${version}`);
|
|
164
|
+
const values = version < metadata.version ? metadata.migrate(saved, version) : saved;
|
|
165
|
+
this.#assertProps(values);
|
|
166
|
+
this.#assertProps(this.#input);
|
|
167
|
+
const props = {};
|
|
168
|
+
for (const [name, field] of this.#fields)
|
|
169
|
+
if (field.kind === 'prop') {
|
|
170
|
+
const value = Object.hasOwn(this.#input, name)
|
|
171
|
+
? this.#input[name]
|
|
172
|
+
: Object.hasOwn(values, name)
|
|
173
|
+
? values[name]
|
|
174
|
+
: field.initial;
|
|
175
|
+
validateProp(value, field.initial, field.options, name);
|
|
176
|
+
props[name] = value;
|
|
177
|
+
}
|
|
178
|
+
if (metadata.resize === 'props')
|
|
179
|
+
for (const name of sizes)
|
|
180
|
+
props[name] = (this.#input[name] ?? values[name] ?? metadata[name]);
|
|
181
|
+
this.#initialized = true;
|
|
182
|
+
if (!this.context.changes.replaying) {
|
|
183
|
+
this.setAttributeValue('data-rasam-version', metadata.version);
|
|
184
|
+
this.setAttributeValue('data-rasam-props', JSON.stringify(props));
|
|
185
|
+
this.#redraw();
|
|
186
|
+
}
|
|
187
|
+
this.#observedProps = this.#readProps();
|
|
188
|
+
const changes = ComponentChanges.for(this);
|
|
189
|
+
const binding = {
|
|
190
|
+
node: this,
|
|
191
|
+
derive: () => {
|
|
192
|
+
if (this.#notifyProps())
|
|
193
|
+
this.#redraw();
|
|
194
|
+
},
|
|
195
|
+
replay: () => {
|
|
196
|
+
this.#notifyProps();
|
|
197
|
+
},
|
|
198
|
+
paint: () => this.#redraw(),
|
|
199
|
+
};
|
|
200
|
+
const stop = changes.bind(binding);
|
|
201
|
+
this.#schedule = () => changes.schedule(binding);
|
|
202
|
+
this.onDispose(() => {
|
|
203
|
+
stop();
|
|
204
|
+
this.destroyed();
|
|
205
|
+
});
|
|
206
|
+
this.context.changes.local(() => {
|
|
207
|
+
if (this.adopting)
|
|
208
|
+
restoredHooks.add(this);
|
|
209
|
+
try {
|
|
210
|
+
this.created();
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
restoredHooks.delete(this);
|
|
214
|
+
this.#constructing = false;
|
|
215
|
+
}
|
|
216
|
+
// A newly constructed, detached node is not eligible for change derivation yet.
|
|
217
|
+
if (this.#readProps() !== this.#observedProps && this.#notifyProps())
|
|
218
|
+
this.#redraw();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** @internal Field decorators install accessors after their initializers execute. */
|
|
222
|
+
[assertField](name) {
|
|
223
|
+
if (name in Component.prototype || this.#fields.has(name) || this.#reserved.has(name))
|
|
224
|
+
throw new TypeError(`Component field conflicts with an inherited rasam member: ${name}`);
|
|
225
|
+
}
|
|
226
|
+
/** @internal Field decorators install accessors after their initializers execute. */
|
|
227
|
+
[installField](name, kind, options, initial) {
|
|
228
|
+
this[assertField](name);
|
|
229
|
+
if (kind === 'prop')
|
|
230
|
+
validateProp(initial, initial, options, name);
|
|
231
|
+
const field = {
|
|
232
|
+
kind,
|
|
233
|
+
options: Object.freeze({
|
|
234
|
+
...options,
|
|
235
|
+
...(options.options ? { options: Object.freeze([...options.options]) } : {}),
|
|
236
|
+
}),
|
|
237
|
+
initial: copy(initial),
|
|
238
|
+
value: initial,
|
|
239
|
+
};
|
|
240
|
+
this.#fields.set(name, field);
|
|
241
|
+
this.#propDefinitions = undefined;
|
|
242
|
+
Object.defineProperty(this, name, {
|
|
243
|
+
enumerable: true,
|
|
244
|
+
configurable: false,
|
|
245
|
+
get: () => {
|
|
246
|
+
if (kind === 'state') {
|
|
247
|
+
if (this.#readingPorts)
|
|
248
|
+
throw new TypeError(`ports() cannot read live state: ${name}`);
|
|
249
|
+
return field.value;
|
|
250
|
+
}
|
|
251
|
+
const props = this.#readProps();
|
|
252
|
+
return Object.hasOwn(props, name)
|
|
253
|
+
? props[name]
|
|
254
|
+
: Object.hasOwn(this.#input, name)
|
|
255
|
+
? this.#input[name]
|
|
256
|
+
: initial;
|
|
257
|
+
},
|
|
258
|
+
set: (value) => {
|
|
259
|
+
this.assertLive();
|
|
260
|
+
if (kind === 'state') {
|
|
261
|
+
if (Object.is(field.value, value))
|
|
262
|
+
return;
|
|
263
|
+
field.value = value;
|
|
264
|
+
if (this.#initialized) {
|
|
265
|
+
this.changed(name);
|
|
266
|
+
this.#schedule?.();
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (restoredHooks.has(this))
|
|
271
|
+
return;
|
|
272
|
+
validateProp(value, initial, options, name);
|
|
273
|
+
if (Object.is(Reflect.get(this, name), value))
|
|
274
|
+
return;
|
|
275
|
+
this[editComponent](`Set ${options.label ?? name}`, () => {
|
|
276
|
+
this.setAttributeValue('data-rasam-props', JSON.stringify({ ...this.#readProps(), [name]: value }));
|
|
277
|
+
});
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** @internal Actions share one change group across nested calls and instances. */
|
|
282
|
+
[editComponent](label, callback) {
|
|
283
|
+
this.assertLive();
|
|
284
|
+
if (this.#constructing || this.context.changes.currentGroup !== undefined)
|
|
285
|
+
return callback();
|
|
286
|
+
const group = beginChangeGroup(this, { source: 'components', label });
|
|
287
|
+
try {
|
|
288
|
+
const result = group.run(callback);
|
|
289
|
+
if (result && typeof result.then === 'function')
|
|
290
|
+
throw new TypeError('Component actions must be synchronous');
|
|
291
|
+
group.end();
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
try {
|
|
296
|
+
group.cancel();
|
|
297
|
+
}
|
|
298
|
+
catch (rollback) {
|
|
299
|
+
throw new AggregateError([error, rollback], 'Component edit and rollback failed');
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
#readProps() {
|
|
305
|
+
// Retain ordinary field-read semantics for destroyed() and post-disposal inspection.
|
|
306
|
+
const raw = this.attributes.get('data-rasam-props');
|
|
307
|
+
if (this.#propsCache && this.#propsCache.raw === raw)
|
|
308
|
+
return this.#propsCache.props;
|
|
309
|
+
this.#propsCache = { raw, props: Object.freeze(parseProps(raw)) };
|
|
310
|
+
return this.#propsCache.props;
|
|
311
|
+
}
|
|
312
|
+
#assertProps(props) {
|
|
313
|
+
if (!props || typeof props !== 'object' || Array.isArray(props))
|
|
314
|
+
throw new TypeError('Component props must be an object');
|
|
315
|
+
for (const [name, value] of Object.entries(props)) {
|
|
316
|
+
if (this.#sized && sizes.includes(name)) {
|
|
317
|
+
if (!isSize(value))
|
|
318
|
+
throw new RangeError(`Component ${name} must be a positive number`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const field = this.#fields.get(name);
|
|
322
|
+
if (!field || field.kind !== 'prop')
|
|
323
|
+
throw new TypeError(`Unknown component prop: ${name}`);
|
|
324
|
+
validateProp(value, field.initial, field.options, name);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
#notifyProps() {
|
|
328
|
+
const props = this.#readProps(), previous = this.#observedProps;
|
|
329
|
+
if (props === previous)
|
|
330
|
+
return false;
|
|
331
|
+
this.#observedProps = props;
|
|
332
|
+
const names = [...this.propDefinitions.map((field) => field.name), ...sizes].filter((name) => !Object.is(previous[name], props[name]));
|
|
333
|
+
for (const name of names)
|
|
334
|
+
this.changed(name);
|
|
335
|
+
return names.length > 0;
|
|
336
|
+
}
|
|
337
|
+
#redraw() {
|
|
338
|
+
this.withOwnChildren(() => this.#view.render(this.render()));
|
|
339
|
+
markDerived(this);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Sizes are user units of at least 1, so a collapsing resize never reaches zero. */
|
|
343
|
+
function toSize(value) {
|
|
344
|
+
const size = typeof value === 'number' ? value : Number(value);
|
|
345
|
+
if (!Number.isFinite(size) || size < 0)
|
|
346
|
+
throw new RangeError('Component sizes must be finite non-negative numbers');
|
|
347
|
+
return Math.max(size, 1);
|
|
348
|
+
}
|
|
349
|
+
function parseProps(raw) {
|
|
350
|
+
const props = raw == null ? {} : JSON.parse(String(raw));
|
|
351
|
+
if (!props || typeof props !== 'object' || Array.isArray(props))
|
|
352
|
+
throw new TypeError('Component props must be a JSON object');
|
|
353
|
+
return props;
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=component.js.map
|