@getaster/elements 0.0.0-stage → 0.1.0-preview.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.
Files changed (163) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +6 -2
  4. package/bin/aster-theme-check.js +317 -0
  5. package/dist/base/drag.d.ts +45 -0
  6. package/dist/base/drag.js +139 -0
  7. package/dist/base/element.d.ts +51 -0
  8. package/dist/base/element.js +133 -0
  9. package/dist/breadcrumbs/breadcrumbs.d.ts +23 -0
  10. package/dist/breadcrumbs/breadcrumbs.js +51 -0
  11. package/dist/button/button.d.ts +37 -0
  12. package/dist/button/button.js +108 -0
  13. package/dist/caret/caret-group.d.ts +14 -0
  14. package/dist/caret/caret-group.js +71 -0
  15. package/dist/carousel/carousel.d.ts +28 -0
  16. package/dist/carousel/carousel.js +134 -0
  17. package/dist/component/component.d.ts +73 -0
  18. package/dist/component/component.js +190 -0
  19. package/dist/component/echarts.d.ts +46 -0
  20. package/dist/component/echarts.js +95 -0
  21. package/dist/composition/authoring.d.ts +157 -0
  22. package/dist/composition/authoring.js +291 -0
  23. package/dist/composition/builtins.d.ts +1130 -0
  24. package/dist/composition/builtins.js +318 -0
  25. package/dist/composition/command-bar.d.ts +18 -0
  26. package/dist/composition/command-bar.js +34 -0
  27. package/dist/composition/commands.d.ts +14 -0
  28. package/dist/composition/commands.js +59 -0
  29. package/dist/composition/composite.d.ts +48 -0
  30. package/dist/composition/composite.js +409 -0
  31. package/dist/composition/contracts.d.ts +63 -0
  32. package/dist/composition/contracts.js +97 -0
  33. package/dist/composition/definition.d.ts +94 -0
  34. package/dist/composition/definition.js +291 -0
  35. package/dist/composition/element.d.ts +35 -0
  36. package/dist/composition/element.js +51 -0
  37. package/dist/composition/registry.d.ts +83 -0
  38. package/dist/composition/registry.js +99 -0
  39. package/dist/composition/schema.d.ts +9 -0
  40. package/dist/composition/schema.js +139 -0
  41. package/dist/composition/sessions.d.ts +15 -0
  42. package/dist/composition/sessions.js +61 -0
  43. package/dist/dashboard/dashboard.d.ts +26 -0
  44. package/dist/dashboard/dashboard.js +296 -0
  45. package/dist/dashboard/layout.d.ts +19 -0
  46. package/dist/dashboard/layout.js +48 -0
  47. package/dist/dashboard/widget.d.ts +42 -0
  48. package/dist/dashboard/widget.js +165 -0
  49. package/dist/date/calendar.d.ts +34 -0
  50. package/dist/date/calendar.js +137 -0
  51. package/dist/date/date-field.d.ts +18 -0
  52. package/dist/date/date-field.js +81 -0
  53. package/dist/date/dates.d.ts +22 -0
  54. package/dist/date/dates.js +80 -0
  55. package/dist/dialog/dialog.d.ts +23 -0
  56. package/dist/dialog/dialog.js +70 -0
  57. package/dist/field/bound-field.d.ts +55 -0
  58. package/dist/field/bound-field.js +139 -0
  59. package/dist/field/check.d.ts +11 -0
  60. package/dist/field/check.js +26 -0
  61. package/dist/field/composite-field.d.ts +35 -0
  62. package/dist/field/composite-field.js +88 -0
  63. package/dist/field/field.d.ts +29 -0
  64. package/dist/field/field.js +75 -0
  65. package/dist/field/input.d.ts +15 -0
  66. package/dist/field/input.js +18 -0
  67. package/dist/field/number-field.d.ts +33 -0
  68. package/dist/field/number-field.js +179 -0
  69. package/dist/field/numeric.d.ts +4 -0
  70. package/dist/field/numeric.js +57 -0
  71. package/dist/field/values.d.ts +14 -0
  72. package/dist/field/values.js +60 -0
  73. package/dist/filter/filter-builder.d.ts +18 -0
  74. package/dist/filter/filter-builder.js +305 -0
  75. package/dist/form/controls.d.ts +20 -0
  76. package/dist/form/controls.js +34 -0
  77. package/dist/form/form.d.ts +46 -0
  78. package/dist/form/form.js +85 -0
  79. package/dist/format.d.ts +10 -0
  80. package/dist/format.js +57 -0
  81. package/dist/grid/filters.d.ts +16 -0
  82. package/dist/grid/filters.js +66 -0
  83. package/dist/grid/grid.d.ts +209 -0
  84. package/dist/grid/grid.js +2587 -0
  85. package/dist/grid/rows.d.ts +32 -0
  86. package/dist/grid/rows.js +89 -0
  87. package/dist/grid/view.d.ts +69 -0
  88. package/dist/grid/view.js +172 -0
  89. package/dist/index.d.ts +63 -0
  90. package/dist/index.js +61 -0
  91. package/dist/layout/stack.d.ts +112 -0
  92. package/dist/layout/stack.js +21 -0
  93. package/dist/list-edit/list-edit.d.ts +42 -0
  94. package/dist/list-edit/list-edit.js +220 -0
  95. package/dist/listbox/listbox.d.ts +45 -0
  96. package/dist/listbox/listbox.js +187 -0
  97. package/dist/menu/menu.d.ts +55 -0
  98. package/dist/menu/menu.js +286 -0
  99. package/dist/motion.d.ts +8 -0
  100. package/dist/motion.js +23 -0
  101. package/dist/overlay/anchor.d.ts +18 -0
  102. package/dist/overlay/anchor.js +41 -0
  103. package/dist/picker/options.d.ts +59 -0
  104. package/dist/picker/options.js +97 -0
  105. package/dist/picker/picker.d.ts +46 -0
  106. package/dist/picker/picker.js +492 -0
  107. package/dist/picker/tree-choices.d.ts +38 -0
  108. package/dist/picker/tree-choices.js +72 -0
  109. package/dist/picker/tree-picker.d.ts +29 -0
  110. package/dist/picker/tree-picker.js +282 -0
  111. package/dist/progress/policy.d.ts +5 -0
  112. package/dist/progress/policy.js +28 -0
  113. package/dist/progress/status.d.ts +20 -0
  114. package/dist/progress/status.js +38 -0
  115. package/dist/rule/operators.d.ts +8 -0
  116. package/dist/rule/operators.js +9 -0
  117. package/dist/rule/rule-editor.d.ts +57 -0
  118. package/dist/rule/rule-editor.js +290 -0
  119. package/dist/section/accordion.d.ts +24 -0
  120. package/dist/section/accordion.js +90 -0
  121. package/dist/section/section.d.ts +30 -0
  122. package/dist/section/section.js +99 -0
  123. package/dist/select/choice-element.d.ts +20 -0
  124. package/dist/select/choice-element.js +55 -0
  125. package/dist/select/choice.d.ts +34 -0
  126. package/dist/select/choice.js +122 -0
  127. package/dist/select/many.d.ts +13 -0
  128. package/dist/select/many.js +20 -0
  129. package/dist/select/select.d.ts +16 -0
  130. package/dist/select/select.js +40 -0
  131. package/dist/sidebar/sidebar.d.ts +47 -0
  132. package/dist/sidebar/sidebar.js +105 -0
  133. package/dist/slicer/slicer.d.ts +26 -0
  134. package/dist/slicer/slicer.js +140 -0
  135. package/dist/split/split.d.ts +41 -0
  136. package/dist/split/split.js +237 -0
  137. package/dist/styles/contract.d.ts +36 -0
  138. package/dist/styles/contract.js +39 -0
  139. package/dist/switch/switch.d.ts +22 -0
  140. package/dist/switch/switch.js +69 -0
  141. package/dist/tabs/tabs.d.ts +30 -0
  142. package/dist/tabs/tabs.js +202 -0
  143. package/dist/tile-grid/scroll.d.ts +9 -0
  144. package/dist/tile-grid/scroll.js +21 -0
  145. package/dist/tile-grid/tile-grid.d.ts +64 -0
  146. package/dist/tile-grid/tile-grid.js +734 -0
  147. package/dist/toast/toast.d.ts +25 -0
  148. package/dist/toast/toast.js +92 -0
  149. package/dist/tree/navigator.d.ts +41 -0
  150. package/dist/tree/navigator.js +228 -0
  151. package/dist/window/chrome.d.ts +3 -0
  152. package/dist/window/chrome.js +7 -0
  153. package/dist/window/window.d.ts +46 -0
  154. package/dist/window/window.js +347 -0
  155. package/dist/window/workspace.d.ts +29 -0
  156. package/dist/window/workspace.js +137 -0
  157. package/package.json +45 -4
  158. package/styles/aster-ui.css +1147 -0
  159. package/styles/base.css +88 -0
  160. package/styles/contract.ts +41 -0
  161. package/styles/elements.css +1058 -0
  162. package/styles/harbour.css +126 -0
  163. package/styles/lamplight.css +118 -0
