@getaster/elements 0.0.0-stage → 0.1.0-preview.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/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +6 -2
- package/bin/aster-theme-check.js +317 -0
- package/dist/base/drag.d.ts +45 -0
- package/dist/base/drag.js +139 -0
- package/dist/base/element.d.ts +51 -0
- package/dist/base/element.js +133 -0
- package/dist/breadcrumbs/breadcrumbs.d.ts +23 -0
- package/dist/breadcrumbs/breadcrumbs.js +51 -0
- package/dist/button/button.d.ts +37 -0
- package/dist/button/button.js +108 -0
- package/dist/caret/caret-group.d.ts +14 -0
- package/dist/caret/caret-group.js +71 -0
- package/dist/carousel/carousel.d.ts +28 -0
- package/dist/carousel/carousel.js +134 -0
- package/dist/component/component.d.ts +73 -0
- package/dist/component/component.js +190 -0
- package/dist/component/echarts.d.ts +46 -0
- package/dist/component/echarts.js +95 -0
- package/dist/composition/authoring.d.ts +157 -0
- package/dist/composition/authoring.js +291 -0
- package/dist/composition/builtins.d.ts +1130 -0
- package/dist/composition/builtins.js +318 -0
- package/dist/composition/command-bar.d.ts +18 -0
- package/dist/composition/command-bar.js +34 -0
- package/dist/composition/commands.d.ts +14 -0
- package/dist/composition/commands.js +59 -0
- package/dist/composition/composite.d.ts +48 -0
- package/dist/composition/composite.js +409 -0
- package/dist/composition/contracts.d.ts +63 -0
- package/dist/composition/contracts.js +97 -0
- package/dist/composition/definition.d.ts +94 -0
- package/dist/composition/definition.js +291 -0
- package/dist/composition/element.d.ts +35 -0
- package/dist/composition/element.js +51 -0
- package/dist/composition/registry.d.ts +83 -0
- package/dist/composition/registry.js +99 -0
- package/dist/composition/schema.d.ts +9 -0
- package/dist/composition/schema.js +139 -0
- package/dist/composition/sessions.d.ts +15 -0
- package/dist/composition/sessions.js +61 -0
- package/dist/dashboard/dashboard.d.ts +26 -0
- package/dist/dashboard/dashboard.js +296 -0
- package/dist/dashboard/layout.d.ts +19 -0
- package/dist/dashboard/layout.js +48 -0
- package/dist/dashboard/widget.d.ts +42 -0
- package/dist/dashboard/widget.js +165 -0
- package/dist/date/calendar.d.ts +34 -0
- package/dist/date/calendar.js +137 -0
- package/dist/date/date-field.d.ts +18 -0
- package/dist/date/date-field.js +81 -0
- package/dist/date/dates.d.ts +22 -0
- package/dist/date/dates.js +80 -0
- package/dist/dialog/dialog.d.ts +23 -0
- package/dist/dialog/dialog.js +70 -0
- package/dist/field/bound-field.d.ts +55 -0
- package/dist/field/bound-field.js +139 -0
- package/dist/field/check.d.ts +11 -0
- package/dist/field/check.js +26 -0
- package/dist/field/composite-field.d.ts +35 -0
- package/dist/field/composite-field.js +88 -0
- package/dist/field/field.d.ts +29 -0
- package/dist/field/field.js +75 -0
- package/dist/field/input.d.ts +15 -0
- package/dist/field/input.js +18 -0
- package/dist/field/number-field.d.ts +33 -0
- package/dist/field/number-field.js +179 -0
- package/dist/field/numeric.d.ts +4 -0
- package/dist/field/numeric.js +57 -0
- package/dist/field/values.d.ts +14 -0
- package/dist/field/values.js +60 -0
- package/dist/filter/filter-builder.d.ts +18 -0
- package/dist/filter/filter-builder.js +305 -0
- package/dist/form/controls.d.ts +20 -0
- package/dist/form/controls.js +34 -0
- package/dist/form/form.d.ts +46 -0
- package/dist/form/form.js +85 -0
- package/dist/format.d.ts +10 -0
- package/dist/format.js +57 -0
- package/dist/grid/filters.d.ts +16 -0
- package/dist/grid/filters.js +66 -0
- package/dist/grid/grid.d.ts +209 -0
- package/dist/grid/grid.js +2584 -0
- package/dist/grid/rows.d.ts +32 -0
- package/dist/grid/rows.js +89 -0
- package/dist/grid/view.d.ts +69 -0
- package/dist/grid/view.js +172 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +61 -0
- package/dist/layout/stack.d.ts +112 -0
- package/dist/layout/stack.js +21 -0
- package/dist/list-edit/list-edit.d.ts +42 -0
- package/dist/list-edit/list-edit.js +220 -0
- package/dist/listbox/listbox.d.ts +45 -0
- package/dist/listbox/listbox.js +187 -0
- package/dist/menu/menu.d.ts +55 -0
- package/dist/menu/menu.js +286 -0
- package/dist/motion.d.ts +8 -0
- package/dist/motion.js +23 -0
- package/dist/overlay/anchor.d.ts +18 -0
- package/dist/overlay/anchor.js +41 -0
- package/dist/picker/options.d.ts +59 -0
- package/dist/picker/options.js +97 -0
- package/dist/picker/picker.d.ts +46 -0
- package/dist/picker/picker.js +492 -0
- package/dist/picker/tree-choices.d.ts +38 -0
- package/dist/picker/tree-choices.js +72 -0
- package/dist/picker/tree-picker.d.ts +29 -0
- package/dist/picker/tree-picker.js +282 -0
- package/dist/progress/policy.d.ts +5 -0
- package/dist/progress/policy.js +28 -0
- package/dist/progress/status.d.ts +20 -0
- package/dist/progress/status.js +38 -0
- package/dist/rule/operators.d.ts +8 -0
- package/dist/rule/operators.js +9 -0
- package/dist/rule/rule-editor.d.ts +57 -0
- package/dist/rule/rule-editor.js +290 -0
- package/dist/section/accordion.d.ts +24 -0
- package/dist/section/accordion.js +90 -0
- package/dist/section/section.d.ts +30 -0
- package/dist/section/section.js +99 -0
- package/dist/select/choice-element.d.ts +20 -0
- package/dist/select/choice-element.js +55 -0
- package/dist/select/choice.d.ts +34 -0
- package/dist/select/choice.js +122 -0
- package/dist/select/many.d.ts +13 -0
- package/dist/select/many.js +20 -0
- package/dist/select/select.d.ts +16 -0
- package/dist/select/select.js +40 -0
- package/dist/sidebar/sidebar.d.ts +47 -0
- package/dist/sidebar/sidebar.js +105 -0
- package/dist/slicer/slicer.d.ts +26 -0
- package/dist/slicer/slicer.js +140 -0
- package/dist/split/split.d.ts +41 -0
- package/dist/split/split.js +237 -0
- package/dist/styles/contract.d.ts +36 -0
- package/dist/styles/contract.js +39 -0
- package/dist/switch/switch.d.ts +22 -0
- package/dist/switch/switch.js +69 -0
- package/dist/tabs/tabs.d.ts +30 -0
- package/dist/tabs/tabs.js +202 -0
- package/dist/tile-grid/scroll.d.ts +9 -0
- package/dist/tile-grid/scroll.js +21 -0
- package/dist/tile-grid/tile-grid.d.ts +64 -0
- package/dist/tile-grid/tile-grid.js +734 -0
- package/dist/toast/toast.d.ts +25 -0
- package/dist/toast/toast.js +92 -0
- package/dist/tree/navigator.d.ts +41 -0
- package/dist/tree/navigator.js +228 -0
- package/dist/window/chrome.d.ts +3 -0
- package/dist/window/chrome.js +7 -0
- package/dist/window/window.d.ts +46 -0
- package/dist/window/window.js +347 -0
- package/dist/window/workspace.d.ts +29 -0
- package/dist/window/workspace.js +137 -0
- package/package.json +45 -4
- package/styles/aster-ui.css +1145 -0
- package/styles/base.css +86 -0
- package/styles/contract.ts +41 -0
- package/styles/elements.css +1058 -0
- package/styles/harbour.css +126 -0
- package/styles/lamplight.css +118 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { planCommands } from "./commands.js";
|
|
2
|
+
import { computed, effect, signal, untracked } from "@preact/signals-core";
|
|
3
|
+
import { render, nothing } from "lit-html";
|
|
4
|
+
import { Command, Lifetime, PageScope, SessionActivity, command } from "@getaster/data";
|
|
5
|
+
import { provideScope } from "../base/element.js";
|
|
6
|
+
import { CompositeError, defineComposite, deriveComposite, located, record } from "./definition.js";
|
|
7
|
+
import { checkContract } from "./registry.js";
|
|
8
|
+
function isSignal(value) {
|
|
9
|
+
return !!value && typeof value === "object" && "peek" in value && "subscribe" in value;
|
|
10
|
+
}
|
|
11
|
+
/** A logical owner with a stable scope and independently activated named views. */
|
|
12
|
+
export class Composite {
|
|
13
|
+
registry;
|
|
14
|
+
element = document.createElement("div");
|
|
15
|
+
lifetime = new Lifetime();
|
|
16
|
+
scope;
|
|
17
|
+
activity;
|
|
18
|
+
commands;
|
|
19
|
+
children;
|
|
20
|
+
model;
|
|
21
|
+
definition;
|
|
22
|
+
#refs = new Map();
|
|
23
|
+
#sources;
|
|
24
|
+
constructor(definition, registry, ...args) {
|
|
25
|
+
this.registry = registry;
|
|
26
|
+
const options = args[0] ?? {};
|
|
27
|
+
this.#sources = options.sources ?? {};
|
|
28
|
+
this.activity = this.lifetime.own(new SessionActivity(options.activity ? { parent: options.activity, inactive: "continue" } : {}));
|
|
29
|
+
this.scope = new PageScope(options.parentScope, options.ctx ? { ctx: options.ctx } : {});
|
|
30
|
+
this.element.className = "aster-composite";
|
|
31
|
+
this.element.tabIndex = -1;
|
|
32
|
+
provideScope(this.element, this.scope);
|
|
33
|
+
try {
|
|
34
|
+
if (registry.inputs)
|
|
35
|
+
checkContract(options.inputs ?? {}, registry.inputs, "inputs");
|
|
36
|
+
this.definition = options.overrides ? deriveComposite(definition, options.overrides) : defineComposite(definition);
|
|
37
|
+
definition = this.definition;
|
|
38
|
+
this.element.dataset.composite = definition.name;
|
|
39
|
+
for (const [key, value] of Object.entries(options.inputs ?? {}))
|
|
40
|
+
this.#refs.set(`inputs.${key}`, value);
|
|
41
|
+
// Validate registration names before constructing the model or starting data work.
|
|
42
|
+
for (const [key, child] of Object.entries(definition.children))
|
|
43
|
+
this.#component(child.type, `definition.children.${key}.type`);
|
|
44
|
+
let type;
|
|
45
|
+
try {
|
|
46
|
+
type = registry.model(definition.model.type);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new CompositeError("definition.model.type", String(error));
|
|
50
|
+
}
|
|
51
|
+
const inputs = this.#values(definition.model.inputs ?? {}, "definition.model.inputs");
|
|
52
|
+
checkContract(inputs, type.inputs, "definition.model.inputs");
|
|
53
|
+
const model = untracked(() => type.create(inputs, this.lifetime, this.activity));
|
|
54
|
+
checkContract(model.values, type.exports, "definition.model.exports");
|
|
55
|
+
this.model = model.values;
|
|
56
|
+
for (const [key, value] of Object.entries(model.values))
|
|
57
|
+
this.#refs.set(`model.${key}`, value);
|
|
58
|
+
for (const [key, value] of Object.entries(model.publications ?? {}))
|
|
59
|
+
this.#refs.set(`model.publications.${key}`, value);
|
|
60
|
+
for (const [key, value] of Object.entries(model.commands ?? {}))
|
|
61
|
+
this.#refs.set(`model.commands.${key}`, value);
|
|
62
|
+
for (const [key, spec] of Object.entries(definition.publish ?? {})) {
|
|
63
|
+
const path = `definition.publish.${key}`;
|
|
64
|
+
const publication = this.#ref(spec.ref, path);
|
|
65
|
+
if (!publication || !publication.values || !Object.values(publication.values).every(isSignal))
|
|
66
|
+
throw new CompositeError(path, "expected a publication");
|
|
67
|
+
this.lifetime.defer(this.scope.publish(key, publication));
|
|
68
|
+
}
|
|
69
|
+
const commands = Object.create(null);
|
|
70
|
+
const plan = planCommands(definition.commands ?? {}, reference => this.#refs.get(reference) instanceof Command);
|
|
71
|
+
if (plan.errors[0])
|
|
72
|
+
throw new CompositeError(plan.errors[0].path, plan.errors[0].message);
|
|
73
|
+
for (const key of plan.order) {
|
|
74
|
+
const spec = definition.commands[key];
|
|
75
|
+
const path = `definition.commands.${key}`;
|
|
76
|
+
const base = this.#ref(spec.use, `${path}.use`);
|
|
77
|
+
if (!(base instanceof Command))
|
|
78
|
+
throw new CompositeError(path, "expected an exported Command");
|
|
79
|
+
const rule = spec.enabledWhen;
|
|
80
|
+
if (rule) {
|
|
81
|
+
const errors = this.scope.checkRule(rule);
|
|
82
|
+
if (errors.length)
|
|
83
|
+
throw new CompositeError(`${path}.enabledWhen`, errors.join("; "));
|
|
84
|
+
}
|
|
85
|
+
const state = rule ? this.scope.state(`commands.${key}`, { enabled: { when: rule, ...(spec.reason ? { reason: spec.reason } : {}) } }, { availability: true }) : undefined;
|
|
86
|
+
const allowed = computed(() => state?.value.enabled ?? true);
|
|
87
|
+
const action = this.lifetime.own(command({
|
|
88
|
+
label: base.label,
|
|
89
|
+
enabled: () => base.enabled.value && allowed.value,
|
|
90
|
+
reason: () => base.reason.value ?? spec.reason ?? "The page rule is not satisfied.",
|
|
91
|
+
run: () => base.execute(),
|
|
92
|
+
}));
|
|
93
|
+
commands[key] = action;
|
|
94
|
+
this.#refs.set(`commands.${key}`, action);
|
|
95
|
+
}
|
|
96
|
+
this.commands = Object.freeze(Object.fromEntries(Object.keys(definition.commands ?? {}).map(key => [key, commands[key]])));
|
|
97
|
+
const children = Object.create(null);
|
|
98
|
+
// Resolve every public binding before starting lazy imports.
|
|
99
|
+
for (const [key, spec] of Object.entries(definition.children)) {
|
|
100
|
+
const path = `definition.children.${key}`;
|
|
101
|
+
const entry = this.#component(spec.type, `${path}.type`);
|
|
102
|
+
const props = this.#props(spec.props ?? {}, `${path}.props`);
|
|
103
|
+
if (spec.enabled !== false)
|
|
104
|
+
checkContract(this.#readProps(props), entry.props, `${path}.props`);
|
|
105
|
+
children[key] = new Child(key, spec, entry, props, this);
|
|
106
|
+
}
|
|
107
|
+
this.children = children;
|
|
108
|
+
const activations = [];
|
|
109
|
+
this.#activation(definition.layout, children, () => true, "definition.layout", activations);
|
|
110
|
+
if (options.placement) {
|
|
111
|
+
const placed = new Set();
|
|
112
|
+
const template = options.placement(key => {
|
|
113
|
+
if (key === undefined) {
|
|
114
|
+
for (const name of Object.keys(children)) {
|
|
115
|
+
if (placed.has(name))
|
|
116
|
+
throw new CompositeError("placement", `duplicate placement '${name}'`);
|
|
117
|
+
placed.add(name);
|
|
118
|
+
}
|
|
119
|
+
return this.#layout(definition.layout, children);
|
|
120
|
+
}
|
|
121
|
+
if (!Object.hasOwn(children, key))
|
|
122
|
+
throw new CompositeError("placement", `unknown child '${key}'`);
|
|
123
|
+
if (placed.has(key))
|
|
124
|
+
throw new CompositeError("placement", `duplicate placement '${key}'`);
|
|
125
|
+
placed.add(key);
|
|
126
|
+
return children[key].element;
|
|
127
|
+
});
|
|
128
|
+
render(template, this.element);
|
|
129
|
+
this.lifetime.defer(() => render(nothing, this.element));
|
|
130
|
+
for (const key of Object.keys(children))
|
|
131
|
+
if (!placed.has(key) || !this.element.contains(children[key].element))
|
|
132
|
+
throw new CompositeError("placement", `child '${key}' has no placement`);
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
this.element.append(this.#layout(definition.layout, children));
|
|
136
|
+
for (const activate of activations)
|
|
137
|
+
activate();
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.lifetime.dispose();
|
|
141
|
+
throw located(error, this.#sources);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
mount(target) {
|
|
145
|
+
if (this.lifetime.disposed)
|
|
146
|
+
throw new Error("Composite is disposed");
|
|
147
|
+
target.append(this.element);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/** The caller performs close/draft guards before unconditional teardown. */
|
|
151
|
+
dispose() { try {
|
|
152
|
+
this.lifetime.dispose();
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.element.remove();
|
|
156
|
+
} }
|
|
157
|
+
#component(type, path) {
|
|
158
|
+
try {
|
|
159
|
+
return this.registry.component(type);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new CompositeError(path, String(error));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
#ref(path, at) {
|
|
166
|
+
if (!this.#refs.has(path))
|
|
167
|
+
throw new CompositeError(at, `unknown public reference '${path}'`);
|
|
168
|
+
return this.#refs.get(path);
|
|
169
|
+
}
|
|
170
|
+
#value(value, path, property = "") {
|
|
171
|
+
if (value === null || typeof value !== "object")
|
|
172
|
+
return value;
|
|
173
|
+
if (Array.isArray(value))
|
|
174
|
+
return value.map((item, i) => this.#value(item, `${path}.${i}`, property));
|
|
175
|
+
const obj = record(value, path);
|
|
176
|
+
for (const kind of ["ref", "bind", "renderer"])
|
|
177
|
+
if (Object.hasOwn(obj, kind)) {
|
|
178
|
+
if (Object.keys(obj).length !== 1 || typeof obj[kind] !== "string")
|
|
179
|
+
throw new CompositeError(path, `invalid ${kind} node`);
|
|
180
|
+
if (kind === "renderer") {
|
|
181
|
+
let renderer;
|
|
182
|
+
try {
|
|
183
|
+
renderer = this.registry.renderer(obj[kind]);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw new CompositeError(path, String(error));
|
|
187
|
+
}
|
|
188
|
+
if (!renderer.accepts(property))
|
|
189
|
+
throw new CompositeError(path, `renderer is not registered for '${property}'`);
|
|
190
|
+
return renderer.render;
|
|
191
|
+
}
|
|
192
|
+
const result = this.#ref(obj[kind], path);
|
|
193
|
+
if (kind === "bind") {
|
|
194
|
+
if (!isSignal(result))
|
|
195
|
+
throw new CompositeError(path, "bind requires a signal");
|
|
196
|
+
return result.value;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
return Object.fromEntries(Object.entries(obj).map(([key, item]) => [key, this.#value(item, `${path}.${key}`, property)]));
|
|
201
|
+
}
|
|
202
|
+
#values(values, path) {
|
|
203
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, this.#value(value, `${path}.${key}`, key)]));
|
|
204
|
+
}
|
|
205
|
+
#props(values, path) {
|
|
206
|
+
return computed(() => this.#values(values, path));
|
|
207
|
+
}
|
|
208
|
+
#readProps(props) { return props.value; }
|
|
209
|
+
#activation(spec, children, active, path, activations) {
|
|
210
|
+
if ("child" in spec) {
|
|
211
|
+
const child = children[spec.child];
|
|
212
|
+
activations.push(() => this.lifetime.defer(effect(() => {
|
|
213
|
+
const show = active();
|
|
214
|
+
untracked(() => child.activate(show));
|
|
215
|
+
})));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
switch (spec.type) {
|
|
219
|
+
case "stack":
|
|
220
|
+
spec.items.forEach((item, i) => this.#activation(item, children, active, `${path}.items.${i}`, activations));
|
|
221
|
+
break;
|
|
222
|
+
case "deck": {
|
|
223
|
+
const current = computed(() => this.#value(spec.active, `${path}.active`));
|
|
224
|
+
for (const [key, item] of Object.entries(spec.items))
|
|
225
|
+
this.#activation(item, children, () => active() && current.value === key, `${path}.items.${key}`, activations);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "outlet": {
|
|
229
|
+
const current = computed(() => this.#value(spec.active, `${path}.active`));
|
|
230
|
+
this.#activation(spec.content, children, () => active() && current.value === true, `${path}.content`, activations);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
#layout(spec, children) {
|
|
236
|
+
if ("child" in spec)
|
|
237
|
+
return children[spec.child].element;
|
|
238
|
+
const node = document.createElement("div");
|
|
239
|
+
node.className = `aster-composite-${spec.type}`;
|
|
240
|
+
switch (spec.type) {
|
|
241
|
+
case "stack":
|
|
242
|
+
spec.items.forEach(item => node.append(this.#layout(item, children)));
|
|
243
|
+
break;
|
|
244
|
+
case "deck":
|
|
245
|
+
Object.values(spec.items).forEach(item => node.append(this.#layout(item, children)));
|
|
246
|
+
break;
|
|
247
|
+
case "outlet":
|
|
248
|
+
node.append(this.#layout(spec.content, children));
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
return node;
|
|
252
|
+
}
|
|
253
|
+
report(error) { return located(error, this.#sources); }
|
|
254
|
+
}
|
|
255
|
+
/** Use either a compiled YAML definition or a JS definition as a registered child. */
|
|
256
|
+
export function compositeComponent(definition, registry, inputs, sources) {
|
|
257
|
+
return {
|
|
258
|
+
props: inputs,
|
|
259
|
+
create: ({ props, scope, activity }) => {
|
|
260
|
+
const instance = new Composite(definition, registry, { inputs: props, parentScope: scope, ...(activity ? { activity } : {}), ...(sources ? { sources } : {}) });
|
|
261
|
+
return { element: instance.element, scope: instance.scope, setActive: active => instance.activity.setActive(active), dispose: () => instance.dispose() };
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
class Child {
|
|
266
|
+
key;
|
|
267
|
+
spec;
|
|
268
|
+
type;
|
|
269
|
+
props;
|
|
270
|
+
owner;
|
|
271
|
+
element = document.createElement("div");
|
|
272
|
+
#status = signal("idle");
|
|
273
|
+
#error = signal(undefined);
|
|
274
|
+
status = this.#status;
|
|
275
|
+
error = this.#error;
|
|
276
|
+
#lifetime;
|
|
277
|
+
#pending;
|
|
278
|
+
#session;
|
|
279
|
+
#generation = 0;
|
|
280
|
+
#activity;
|
|
281
|
+
constructor(key, spec, type, props, owner) {
|
|
282
|
+
this.key = key;
|
|
283
|
+
this.spec = spec;
|
|
284
|
+
this.type = type;
|
|
285
|
+
this.props = props;
|
|
286
|
+
this.owner = owner;
|
|
287
|
+
this.#activity = owner.lifetime.own(new SessionActivity({ parent: owner.activity, active: false }));
|
|
288
|
+
this.element.className = "aster-composite-child";
|
|
289
|
+
this.element.dataset.child = key;
|
|
290
|
+
this.element.hidden = true;
|
|
291
|
+
if (spec.enabled === false)
|
|
292
|
+
this.#status.value = "suppressed";
|
|
293
|
+
owner.lifetime.defer(() => { this.#reset(); this.#status.value = "disposed"; });
|
|
294
|
+
}
|
|
295
|
+
activate(active) {
|
|
296
|
+
if (this.spec.enabled === false)
|
|
297
|
+
return;
|
|
298
|
+
this.#activity.setActive(active);
|
|
299
|
+
this.#session?.setActive?.(active);
|
|
300
|
+
if (!active && this.element.contains(document.activeElement))
|
|
301
|
+
this.owner.element.focus();
|
|
302
|
+
this.element.hidden = !active;
|
|
303
|
+
this.element.inert = !active;
|
|
304
|
+
if (!active && this.spec.retain === false) {
|
|
305
|
+
this.#reset();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (active || this.spec.create !== "on-demand") {
|
|
309
|
+
if (this.#status.peek() === "idle")
|
|
310
|
+
void this.ensure().catch(() => { });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
ensure() {
|
|
314
|
+
if (this.owner.lifetime.disposed)
|
|
315
|
+
return Promise.reject(new Error("Composite is disposed"));
|
|
316
|
+
if (this.spec.enabled === false)
|
|
317
|
+
return Promise.reject(new Error(`Child '${this.key}' is suppressed`));
|
|
318
|
+
if (this.#pending)
|
|
319
|
+
return this.#pending;
|
|
320
|
+
if (this.#session)
|
|
321
|
+
return Promise.resolve(this.#session.element);
|
|
322
|
+
const lifetime = this.#lifetime = new Lifetime();
|
|
323
|
+
const generation = ++this.#generation;
|
|
324
|
+
this.#status.value = "loading";
|
|
325
|
+
this.#error.value = undefined;
|
|
326
|
+
this.element.setAttribute("aria-busy", "true");
|
|
327
|
+
const status = document.createElement("p");
|
|
328
|
+
status.setAttribute("role", "status");
|
|
329
|
+
status.textContent = `Loading ${this.key}…`;
|
|
330
|
+
this.element.replaceChildren(status);
|
|
331
|
+
const pending = this.owner.registry.load(this.spec.type).then(async (create) => {
|
|
332
|
+
lifetime.signal.throwIfAborted();
|
|
333
|
+
const session = await create({ props: this.props.peek(), lifetime, scope: this.owner.scope, activity: this.#activity });
|
|
334
|
+
lifetime.defer(() => { try {
|
|
335
|
+
session.dispose?.();
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
session.element.remove();
|
|
339
|
+
} });
|
|
340
|
+
lifetime.signal.throwIfAborted();
|
|
341
|
+
if (generation !== this.#generation)
|
|
342
|
+
throw new Error("Stale child creation");
|
|
343
|
+
this.#session = session;
|
|
344
|
+
session.setActive?.(this.#activity.active.peek());
|
|
345
|
+
provideScope(session.element, session.scope ?? this.owner.scope);
|
|
346
|
+
if (this.type.apply) {
|
|
347
|
+
let initial = true;
|
|
348
|
+
lifetime.defer(effect(() => {
|
|
349
|
+
try {
|
|
350
|
+
const props = this.props.value;
|
|
351
|
+
checkContract(props, this.type.props, `definition.children.${this.key}.props`);
|
|
352
|
+
untracked(() => this.type.apply(session.element, props));
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (initial)
|
|
356
|
+
throw error;
|
|
357
|
+
queueMicrotask(() => this.#fail(error, generation, lifetime));
|
|
358
|
+
}
|
|
359
|
+
}));
|
|
360
|
+
initial = false;
|
|
361
|
+
}
|
|
362
|
+
this.element.replaceChildren(session.element);
|
|
363
|
+
this.element.removeAttribute("aria-busy");
|
|
364
|
+
this.#status.value = "ready";
|
|
365
|
+
return session.element;
|
|
366
|
+
}).catch((error) => {
|
|
367
|
+
this.#fail(error, generation, lifetime);
|
|
368
|
+
throw error;
|
|
369
|
+
}).finally(() => { if (generation === this.#generation)
|
|
370
|
+
this.#pending = undefined; });
|
|
371
|
+
this.#pending = pending;
|
|
372
|
+
return pending;
|
|
373
|
+
}
|
|
374
|
+
#fail(error, generation, lifetime) {
|
|
375
|
+
if (generation !== this.#generation || this.owner.lifetime.disposed)
|
|
376
|
+
return;
|
|
377
|
+
try {
|
|
378
|
+
lifetime.dispose();
|
|
379
|
+
}
|
|
380
|
+
catch (cleanup) {
|
|
381
|
+
error = new AggregateError([error, cleanup], "Child failed and cleanup reported an error");
|
|
382
|
+
}
|
|
383
|
+
this.#session = undefined;
|
|
384
|
+
this.#status.value = "error";
|
|
385
|
+
const failure = this.owner.report(error instanceof CompositeError ? error : new CompositeError(`definition.children.${this.key}`, error instanceof Error ? error.message : String(error)));
|
|
386
|
+
this.#error.value = failure;
|
|
387
|
+
this.element.removeAttribute("aria-busy");
|
|
388
|
+
const message = document.createElement("p");
|
|
389
|
+
message.setAttribute("role", "status");
|
|
390
|
+
message.textContent = `Could not load ${this.key}. ${failure.message}`;
|
|
391
|
+
const retry = document.createElement("button");
|
|
392
|
+
retry.type = "button";
|
|
393
|
+
retry.className = "aster-button";
|
|
394
|
+
retry.textContent = `Retry ${this.key}`;
|
|
395
|
+
retry.addEventListener("click", () => void this.ensure().catch(() => { }), { signal: this.owner.lifetime.signal });
|
|
396
|
+
this.element.replaceChildren(message, retry);
|
|
397
|
+
}
|
|
398
|
+
#reset() {
|
|
399
|
+
++this.#generation;
|
|
400
|
+
this.#lifetime?.dispose();
|
|
401
|
+
this.#lifetime = undefined;
|
|
402
|
+
this.#pending = undefined;
|
|
403
|
+
this.#session = undefined;
|
|
404
|
+
this.#status.value = "idle";
|
|
405
|
+
this.#error.value = undefined;
|
|
406
|
+
this.element.replaceChildren();
|
|
407
|
+
this.element.removeAttribute("aria-busy");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SessionActivity } from "@getaster/data";
|
|
2
|
+
import type { ReadonlySignal } from "@preact/signals-core";
|
|
3
|
+
import type { Lifetime, Publication } from "@getaster/data";
|
|
4
|
+
import { type CompositeContract, type CompositeModel, type CompositeModelType, type CompositeProperty, type CompositeValues } from "./registry.ts";
|
|
5
|
+
export interface CompositeType<T> extends CompositeProperty {
|
|
6
|
+
readonly validate: (value: unknown) => value is T;
|
|
7
|
+
}
|
|
8
|
+
export type CompositeTypes = Readonly<Record<string, CompositeType<unknown>>>;
|
|
9
|
+
type TypeValue<T> = T extends CompositeType<infer V> ? V : unknown;
|
|
10
|
+
export type CompositeContractValues<C extends CompositeContract> = {
|
|
11
|
+
readonly [K in keyof C as C[K] extends {
|
|
12
|
+
readonly optional: true;
|
|
13
|
+
} ? never : K]: TypeValue<C[K]>;
|
|
14
|
+
} & {
|
|
15
|
+
readonly [K in keyof C as C[K] extends {
|
|
16
|
+
readonly optional: true;
|
|
17
|
+
} ? K : never]?: Exclude<TypeValue<C[K]>, undefined>;
|
|
18
|
+
};
|
|
19
|
+
declare function type<T>(description: string, validate: (value: unknown) => value is T, schema?: Readonly<Record<string, unknown>>): CompositeType<T>;
|
|
20
|
+
/** Reusable runtime checks, inferred TS values and discoverable contract metadata. */
|
|
21
|
+
export declare const compositeTypes: Readonly<{
|
|
22
|
+
string: CompositeType<string>;
|
|
23
|
+
boolean: CompositeType<boolean>;
|
|
24
|
+
number: CompositeType<number>;
|
|
25
|
+
positiveInteger: CompositeType<number>;
|
|
26
|
+
custom: typeof type;
|
|
27
|
+
refine<const C extends CompositeType<unknown>>(inner: C, description: string, accepts: (value: TypeValue<C>) => boolean, schema: Readonly<Record<string, unknown>>): C;
|
|
28
|
+
instance<T>(ctor: abstract new (...args: never[]) => T): CompositeType<T>;
|
|
29
|
+
optional<const C extends CompositeType<unknown>>(inner: C): Omit<C, "validate"> & CompositeType<TypeValue<C> | undefined> & {
|
|
30
|
+
readonly optional: true;
|
|
31
|
+
};
|
|
32
|
+
array<const C extends CompositeType<unknown>>(inner: C): CompositeType<readonly TypeValue<C>[]> & {
|
|
33
|
+
readonly item: C;
|
|
34
|
+
};
|
|
35
|
+
signal<const C extends CompositeType<unknown>>(inner: C): CompositeType<ReadonlySignal<TypeValue<C>>> & {
|
|
36
|
+
readonly signalValue: C;
|
|
37
|
+
};
|
|
38
|
+
publication<const C extends CompositeTypes>(fields: C): CompositeType<Publication>;
|
|
39
|
+
object<const C extends CompositeTypes>(fields: C): CompositeType<CompositeContractValues<C>> & {
|
|
40
|
+
readonly fields: C;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
export interface TypedCompositeModel<I extends CompositeTypes, E extends CompositeTypes, C extends CompositeTypes = {}, P extends CompositeTypes = {}> extends CompositeModelType {
|
|
44
|
+
readonly inputs: I;
|
|
45
|
+
readonly exports: E;
|
|
46
|
+
readonly commands: C;
|
|
47
|
+
readonly publications: P;
|
|
48
|
+
/** Validate and narrow public exports without an application-side cast. */
|
|
49
|
+
read(values: CompositeValues): CompositeContractValues<E>;
|
|
50
|
+
}
|
|
51
|
+
/** One contract drives runtime validation and contextual typing of model code. */
|
|
52
|
+
export declare function defineCompositeModel<const I extends CompositeTypes, const E extends CompositeTypes, const C extends CompositeTypes = {}, const P extends CompositeTypes = {}>(spec: {
|
|
53
|
+
readonly inputs: I;
|
|
54
|
+
readonly exports: E;
|
|
55
|
+
readonly commands?: C;
|
|
56
|
+
readonly publications?: P;
|
|
57
|
+
readonly create: (inputs: CompositeContractValues<I>, lifetime: Lifetime, activity?: SessionActivity) => Omit<CompositeModel, "values"> & {
|
|
58
|
+
readonly values: CompositeContractValues<E>;
|
|
59
|
+
};
|
|
60
|
+
}): TypedCompositeModel<I, E, C, P>;
|
|
61
|
+
/** JSON-compatible metadata; identity and signal checks still require the runtime. */
|
|
62
|
+
export declare function describeCompositeContract(contract: CompositeContract): Readonly<Record<string, unknown>>;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command } from "@getaster/data";
|
|
2
|
+
import { checkContract, snapshotContract } from "./registry.js";
|
|
3
|
+
function type(description, validate, schema = {}) {
|
|
4
|
+
const metadata = structuredClone(schema);
|
|
5
|
+
function freeze(value) {
|
|
6
|
+
if (!value || typeof value !== "object")
|
|
7
|
+
return;
|
|
8
|
+
Object.values(value).forEach(freeze);
|
|
9
|
+
Object.freeze(value);
|
|
10
|
+
}
|
|
11
|
+
freeze(metadata);
|
|
12
|
+
return Object.freeze({ description, validate, schema: metadata });
|
|
13
|
+
}
|
|
14
|
+
/** Reusable runtime checks, inferred TS values and discoverable contract metadata. */
|
|
15
|
+
export const compositeTypes = Object.freeze({
|
|
16
|
+
string: type("string", (value) => typeof value === "string", { type: "string" }),
|
|
17
|
+
boolean: type("boolean", (value) => typeof value === "boolean", { type: "boolean" }),
|
|
18
|
+
number: type("finite number", (value) => typeof value === "number" && Number.isFinite(value), { type: "number" }),
|
|
19
|
+
positiveInteger: type("positive safe integer", (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0, { type: "integer", minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
|
|
20
|
+
custom: type,
|
|
21
|
+
refine(inner, description, accepts, schema) {
|
|
22
|
+
const validate = inner.validate;
|
|
23
|
+
return Object.freeze({ ...inner, ...type(description, (value) => validate(value) && accepts(value), { allOf: [inner.schema ?? {}, schema] }) });
|
|
24
|
+
},
|
|
25
|
+
instance(ctor) {
|
|
26
|
+
const name = Object.is(ctor, Command) ? "Command" : ctor.name;
|
|
27
|
+
const bases = [];
|
|
28
|
+
for (let base = Object.getPrototypeOf(ctor); base && base !== Function.prototype; base = Object.getPrototypeOf(base)) {
|
|
29
|
+
if (base.name)
|
|
30
|
+
bases.push(Object.is(base, Command) ? "Command" : base.name);
|
|
31
|
+
}
|
|
32
|
+
return type(name, (value) => value instanceof ctor, { asterType: name, asterBases: bases });
|
|
33
|
+
},
|
|
34
|
+
optional(inner) {
|
|
35
|
+
return Object.freeze({ ...inner, schema: Object.freeze({ anyOf: Object.freeze([inner.schema ?? {}, Object.freeze({ asterType: "undefined" })]) }), optional: true, validate: (value) => value === undefined || inner.validate(value) });
|
|
36
|
+
},
|
|
37
|
+
array(inner) {
|
|
38
|
+
return Object.freeze({ ...type(`array of ${inner.description}`, (value) => Array.isArray(value) && value.every(item => inner.validate(item)), { type: "array", items: inner.schema ?? {} }), item: inner });
|
|
39
|
+
},
|
|
40
|
+
signal(inner) {
|
|
41
|
+
return Object.freeze({ ...type(`signal of ${inner.description}`, (value) => {
|
|
42
|
+
if (!value || typeof value !== "object" || !("peek" in value) || typeof value.peek !== "function" || !("subscribe" in value) || typeof value.subscribe !== "function")
|
|
43
|
+
return false;
|
|
44
|
+
return inner.validate(value.peek());
|
|
45
|
+
}, { asterType: "signal", value: inner.schema ?? {} }), signalValue: inner });
|
|
46
|
+
},
|
|
47
|
+
publication(fields) {
|
|
48
|
+
const members = snapshotContract(fields);
|
|
49
|
+
return type("publication", (value) => {
|
|
50
|
+
if (!value || typeof value !== "object" || !("values" in value) || !value.values || typeof value.values !== "object")
|
|
51
|
+
return false;
|
|
52
|
+
const values = value.values;
|
|
53
|
+
return Object.entries(members).every(([key, spec]) => compositeTypes.signal(spec).validate(values[key]));
|
|
54
|
+
}, { asterType: "publication", properties: Object.fromEntries(Object.entries(members).map(([key, spec]) => [key, spec.schema ?? {}])) });
|
|
55
|
+
},
|
|
56
|
+
object(fields) {
|
|
57
|
+
const members = Object.freeze(Object.fromEntries(Object.entries(fields).map(([key, spec]) => [key, Object.freeze({ ...spec })])));
|
|
58
|
+
const required = Object.keys(members).filter(key => !members[key].optional);
|
|
59
|
+
return Object.freeze({ ...type("object", (value) => {
|
|
60
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![null, Object.prototype].includes(Object.getPrototypeOf(value)))
|
|
61
|
+
return false;
|
|
62
|
+
const values = value;
|
|
63
|
+
return Object.keys(values).every(key => Object.hasOwn(members, key)) && Object.entries(members).every(([key, spec]) => spec.validate(values[key]));
|
|
64
|
+
}, { type: "object", properties: Object.fromEntries(Object.entries(members).map(([key, spec]) => [key, spec.schema ?? {}])), required, additionalProperties: false }), fields: members });
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
/** One contract drives runtime validation and contextual typing of model code. */
|
|
68
|
+
export function defineCompositeModel(spec) {
|
|
69
|
+
const inputsContract = snapshotContract(spec.inputs);
|
|
70
|
+
const exports = snapshotContract(spec.exports);
|
|
71
|
+
const commands = snapshotContract(spec.commands ?? {});
|
|
72
|
+
const publications = snapshotContract(spec.publications ?? {});
|
|
73
|
+
const create = spec.create;
|
|
74
|
+
return {
|
|
75
|
+
inputs: inputsContract,
|
|
76
|
+
exports,
|
|
77
|
+
commands,
|
|
78
|
+
publications,
|
|
79
|
+
create: (inputs, lifetime, activity) => {
|
|
80
|
+
checkContract(inputs, inputsContract, "model.inputs");
|
|
81
|
+
const model = create(inputs, lifetime, activity);
|
|
82
|
+
if (spec.commands)
|
|
83
|
+
checkContract(model.commands ?? {}, commands, "model.commands");
|
|
84
|
+
if (spec.publications)
|
|
85
|
+
checkContract(model.publications ?? {}, publications, "model.publications");
|
|
86
|
+
return model;
|
|
87
|
+
},
|
|
88
|
+
read: values => {
|
|
89
|
+
checkContract(values, exports, "model.exports");
|
|
90
|
+
return values;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** JSON-compatible metadata; identity and signal checks still require the runtime. */
|
|
95
|
+
export function describeCompositeContract(contract) {
|
|
96
|
+
return Object.fromEntries(Object.entries(contract).map(([key, spec]) => [key, { description: spec.description ?? "Custom validator", optional: spec.optional ?? false, schema: spec.schema ?? {} }]));
|
|
97
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Condition } from "@getaster/data";
|
|
2
|
+
export type CompositeValue = null | boolean | number | string | readonly CompositeValue[] | {
|
|
3
|
+
readonly [key: string]: CompositeValue;
|
|
4
|
+
};
|
|
5
|
+
export interface CompositeChild {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly props?: Readonly<Record<string, CompositeValue>>;
|
|
8
|
+
readonly create?: "eager" | "on-demand";
|
|
9
|
+
readonly retain?: boolean;
|
|
10
|
+
/** Static suppression; does not construct or load this child. */
|
|
11
|
+
readonly enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface CompositeChildCustomization {
|
|
14
|
+
readonly props?: readonly string[];
|
|
15
|
+
readonly replace?: readonly string[];
|
|
16
|
+
readonly optional?: boolean;
|
|
17
|
+
readonly lifecycle?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CompositeCustomization {
|
|
20
|
+
readonly children?: Readonly<Record<string, CompositeChildCustomization>>;
|
|
21
|
+
readonly modelInputs?: readonly string[];
|
|
22
|
+
readonly layout?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type CompositeLayout = {
|
|
25
|
+
readonly child: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: "stack";
|
|
28
|
+
readonly items: readonly CompositeLayout[];
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: "deck";
|
|
31
|
+
readonly active: CompositeValue;
|
|
32
|
+
readonly items: Readonly<Record<string, CompositeLayout>>;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "outlet";
|
|
35
|
+
readonly active: CompositeValue;
|
|
36
|
+
readonly content: CompositeLayout;
|
|
37
|
+
};
|
|
38
|
+
export interface CompositeDefinition {
|
|
39
|
+
readonly version: 1;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly model: {
|
|
42
|
+
readonly type: string;
|
|
43
|
+
readonly inputs?: Readonly<Record<string, CompositeValue>>;
|
|
44
|
+
};
|
|
45
|
+
readonly publish?: Readonly<Record<string, {
|
|
46
|
+
readonly ref: string;
|
|
47
|
+
}>>;
|
|
48
|
+
readonly commands?: Readonly<Record<string, {
|
|
49
|
+
readonly use: string;
|
|
50
|
+
readonly enabledWhen?: Condition;
|
|
51
|
+
readonly reason?: string;
|
|
52
|
+
}>>;
|
|
53
|
+
readonly children: Readonly<Record<string, CompositeChild>>;
|
|
54
|
+
readonly layout: CompositeLayout;
|
|
55
|
+
readonly customize?: CompositeCustomization;
|
|
56
|
+
}
|
|
57
|
+
export type CompositeOverrides<D extends CompositeDefinition = CompositeDefinition> = {
|
|
58
|
+
readonly name?: string;
|
|
59
|
+
readonly modelInputs?: Readonly<Partial<Record<keyof NonNullable<D["model"]["inputs"]>, CompositeValue>>>;
|
|
60
|
+
readonly children?: {
|
|
61
|
+
readonly [K in keyof D["children"]]?: {
|
|
62
|
+
readonly type?: string;
|
|
63
|
+
readonly props?: Readonly<Partial<Record<keyof NonNullable<D["children"][K]["props"]>, CompositeValue>>>;
|
|
64
|
+
readonly enabled?: boolean;
|
|
65
|
+
readonly create?: "eager" | "on-demand";
|
|
66
|
+
readonly retain?: boolean;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
readonly layout?: CompositeLayout;
|
|
70
|
+
};
|
|
71
|
+
export type CompositeVariant<D extends CompositeDefinition> = Omit<CompositeDefinition, "children"> & {
|
|
72
|
+
readonly children: {
|
|
73
|
+
readonly [K in keyof D["children"]]: CompositeChild;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export interface CompositeLocation {
|
|
77
|
+
readonly file: string;
|
|
78
|
+
readonly line: number;
|
|
79
|
+
readonly column: number;
|
|
80
|
+
}
|
|
81
|
+
export type CompositeSources = Readonly<Record<string, CompositeLocation>>;
|
|
82
|
+
export declare class CompositeError extends Error {
|
|
83
|
+
readonly path: string;
|
|
84
|
+
constructor(path: string, message: string);
|
|
85
|
+
}
|
|
86
|
+
export declare function located(error: unknown, sources?: CompositeSources): Error;
|
|
87
|
+
export declare function record(value: unknown, path: string): Record<string, unknown>;
|
|
88
|
+
export declare function keys(value: Record<string, unknown>, allowed: readonly string[], path: string): void;
|
|
89
|
+
export declare function name(value: unknown, path: string): asserts value is string;
|
|
90
|
+
/** Validate the JSON-compatible subset shared by YAML and JavaScript definitions. */
|
|
91
|
+
export declare function defineComposite<const D extends CompositeDefinition>(value: D): D;
|
|
92
|
+
export declare function defineComposite(value: unknown): CompositeDefinition;
|
|
93
|
+
/** Immutable public customization: base → derived definition → instance overrides. */
|
|
94
|
+
export declare function deriveComposite<D extends CompositeDefinition>(base: D, overrides: CompositeOverrides<NoInfer<D>>): CompositeVariant<D>;
|