@@ -0,0 +1,318 @@
1
+ import { Collection, Command, DataContext, EditModel, FilterDraft, ListEdit, Selection, SharedFilters, Tree } from "@getaster/data";
2
+ import { nothing } from "lit-html";
3
+ import { compositeTypes as t } from "./contracts.js";
4
+ import { compositeElement } from "./registry.js";
5
+ import { PickerOptions } from "../picker/options.js";
6
+ import { TreeChoices } from "../picker/tree-choices.js";
7
+ import { ProgressStatus } from "../progress/status.js";
8
+ import { Sidebar } from "../sidebar/sidebar.js";
9
+ import { Button } from "../button/button.js";
10
+ import { Field } from "../field/field.js";
11
+ import { NumberField } from "../field/number-field.js";
12
+ import { canonicalNumeric, compareNumeric } from "../field/numeric.js";
13
+ import { CompositeField } from "../field/composite-field.js";
14
+ import { Stack, HorizontalLayout, VerticalLayout } from "../layout/stack.js";
15
+ import { Breadcrumbs } from "../breadcrumbs/breadcrumbs.js";
16
+ import { Accordion } from "../section/accordion.js";
17
+ import { Check } from "../field/check.js";
18
+ import { Select } from "../select/select.js";
19
+ import { Picker } from "../picker/picker.js";
20
+ import { TreePicker } from "../picker/tree-picker.js";
21
+ import { DateField } from "../date/date-field.js";
22
+ import { Choice } from "../select/choice-element.js";
23
+ import { Switch } from "../switch/switch.js";
24
+ import { Grid } from "../grid/grid.js";
25
+ import { TileGrid } from "../tile-grid/tile-grid.js";
26
+ import { Listbox } from "../listbox/listbox.js";
27
+ import { Form } from "../form/form.js";
28
+ import { ListEditor } from "../list-edit/list-edit.js";
29
+ import { RuleEditor } from "../rule/rule-editor.js";
30
+ import { FilterBuilder } from "../filter/filter-builder.js";
31
+ import { TreeNavigator } from "../tree/navigator.js";
32
+ import { Tabs } from "../tabs/tabs.js";
33
+ import { Section } from "../section/section.js";
34
+ import { Split } from "../split/split.js";
35
+ import { Carousel } from "../carousel/carousel.js";
36
+ import { Slicer } from "../slicer/slicer.js";
37
+ import { ComponentHost } from "../component/component.js";
38
+ import { Window } from "../window/window.js";
39
+ import { Dialog } from "../dialog/dialog.js";
40
+ import { Dashboard } from "../dashboard/dashboard.js";
41
+ import { CommandBar } from "../composition/command-bar.js";
42
+ const object = (value) => !!value && typeof value === "object" && [Object.prototype, null].includes(Object.getPrototypeOf(value));
43
+ const fn = (value) => typeof value === "function";
44
+ const view = (value) => value === nothing || object(value) && "_$litType$" in value;
45
+ const value = (v) => v === null || ["string", "boolean"].includes(typeof v) || typeof v === "number" && Number.isFinite(v) || Array.isArray(v) && v.every(value);
46
+ const options = t.array(t.object({ value: t.custom("OData value", (v) => value(v)), label: t.string }));
47
+ const tabItems = t.array(t.object({ value: t.string, label: t.string, closable: t.optional(t.boolean), disabled: t.optional(t.boolean) }));
48
+ const literals = (...values) => t.custom("one of " + values.join(", "), (v) => values.some(item => item === v), { enum: values });
49
+ function checked(description, test) {
50
+ return t.custom(description, (value) => test(value));
51
+ }
52
+ function bindable(inner) {
53
+ const signal = t.signal(inner);
54
+ return t.custom(inner.description + " or signal", (v) => inner.validate(v) || signal.validate(v), { anyOf: [inner.schema ?? {}, signal.schema ?? {}] });
55
+ }
56
+ // Omission preserves the element's own default; explicit undefined must still validate.
57
+ function element() {
58
+ return (tag, props) => {
59
+ const defaults = Object.fromEntries(Object.entries(props).map(([key, spec]) => [key, Object.freeze({ ...spec, optional: true })]));
60
+ return Object.freeze(compositeElement(tag, Object.freeze(defaults)));
61
+ };
62
+ }
63
+ const optionalNumber = (v) => v === undefined || t.number.validate(v);
64
+ const optionalBoolean = (v) => v === undefined || typeof v === "boolean";
65
+ const numeric = (v) => {
66
+ if (typeof v !== "string" && typeof v !== "number")
67
+ return false;
68
+ try {
69
+ canonicalNumeric(v);
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ };
76
+ const sourceOptions = (v) => v === undefined || v instanceof Collection || v instanceof PickerOptions;
77
+ const formItem = checked("field name, configured field or derived value", v => typeof v === "string" || object(v) && optionalNumber(v.span) && (typeof v.field === "string" && sourceOptions(v.options)
78
+ || typeof v.title === "string" && t.signal(t.string).validate(v.value)));
79
+ const sections = t.array(t.object({ title: t.string, columns: t.optional(t.positiveInteger), open: t.optional(t.boolean), items: t.array(formItem) }));
80
+ const gridView = checked("Grid view", v => object(v)
81
+ && Array.isArray(v.columns) && v.columns.every(c => object(c) && typeof c.field === "string" && optionalNumber(c.width) && optionalBoolean(c.hidden) && (c.sortField === undefined || typeof c.sortField === "string" && c.sortField.trim().length > 0))
82
+ && Array.isArray(v.sort) && v.sort.every(k => object(k) && typeof k.field === "string" && optionalBoolean(k.descending))
83
+ && object(v.filters) && Object.values(v.filters).every(c => object(c) && typeof c.op === "string")
84
+ && typeof v.frozen === "number" && Number.isSafeInteger(v.frozen) && v.frozen >= 0
85
+ && (v.groupBy === undefined || typeof v.groupBy === "string" || Array.isArray(v.groupBy) && v.groupBy.every(k => typeof k === "string")));
86
+ const milliseconds = t.custom("nonnegative milliseconds", (value) => Number.isSafeInteger(value) && typeof value === "number" && value >= 0 && value <= 2_147_483_647, { type: "integer", minimum: 0, maximum: 2_147_483_647 });
87
+ const progressTiming = t.object({ delayMs: t.optional(milliseconds), minVisibleMs: t.optional(milliseconds) });
88
+ const bound = {
89
+ model: t.optional(t.instance(EditModel)), field: t.string, label: t.string,
90
+ labelled: t.boolean, sharedReasons: t.instance((Map)),
91
+ };
92
+ /** Reusable public control contracts. Select entries explicitly for an application's allowlist. */
93
+ export const asterComponents = Object.freeze({
94
+ Button: element()("aster-button", {
95
+ label: t.string,
96
+ busyLabel: t.optional(t.string),
97
+ confirmLabel: t.optional(t.string),
98
+ busy: bindable(t.boolean),
99
+ variant: literals("secondary", "primary"),
100
+ type: literals("button", "submit"),
101
+ icon: checked("Button.icon", view),
102
+ reason: t.optional(t.string),
103
+ }),
104
+ Field: element()("aster-field", {
105
+ ...bound,
106
+ value: t.string,
107
+ placeholder: t.string,
108
+ readOnly: t.boolean,
109
+ keyboard: t.string,
110
+ trailing: checked("Field.trailing", view),
111
+ }),
112
+ NumberField: element()("aster-number-field", {
113
+ ...bound, value: checked("numeric value", v => v === null || typeof v === "string" || typeof v === "number" && Number.isFinite(v)),
114
+ numericType: literals("decimal", "integer", "float"),
115
+ min: t.optional(t.custom("numeric bound", numeric)),
116
+ max: t.optional(t.custom("numeric bound", numeric)),
117
+ step: t.optional(t.custom("positive numeric step", (v) => numeric(v) && compareNumeric(v, 0) > 0)),
118
+ stepButtons: t.boolean, prefixText: t.string, suffixText: t.string, placeholder: t.string, readOnly: t.boolean,
119
+ format: checked("numeric display formatter", v => v === undefined || fn(v)),
120
+ }),
121
+ CompositeField: element()("aster-composite-field", {
122
+ model: t.optional(t.instance(EditModel)), fields: t.array(t.string), label: t.string, help: t.string, error: t.string,
123
+ disabled: t.boolean, readOnly: t.boolean, content: checked("CompositeField.content", view),
124
+ value: t.custom("application value", (_v) => true),
125
+ renderValue: checked("composite value renderer", v => v === undefined || fn(v)),
126
+ }),
127
+ Stack: Object.freeze(compositeElement(Stack)),
128
+ HorizontalLayout: Object.freeze(compositeElement(HorizontalLayout)),
129
+ VerticalLayout: Object.freeze(compositeElement(VerticalLayout)),
130
+ Breadcrumbs: Object.freeze(compositeElement(Breadcrumbs)),
131
+ Accordion: element()("aster-accordion", {
132
+ items: checked("accordion items", v => Array.isArray(v) && new Set(v.map(i => i?.id)).size === v.length && v.every(i => object(i) && typeof i.id === "string" && i.id.length > 0 && typeof i.heading === "string" && !(i.body !== undefined && i.create !== undefined) && optionalBoolean(i.disabled) && (i.body === undefined || view(i.body)) && (i.summary === undefined || view(i.summary)) && (i.actions === undefined || view(i.actions)) && (i.create === undefined || fn(i.create)))),
133
+ expanded: t.array(t.string), multiple: t.boolean, headingLevel: literals(2, 3, 4),
134
+ }),
135
+ Check: element()("aster-check", {
136
+ ...bound,
137
+ }),
138
+ Select: element()("aster-select", {
139
+ ...bound,
140
+ choices: t.optional(options),
141
+ }),
142
+ Picker: element()("aster-picker", {
143
+ ...bound,
144
+ options: checked("Picker.options", v => v === undefined || v instanceof Collection || v instanceof PickerOptions),
145
+ display: t.string,
146
+ valueField: t.string,
147
+ multiple: t.boolean,
148
+ values: checked("Picker.values", v => Array.isArray(v) && v.every(value)),
149
+ placeholder: t.string,
150
+ }),
151
+ TreePicker: element()("aster-tree-picker", {
152
+ ...bound,
153
+ options: t.optional(t.instance(TreeChoices)),
154
+ placeholder: t.string,
155
+ }),
156
+ DateField: element()("aster-date", {
157
+ ...bound,
158
+ }),
159
+ Choice: element()("aster-choice", {
160
+ options: options,
161
+ value: checked("Choice.value", v => v === undefined || value(v)),
162
+ label: t.string,
163
+ placeholder: t.string,
164
+ }),
165
+ Switch: element()("aster-switch", {
166
+ binding: checked("Switch.binding", v => v === undefined || object(v) && v.ctx instanceof DataContext && typeof v.set === "string" && typeof v.key === "string" && typeof v.field === "string"),
167
+ label: t.string,
168
+ }),
169
+ Grid: element()("aster-grid", {
170
+ progressPolicy: progressTiming,
171
+ ctx: t.optional(t.instance(DataContext)),
172
+ set: t.string,
173
+ base: checked("Grid.base", v => v === undefined || object(v) && typeof v.op === "string"),
174
+ view: t.optional(gridView),
175
+ label: t.string,
176
+ publish: t.string,
177
+ persist: t.string,
178
+ renderers: checked("Grid.renderers", v => object(v) && Object.values(v).every(fn)),
179
+ formatters: checked("Grid.formatters", v => object(v) && Object.values(v).every(fn)),
180
+ summaries: checked("Grid.summaries", v => Array.isArray(v) && v.every(s => object(s) && (s.label === undefined || typeof s.label === "string") && (s.method === "count" || ["sum", "min", "max", "average", "countdistinct"].includes(String(s.method)) && typeof s.field === "string"))),
181
+ lookups: checked("Grid.lookups", v => object(v) && Object.values(v).every(l => object(l) && typeof l.set === "string" && typeof l.display === "string")),
182
+ rowMenu: checked("Grid.rowMenu", fn),
183
+ tree: checked("Grid.tree", v => v === undefined || object(v) && typeof v.parentField === "string" && optionalBoolean(v.draggable) && (v.filterMode === undefined || ["keepAncestors", "strict"].includes(String(v.filterMode)))),
184
+ rowControls: checked("Grid.rowControls", v => v === undefined || fn(v)),
185
+ expansion: checked("Grid.expansion", v => v === undefined || object(v) && fn(v.content) && optionalBoolean(v.single) && optionalNumber(v.limit) && (v.canExpand === undefined || fn(v.canExpand))),
186
+ editable: t.boolean,
187
+ rules: checked("Grid.rules", object),
188
+ empty: checked("Grid.empty", view),
189
+ collection: t.optional(t.instance(Collection)),
190
+ selection: t.optional(t.instance(Selection)),
191
+ selectable: t.boolean,
192
+ }),
193
+ TileGrid: element()("aster-tile-grid", {
194
+ collection: t.optional(t.instance(Collection)),
195
+ selection: t.optional(t.instance(Selection)),
196
+ selectable: t.boolean,
197
+ label: t.string,
198
+ publish: t.string,
199
+ presentTile: checked("TileGrid.presentTile", v => v === undefined || fn(v)),
200
+ renderTile: checked("TileGrid.renderTile", v => v === undefined || fn(v)),
201
+ actions: checked("TileGrid.actions", v => v === undefined || fn(v)),
202
+ empty: checked("TileGrid.empty", view),
203
+ layout: literals("uniform", "content"),
204
+ virtualized: t.boolean,
205
+ }),
206
+ Listbox: element()("aster-listbox", {
207
+ collection: t.optional(t.instance(Collection)),
208
+ selection: t.optional(t.instance(Selection)),
209
+ publish: t.string,
210
+ renderRow: checked("Listbox.renderRow", fn),
211
+ empty: checked("Listbox.empty", view),
212
+ label: t.string,
213
+ }),
214
+ Form: element()("aster-form", {
215
+ model: t.optional(t.instance(EditModel)),
216
+ sections,
217
+ headingLevel: literals(2, 3, 4),
218
+ }),
219
+ ListEditor: element()("aster-list-editor", {
220
+ list: t.optional(t.instance(ListEdit)),
221
+ columns: checked("ListEditor.columns", v => Array.isArray(v) && v.every(c => object(c) && typeof c.field === "string")),
222
+ label: t.string,
223
+ addLabel: t.string,
224
+ readOnlyReason: t.optional(t.string),
225
+ empty: checked("ListEditor.empty", view),
226
+ }),
227
+ RuleEditor: element()("aster-rule-editor", {
228
+ list: t.optional(t.instance(ListEdit)),
229
+ category: t.string,
230
+ label: t.string,
231
+ messages: t.boolean,
232
+ fields: checked("RuleEditor.fields", v => v === undefined || v instanceof PickerOptions || v instanceof TreeChoices),
233
+ describe: checked("RuleEditor.describe", v => v === undefined || fn(v)),
234
+ readOnlyReason: t.optional(t.string),
235
+ empty: t.string,
236
+ }),
237
+ FilterBuilder: element()("aster-filter-builder", {
238
+ model: t.optional(t.instance(FilterDraft)),
239
+ label: t.string,
240
+ readOnlyReason: t.optional(t.string),
241
+ }),
242
+ TreeNavigator: element()("aster-tree-navigator", {
243
+ tree: t.optional(t.instance(Tree)),
244
+ display: t.string,
245
+ label: t.string,
246
+ choosable: checked("TreeNavigator.choosable", v => v === undefined || fn(v)),
247
+ headingLevel: literals(1, 2, 3, 4, 5, 6),
248
+ chooseLabel: t.string,
249
+ }),
250
+ Tabs: element()("aster-tabs", {
251
+ renderPanel: checked("Tabs.renderPanel", v => v === undefined || fn(v)),
252
+ activation: literals("automatic", "manual"),
253
+ items: bindable(tabItems),
254
+ value: bindable(t.optional(t.string)),
255
+ label: t.string,
256
+ controls: t.optional(t.string),
257
+ }),
258
+ Section: element()("aster-section", {
259
+ actions: checked("Section.actions", view),
260
+ disabled: t.boolean,
261
+ heading: t.string,
262
+ open: t.boolean,
263
+ body: checked("Section.body", view),
264
+ level: literals(2, 3, 4),
265
+ summary: checked("Section.summary", view),
266
+ }),
267
+ Split: element()("aster-split", {
268
+ orientation: literals("horizontal", "vertical"), size: t.number, collapsible: t.boolean, collapsed: t.boolean, responsive: t.boolean,
269
+ start: checked("Split.start", view),
270
+ end: checked("Split.end", view),
271
+ width: t.number,
272
+ min: t.number,
273
+ max: t.number,
274
+ label: t.string,
275
+ persist: t.string,
276
+ }),
277
+ Carousel: element()("aster-carousel", {
278
+ slides: checked("Carousel.slides", v => Array.isArray(v) && new Set(v.filter(s => s?.id !== undefined).map(s => s.id)).size === v.filter(s => s?.id !== undefined).length && v.every(s => object(s) && typeof s.label === "string" && (s.id === undefined || typeof s.id === "string") && view(s.view))),
279
+ label: t.string,
280
+ }),
281
+ Slicer: element()("aster-slicer", {
282
+ filters: t.optional(t.instance(SharedFilters)),
283
+ label: t.string,
284
+ empty: t.string,
285
+ }),
286
+ ComponentHost: element()("aster-component", {
287
+ progressPolicy: progressTiming,
288
+ adapter: checked("ComponentHost.adapter", v => v === undefined || !!v && typeof v === "object" && "mount" in v && fn(v.mount)),
289
+ alternative: checked("ComponentHost.alternative", v => v === undefined || fn(v)),
290
+ label: t.string,
291
+ locale: t.optional(t.string),
292
+ }),
293
+ Window: element()("aster-window", {
294
+ heading: t.string,
295
+ content: checked("Window.content", view),
296
+ managed: t.boolean,
297
+ preserveContent: t.boolean,
298
+ beforeClose: checked("Window.beforeClose", v => v === undefined || fn(v)),
299
+ }),
300
+ Dialog: element()("aster-dialog", {
301
+ content: checked("Dialog.content", view),
302
+ labelledBy: t.optional(t.string),
303
+ }),
304
+ Dashboard: element()("aster-dashboard", {
305
+ label: t.string,
306
+ }),
307
+ CommandBar: element()("aster-command-bar", {
308
+ showErrors: t.boolean,
309
+ formatError: checked("CommandBar.formatError", v => v === undefined || fn(v)),
310
+ label: t.string,
311
+ commands: t.array(t.instance(Command)),
312
+ }),
313
+ ProgressStatus: element()("aster-progress-status", {
314
+ busy: t.boolean, error: t.optional(t.string), progressPolicy: progressTiming,
315
+ pendingText: t.string, completeText: t.string, reserveSpace: t.boolean,
316
+ }),
317
+ Sidebar: Object.freeze(compositeElement(Sidebar)),
318
+ });
@@ -0,0 +1,18 @@
1
+ import { Command } from "@getaster/data";
2
+ import { AsterElement, type View } from "../base/element.ts";
3
+ import "../button/button.ts";
4
+ /** A labelled action group; every surface consumes the original guarded commands. */
5
+ export declare class CommandBar extends AsterElement {
6
+ #private;
7
+ get commands(): readonly Command[];
8
+ set commands(value: readonly Command[]);
9
+ get label(): string;
10
+ set label(value: string);
11
+ /** Disable when the application presents contextual failures at another surface. */
12
+ get showErrors(): boolean;
13
+ set showErrors(value: boolean);
14
+ /** Translate errors into application-facing text without changing command state. */
15
+ get formatError(): ((error: unknown, action: Command) => string) | undefined;
16
+ set formatError(value: ((error: unknown, action: Command) => string) | undefined);
17
+ protected render(): View;
18
+ }
@@ -0,0 +1,34 @@
1
+ import { Command } from "@getaster/data";
2
+ import { html, nothing } from "lit-html";
3
+ import { AsterElement, define } from "../base/element.js";
4
+ import "../button/button.js";
5
+ /** A labelled action group; every surface consumes the original guarded commands. */
6
+ export class CommandBar extends AsterElement {
7
+ #commands = this.prop([]);
8
+ #label = this.prop("Actions");
9
+ #showErrors = this.prop(true);
10
+ #formatError = this.prop(undefined);
11
+ get commands() { return this.#commands.value; }
12
+ set commands(value) { this.#commands.value = value; }
13
+ get label() { return this.#label.value; }
14
+ set label(value) { this.#label.value = value; }
15
+ /** Disable when the application presents contextual failures at another surface. */
16
+ get showErrors() { return this.#showErrors.value; }
17
+ set showErrors(value) { this.#showErrors.value = value; }
18
+ /** Translate errors into application-facing text without changing command state. */
19
+ get formatError() { return this.#formatError.value; }
20
+ set formatError(value) { this.#formatError.value = value; }
21
+ render() {
22
+ const commands = this.#commands.value;
23
+ const failures = this.#showErrors.value ? commands.filter(action => action.error.value !== undefined) : [];
24
+ return html `<div class="aster-command-bar" role="group" aria-label=${this.#label.value}>
25
+ ${commands.map(action => html `<aster-button .label=${action.label} .busy=${action.busy.value}
26
+ .reason=${action.reason.value} @click=${() => void action.execute().catch(() => { })}></aster-button>`)}
27
+ </div>${failures.length ? html `<div class="aster-command-error" role="status">${failures.map(action => {
28
+ const error = action.error.value;
29
+ const message = this.#formatError.value?.(error, action) ?? (error instanceof Error ? error.message : String(error));
30
+ return html `<p><strong>${action.label}</strong><span>${message}</span></p>`;
31
+ })}</div>` : nothing}`;
32
+ }
33
+ }
34
+ define("aster-command-bar", CommandBar);
@@ -0,0 +1,14 @@
1
+ import type { CompositeDefinition } from "./definition.ts";
2
+ type Commands = NonNullable<CompositeDefinition["commands"]>;
3
+ export interface CommandPlan {
4
+ readonly order: readonly string[];
5
+ readonly errors: readonly {
6
+ readonly path: string;
7
+ readonly message: string;
8
+ }[];
9
+ }
10
+ /** Resolve declared aliases by dependency, independent of object key order. */
11
+ export declare function planCommands(commands: Commands, accepts: (reference: string) => boolean): CommandPlan;
12
+ /** Suggest aliases which are valid and cannot introduce a cycle at this destination. */
13
+ export declare function commandTargets(commands: Commands, base: readonly string[], destination?: string): readonly string[];
14
+ export {};
@@ -0,0 +1,59 @@
1
+ /** Resolve declared aliases by dependency, independent of object key order. */
2
+ export function planCommands(commands, accepts) {
3
+ const state = new Map();
4
+ const order = [];
5
+ const errors = [];
6
+ function visit(key, chain) {
7
+ switch (state.get(key)) {
8
+ case "valid": return true;
9
+ case "invalid": return false;
10
+ case "visiting":
11
+ errors.push({ path: `definition.commands.${key}.use`, message: `Command reference cycle: ${[...chain, key].join(" → ")}` });
12
+ return false;
13
+ }
14
+ if (chain.length >= 128) {
15
+ errors.push({ path: `definition.commands.${key}.use`, message: "Command alias chain exceeds 128 links" });
16
+ state.set(key, "invalid");
17
+ return false;
18
+ }
19
+ state.set(key, "visiting");
20
+ const reference = commands[key].use;
21
+ const alias = reference.startsWith("commands.") ? reference.slice("commands.".length) : undefined;
22
+ let valid;
23
+ if (alias !== undefined && Object.hasOwn(commands, alias))
24
+ valid = visit(alias, [...chain, key]);
25
+ else {
26
+ valid = accepts(reference);
27
+ if (!valid)
28
+ errors.push({ path: `definition.commands.${key}.use`, message: `Expected a declared Command reference; unknown or incompatible '${reference}'` });
29
+ }
30
+ state.set(key, valid ? "valid" : "invalid");
31
+ if (valid)
32
+ order.push(key);
33
+ return valid;
34
+ }
35
+ Object.keys(commands).forEach(key => visit(key, []));
36
+ return { order, errors };
37
+ }
38
+ /** Suggest aliases which are valid and cannot introduce a cycle at this destination. */
39
+ export function commandTargets(commands, base, destination) {
40
+ const references = new Set(base);
41
+ const plan = planCommands(commands, reference => references.has(reference));
42
+ const dependsOn = (key, target) => {
43
+ const seen = new Set();
44
+ while (Object.hasOwn(commands, key) && !seen.has(key)) {
45
+ if (key === target)
46
+ return true;
47
+ seen.add(key);
48
+ const reference = commands[key].use;
49
+ if (!reference.startsWith("commands."))
50
+ return false;
51
+ key = reference.slice("commands.".length);
52
+ }
53
+ return false;
54
+ };
55
+ for (const key of plan.order)
56
+ if (!destination || !dependsOn(key, destination))
57
+ references.add(`commands.${key}`);
58
+ return [...references];
59
+ }
@@ -0,0 +1,48 @@
1
+ import { type ReadonlySignal } from "@preact/signals-core";
2
+ import { type TemplateResult } from "lit-html";
3
+ import { Command, Lifetime, PageScope, SessionActivity, type DataContext } from "@getaster/data";
4
+ import { type CompositeDefinition, type CompositeSources, type CompositeOverrides } from "./definition.ts";
5
+ import { type CompositeResolver, type CompositeValues, type CompositeComponentType, type CompositeContract } from "./registry.ts";
6
+ import type { CompositeContractValues } from "./contracts.ts";
7
+ export type CompositePlacement = (place: (child?: string) => HTMLElement) => TemplateResult;
8
+ export interface CompositeOptions<I extends CompositeContract = CompositeContract> {
9
+ readonly inputs?: CompositeContractValues<I>;
10
+ readonly parentScope?: PageScope;
11
+ readonly activity?: SessionActivity;
12
+ /** Enables typed navigation in PageRules; inherited by nested scopes. */
13
+ readonly ctx?: DataContext;
14
+ readonly sources?: CompositeSources;
15
+ readonly overrides?: CompositeOverrides;
16
+ /** Optional semantic markup; the definition still controls activation and ownership. */
17
+ readonly placement?: CompositePlacement;
18
+ }
19
+ type CompositeArguments<I extends CompositeContract> = {} extends CompositeContractValues<I> ? [options?: CompositeOptions<I>] : [options: CompositeOptions<I> & {
20
+ readonly inputs: CompositeContractValues<I>;
21
+ }];
22
+ export interface CompositeChildHandle {
23
+ readonly status: ReadonlySignal<"idle" | "loading" | "ready" | "error" | "suppressed" | "disposed">;
24
+ readonly error: ReadonlySignal<Error | undefined>;
25
+ /** Does not change placement/visibility; concurrent callers join one creation. */
26
+ ensure(): Promise<HTMLElement>;
27
+ }
28
+ /** A logical owner with a stable scope and independently activated named views. */
29
+ export declare class Composite<I extends CompositeContract = CompositeContract> {
30
+ #private;
31
+ readonly registry: CompositeResolver<I>;
32
+ readonly element: HTMLDivElement;
33
+ readonly lifetime: Lifetime;
34
+ readonly scope: PageScope;
35
+ readonly activity: SessionActivity;
36
+ readonly commands: Readonly<Record<string, Command>>;
37
+ readonly children: Readonly<Record<string, CompositeChildHandle>>;
38
+ readonly model: CompositeValues;
39
+ readonly definition: CompositeDefinition;
40
+ constructor(definition: CompositeDefinition, registry: CompositeResolver<I>, ...args: CompositeArguments<NoInfer<I>>);
41
+ mount(target: HTMLElement): this;
42
+ /** The caller performs close/draft guards before unconditional teardown. */
43
+ dispose(): void;
44
+ report(error: unknown): Error;
45
+ }
46
+ /** Use either a compiled YAML definition or a JS definition as a registered child. */
47
+ export declare function compositeComponent(definition: CompositeDefinition, registry: CompositeResolver, inputs: CompositeContract, sources?: CompositeSources): CompositeComponentType;
48
+ export {};