@getaster/elements 0.3.5 → 0.4.5
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/NOTICE +4 -0
- package/README.md +13 -4
- package/bin/aster-theme-check.js +35 -4
- package/dist/base/element.d.ts +8 -1
- package/dist/base/element.js +7 -2
- package/dist/button/button.d.ts +17 -2
- package/dist/button/button.js +42 -8
- package/dist/composition/builtins.d.ts +623 -1
- package/dist/composition/builtins.js +232 -4
- package/dist/date/calendar.d.ts +3 -0
- package/dist/date/calendar.js +8 -2
- package/dist/date/date-field.d.ts +23 -3
- package/dist/date/date-field.js +77 -9
- package/dist/date/dates.d.ts +10 -2
- package/dist/date/dates.js +23 -3
- package/dist/dialog/config.d.ts +4 -0
- package/dist/dialog/config.js +8 -0
- package/dist/dialog/dialog.d.ts +34 -6
- package/dist/dialog/dialog.js +151 -14
- package/dist/field/bound-field.d.ts +17 -1
- package/dist/field/bound-field.js +68 -7
- package/dist/field/check.js +1 -0
- package/dist/field/field.js +1 -1
- package/dist/field/input.d.ts +1 -0
- package/dist/field/input.js +2 -2
- package/dist/field/number-field.js +2 -2
- package/dist/filter/filter-builder.d.ts +7 -1
- package/dist/filter/filter-builder.js +8 -1
- package/dist/form/controls.d.ts +40 -8
- package/dist/form/controls.js +58 -15
- package/dist/form/custom-field.d.ts +1 -0
- package/dist/form/custom-field.js +23 -0
- package/dist/form/features.d.ts +27 -0
- package/dist/form/features.js +52 -0
- package/dist/form/form.d.ts +67 -7
- package/dist/form/form.js +267 -24
- package/dist/form/policy.d.ts +29 -0
- package/dist/form/policy.js +16 -0
- package/dist/grid/appearance.d.ts +365 -0
- package/dist/grid/appearance.js +814 -0
- package/dist/grid/features.d.ts +38 -0
- package/dist/grid/features.js +78 -0
- package/dist/grid/grid.d.ts +216 -4
- package/dist/grid/grid.js +1810 -308
- package/dist/grid/policy.d.ts +58 -0
- package/dist/grid/policy.js +17 -0
- package/dist/grid/view.d.ts +32 -2
- package/dist/grid/view.js +89 -4
- package/dist/index.d.ts +14 -11
- package/dist/index.js +2 -1
- package/dist/menu/config.d.ts +7 -0
- package/dist/menu/config.js +44 -0
- package/dist/menu/menu.d.ts +23 -0
- package/dist/menu/menu.js +69 -17
- package/dist/picker/picker.js +8 -3
- package/dist/section/accordion.d.ts +26 -0
- package/dist/section/accordion.js +86 -8
- package/dist/section/policy.d.ts +8 -0
- package/dist/section/policy.js +17 -0
- package/dist/section/section.d.ts +30 -0
- package/dist/section/section.js +106 -3
- package/dist/select/choice-element.d.ts +12 -0
- package/dist/select/choice-element.js +36 -0
- package/dist/select/choice.d.ts +12 -2
- package/dist/select/choice.js +93 -28
- package/dist/select/select.d.ts +12 -0
- package/dist/select/select.js +37 -1
- package/dist/styles/contract.d.ts +14 -0
- package/dist/styles/contract.js +15 -0
- package/dist/switch/switch.d.ts +16 -0
- package/dist/switch/switch.js +56 -6
- package/dist/tree/navigator.d.ts +3 -0
- package/dist/tree/navigator.js +26 -2
- package/dist/window/chrome.d.ts +5 -1
- package/dist/window/chrome.js +4 -3
- package/dist/window/config.d.ts +71 -0
- package/dist/window/config.js +40 -0
- package/dist/window/window.d.ts +51 -13
- package/dist/window/window.js +233 -38
- package/dist/yaml.d.ts +19 -0
- package/dist/yaml.js +57 -0
- package/package.json +10 -4
- package/styles/aster-ui.css +181 -10
- package/styles/base.css +14 -0
- package/styles/contract.ts +16 -0
- package/styles/elements.css +167 -10
- package/styles/fonts/OFL-bricolage-grotesque.txt +93 -0
- package/styles/fonts/OFL-fraunces.txt +93 -0
- package/styles/fonts/bricolage-grotesque-latin-opsz-normal.woff2 +0 -0
- package/styles/fonts/fraunces-latin-full-normal.woff2 +0 -0
- package/styles/fonts.css +17 -0
- package/styles/theme-harbour.css +5 -0
- package/styles/theme-lamplight.css +5 -0
|
@@ -22,9 +22,12 @@ import { DateField } from "../date/date-field.js";
|
|
|
22
22
|
import { Choice } from "../select/choice-element.js";
|
|
23
23
|
import { Switch } from "../switch/switch.js";
|
|
24
24
|
import { Grid } from "../grid/grid.js";
|
|
25
|
+
import { validateFeatures } from "../grid/features.js";
|
|
26
|
+
import { validateGridLabels, validateGridPresentation } from "../grid/appearance.js";
|
|
25
27
|
import { TileGrid } from "../tile-grid/tile-grid.js";
|
|
26
28
|
import { Listbox } from "../listbox/listbox.js";
|
|
27
29
|
import { Form } from "../form/form.js";
|
|
30
|
+
import { validateFormFeatures } from "../form/features.js";
|
|
28
31
|
import { ListEditor } from "../list-edit/list-edit.js";
|
|
29
32
|
import { RuleEditor } from "../rule/rule-editor.js";
|
|
30
33
|
import { FilterBuilder } from "../filter/filter-builder.js";
|
|
@@ -37,6 +40,7 @@ import { Slicer } from "../slicer/slicer.js";
|
|
|
37
40
|
import { ComponentHost } from "../component/component.js";
|
|
38
41
|
import { Window } from "../window/window.js";
|
|
39
42
|
import { Dialog } from "../dialog/dialog.js";
|
|
43
|
+
import { Menu } from "../menu/menu.js";
|
|
40
44
|
import { Dashboard } from "../dashboard/dashboard.js";
|
|
41
45
|
import { CommandBar } from "../composition/command-bar.js";
|
|
42
46
|
const object = (value) => !!value && typeof value === "object" && [Object.prototype, null].includes(Object.getPrototypeOf(value));
|
|
@@ -74,20 +78,136 @@ const numeric = (v) => {
|
|
|
74
78
|
}
|
|
75
79
|
};
|
|
76
80
|
const sourceOptions = (v) => v === undefined || v instanceof Collection || v instanceof PickerOptions;
|
|
77
|
-
const
|
|
78
|
-
|
|
81
|
+
const formControlName = checked("form control name", (v) => v === "text" || v === "number" || v === "date" || v === "check" || v === "select" || v === "picker");
|
|
82
|
+
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)
|
|
83
|
+
&& (v.title === undefined || typeof v.title === "string")
|
|
84
|
+
&& (v.hint === undefined || typeof v.hint === "string")
|
|
85
|
+
&& (v.placeholder === undefined || typeof v.placeholder === "string")
|
|
86
|
+
&& optionalBoolean(v.readOnly) && optionalBoolean(v.disabled) && optionalBoolean(v.required)
|
|
87
|
+
&& (v.control === undefined || formControlName.validate(v.control)))
|
|
88
|
+
|| (typeof v.title === "string" && t.signal(t.string).validate(v.value)))));
|
|
79
89
|
const sections = t.array(t.object({ title: t.string, columns: t.optional(t.positiveInteger), open: t.optional(t.boolean), items: t.array(formItem) }));
|
|
90
|
+
const formPolicy = t.object({ readOnly: t.optional(t.boolean), disabled: t.optional(t.boolean) });
|
|
91
|
+
const formChrome = t.object({
|
|
92
|
+
columns: t.optional(t.positiveInteger),
|
|
93
|
+
titleOrientation: t.optional(literals("top", "start")),
|
|
94
|
+
showInlineErrors: t.optional(t.boolean),
|
|
95
|
+
errorSummary: t.optional(t.boolean),
|
|
96
|
+
autoFocus: t.optional(t.boolean),
|
|
97
|
+
});
|
|
98
|
+
const formFeatures = t.custom("form feature list", (value) => {
|
|
99
|
+
try {
|
|
100
|
+
validateFormFeatures(value);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}, { type: "array", items: { type: "object" } });
|
|
107
|
+
const isoDate = checked("date (YYYY-MM-DD)", (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v));
|
|
80
108
|
const gridView = checked("Grid view", v => object(v)
|
|
81
109
|
&& 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
110
|
&& Array.isArray(v.sort) && v.sort.every(k => object(k) && typeof k.field === "string" && optionalBoolean(k.descending))
|
|
83
111
|
&& object(v.filters) && Object.values(v.filters).every(c => object(c) && typeof c.op === "string")
|
|
84
112
|
&& typeof v.frozen === "number" && Number.isSafeInteger(v.frozen) && v.frozen >= 0
|
|
85
113
|
&& (v.groupBy === undefined || typeof v.groupBy === "string" || Array.isArray(v.groupBy) && v.groupBy.every(k => typeof k === "string")));
|
|
114
|
+
const gridChrome = t.object({
|
|
115
|
+
toolbar: t.optional(t.boolean),
|
|
116
|
+
header: t.optional(t.boolean),
|
|
117
|
+
filterRow: t.optional(checked('true, false or "auto"', v => v === "auto" || typeof v === "boolean")),
|
|
118
|
+
headerMenu: t.optional(t.boolean),
|
|
119
|
+
pagingBar: t.optional(t.boolean),
|
|
120
|
+
});
|
|
121
|
+
const gridToolbar = t.object({
|
|
122
|
+
count: t.optional(t.boolean),
|
|
123
|
+
sort: t.optional(t.boolean),
|
|
124
|
+
filters: t.optional(t.boolean),
|
|
125
|
+
advanced: t.optional(t.boolean),
|
|
126
|
+
columns: t.optional(t.boolean),
|
|
127
|
+
views: t.optional(t.boolean),
|
|
128
|
+
expandAll: t.optional(t.boolean),
|
|
129
|
+
extra: t.optional(checked("Grid.toolbar.extra", v => v === undefined || view(v))),
|
|
130
|
+
});
|
|
131
|
+
// Capability switches in a composition catalog are plain data: a boolean or a
|
|
132
|
+
// field-name allowlist. Predicates remain a JavaScript-only setter value.
|
|
133
|
+
const gridCapability = t.custom("boolean or field allowlist", (value) => typeof value === "boolean" || Array.isArray(value) && value.every((field) => typeof field === "string"), { anyOf: [{ type: "boolean" }, { type: "array", items: { type: "string" } }] });
|
|
134
|
+
const fieldPolicy = t.object({
|
|
135
|
+
sortable: t.optional(gridCapability), filterable: t.optional(gridCapability), groupable: t.optional(gridCapability),
|
|
136
|
+
resizable: t.optional(gridCapability), movable: t.optional(gridCapability), hideable: t.optional(gridCapability),
|
|
137
|
+
freezable: t.optional(gridCapability), editable: t.optional(gridCapability),
|
|
138
|
+
});
|
|
139
|
+
const fieldPolicies = t.custom("field policy record", (value) => object(value) && Object.values(value).every((entry) => fieldPolicy.validate(entry)), { type: "object", additionalProperties: fieldPolicy.schema ?? {} });
|
|
140
|
+
const gridPolicy = t.object({
|
|
141
|
+
sortable: t.optional(gridCapability),
|
|
142
|
+
filterable: t.optional(gridCapability),
|
|
143
|
+
groupable: t.optional(gridCapability),
|
|
144
|
+
resizable: t.optional(gridCapability),
|
|
145
|
+
movable: t.optional(gridCapability),
|
|
146
|
+
hideable: t.optional(gridCapability),
|
|
147
|
+
freezable: t.optional(gridCapability),
|
|
148
|
+
editable: t.optional(gridCapability),
|
|
149
|
+
selectable: t.optional(t.boolean),
|
|
150
|
+
fields: t.optional(fieldPolicies),
|
|
151
|
+
});
|
|
152
|
+
const gridFeatures = t.custom("grid feature list", (value) => {
|
|
153
|
+
try {
|
|
154
|
+
validateFeatures(value);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}, { type: "array", items: { type: "object" } });
|
|
161
|
+
// Presentation is plain data in a catalog; JS-only class predicates and hilite
|
|
162
|
+
// tests fail validation because `validateGridPresentation` rejects functions.
|
|
163
|
+
const gridPresentation = t.custom("grid presentation", (value) => {
|
|
164
|
+
try {
|
|
165
|
+
validateGridPresentation(value);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}, { type: "object" });
|
|
172
|
+
const gridLabels = t.custom("grid label bag", (value) => {
|
|
173
|
+
try {
|
|
174
|
+
validateGridLabels(value);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}, { type: "object", additionalProperties: { type: "string" } });
|
|
86
181
|
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 });
|
|
182
|
+
const sectionChrome = t.object({
|
|
183
|
+
titleBar: t.optional(t.boolean),
|
|
184
|
+
fold: t.optional(t.boolean),
|
|
185
|
+
close: t.optional(t.boolean),
|
|
186
|
+
footer: t.optional(t.boolean),
|
|
187
|
+
status: t.optional(literals("collapsed", "always")),
|
|
188
|
+
});
|
|
189
|
+
const windowChrome = t.object({
|
|
190
|
+
header: t.optional(t.boolean),
|
|
191
|
+
title: t.optional(t.boolean),
|
|
192
|
+
close: t.optional(t.boolean),
|
|
193
|
+
minimize: t.optional(t.boolean),
|
|
194
|
+
maximize: t.optional(t.boolean),
|
|
195
|
+
resizer: t.optional(t.boolean),
|
|
196
|
+
footer: t.optional(t.boolean),
|
|
197
|
+
statusBar: t.optional(t.boolean),
|
|
198
|
+
});
|
|
199
|
+
const positiveNumber = t.custom("positive finite number", (v) => typeof v === "number" && Number.isFinite(v) && v > 0, { type: "number", exclusiveMinimum: 0 });
|
|
200
|
+
const windowPlacement = t.object({
|
|
201
|
+
state: literals("normal", "maximized", "minimized"),
|
|
202
|
+
bounds: t.optional(t.object({ x: t.number, y: t.number, width: positiveNumber, height: positiveNumber })),
|
|
203
|
+
restoreMaximized: t.optional(t.boolean),
|
|
204
|
+
});
|
|
205
|
+
const initialFocus = checked('"first", "dialog", "none" or a focus function', v => v === undefined || v === "first" || v === "dialog" || v === "none" || fn(v));
|
|
87
206
|
const progressTiming = t.object({ delayMs: t.optional(milliseconds), minVisibleMs: t.optional(milliseconds) });
|
|
88
207
|
const bound = {
|
|
89
208
|
model: t.optional(t.instance(EditModel)), field: t.string, label: t.string,
|
|
90
209
|
labelled: t.boolean, sharedReasons: t.instance((Map)),
|
|
210
|
+
hint: t.string, showErrors: t.boolean, validateOn: literals("submit", "change", "exit"),
|
|
91
211
|
};
|
|
92
212
|
/** Reusable public control contracts. Select entries explicitly for an application's allowlist. */
|
|
93
213
|
export const asterComponents = Object.freeze({
|
|
@@ -97,6 +217,9 @@ export const asterComponents = Object.freeze({
|
|
|
97
217
|
confirmLabel: t.optional(t.string),
|
|
98
218
|
busy: bindable(t.boolean),
|
|
99
219
|
variant: literals("secondary", "primary"),
|
|
220
|
+
tone: literals("secondary", "primary", "danger"),
|
|
221
|
+
size: literals("sm", "md", "lg"),
|
|
222
|
+
disabled: t.boolean,
|
|
100
223
|
type: literals("button", "submit"),
|
|
101
224
|
icon: checked("Button.icon", view),
|
|
102
225
|
reason: t.optional(t.string),
|
|
@@ -131,6 +254,8 @@ export const asterComponents = Object.freeze({
|
|
|
131
254
|
Accordion: element()("aster-accordion", {
|
|
132
255
|
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
256
|
expanded: t.array(t.string), multiple: t.boolean, headingLevel: literals(2, 3, 4),
|
|
257
|
+
visibilityMode: literals("multiple", "mutex"),
|
|
258
|
+
canClose: t.boolean, canReorder: t.boolean, canCollapseAll: t.boolean,
|
|
134
259
|
}),
|
|
135
260
|
Check: element()("aster-check", {
|
|
136
261
|
...bound,
|
|
@@ -138,6 +263,10 @@ export const asterComponents = Object.freeze({
|
|
|
138
263
|
Select: element()("aster-select", {
|
|
139
264
|
...bound,
|
|
140
265
|
choices: t.optional(options),
|
|
266
|
+
placeholder: t.string,
|
|
267
|
+
filter: t.boolean,
|
|
268
|
+
clearable: t.boolean,
|
|
269
|
+
noResults: t.string,
|
|
141
270
|
}),
|
|
142
271
|
Picker: element()("aster-picker", {
|
|
143
272
|
...bound,
|
|
@@ -155,16 +284,29 @@ export const asterComponents = Object.freeze({
|
|
|
155
284
|
}),
|
|
156
285
|
DateField: element()("aster-date", {
|
|
157
286
|
...bound,
|
|
287
|
+
min: t.optional(isoDate),
|
|
288
|
+
max: t.optional(isoDate),
|
|
289
|
+
format: literals("medium", "short", "iso"),
|
|
290
|
+
placeholder: t.string,
|
|
291
|
+
dismissOnSelect: t.boolean,
|
|
158
292
|
}),
|
|
159
293
|
Choice: element()("aster-choice", {
|
|
160
294
|
options: options,
|
|
161
295
|
value: checked("Choice.value", v => v === undefined || value(v)),
|
|
162
296
|
label: t.string,
|
|
163
297
|
placeholder: t.string,
|
|
298
|
+
filter: t.boolean,
|
|
299
|
+
clearable: t.boolean,
|
|
300
|
+
noResults: t.string,
|
|
301
|
+
disabled: t.boolean,
|
|
164
302
|
}),
|
|
165
303
|
Switch: element()("aster-switch", {
|
|
166
304
|
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
305
|
label: t.string,
|
|
306
|
+
disabled: t.boolean,
|
|
307
|
+
readOnly: t.boolean,
|
|
308
|
+
onLabel: t.optional(t.string),
|
|
309
|
+
offLabel: t.optional(t.string),
|
|
168
310
|
}),
|
|
169
311
|
Grid: element()("aster-grid", {
|
|
170
312
|
progressPolicy: progressTiming,
|
|
@@ -177,18 +319,55 @@ export const asterComponents = Object.freeze({
|
|
|
177
319
|
persist: t.string,
|
|
178
320
|
renderers: checked("Grid.renderers", v => object(v) && Object.values(v).every(fn)),
|
|
179
321
|
formatters: checked("Grid.formatters", v => object(v) && Object.values(v).every(fn)),
|
|
322
|
+
presentation: gridPresentation,
|
|
323
|
+
labels: gridLabels,
|
|
180
324
|
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
325
|
lookups: checked("Grid.lookups", v => object(v) && Object.values(v).every(l => object(l) && typeof l.set === "string" && typeof l.display === "string")),
|
|
182
326
|
rowMenu: checked("Grid.rowMenu", fn),
|
|
183
|
-
|
|
327
|
+
columnMenu: checked("Grid.columnMenu", fn),
|
|
328
|
+
filterOperators: checked("Grid.filterOperators", v => object(v) && Object.values(v).every(list => Array.isArray(list) && list.every(op => typeof op === "string" && op.length > 0))),
|
|
329
|
+
tree: checked("Grid.tree", v => v === undefined || object(v) && typeof v.parentField === "string"
|
|
330
|
+
&& optionalBoolean(v.draggable) && optionalBoolean(v.reparent) && optionalBoolean(v.dropOnLeaves) && optionalBoolean(v.cascadeSelection)
|
|
331
|
+
&& optionalBoolean(v.showOpener) && (v.autoOpen === undefined || Number.isSafeInteger(v.autoOpen) && typeof v.autoOpen === "number" && v.autoOpen >= 0)
|
|
332
|
+
&& (v.pageSize === undefined || t.positiveInteger.validate(v.pageSize)) && (v.matchLimit === undefined || t.positiveInteger.validate(v.matchLimit))
|
|
333
|
+
&& (v.openLimit === undefined || t.positiveInteger.validate(v.openLimit))
|
|
334
|
+
&& (v.folderIcon === undefined || view(v.folderIcon)) && (v.nodeIcon === undefined || view(v.nodeIcon))
|
|
335
|
+
&& (v.filterMode === undefined || ["keepAncestors", "strict"].includes(String(v.filterMode)))),
|
|
184
336
|
rowControls: checked("Grid.rowControls", v => v === undefined || fn(v)),
|
|
185
337
|
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
|
-
|
|
338
|
+
policy: gridPolicy,
|
|
339
|
+
features: gridFeatures,
|
|
340
|
+
sortable: gridCapability,
|
|
341
|
+
filterable: gridCapability,
|
|
342
|
+
groupable: gridCapability,
|
|
343
|
+
resizable: gridCapability,
|
|
344
|
+
movable: gridCapability,
|
|
345
|
+
hideable: gridCapability,
|
|
346
|
+
freezable: gridCapability,
|
|
347
|
+
editable: gridCapability,
|
|
348
|
+
multiSort: t.boolean,
|
|
349
|
+
editMode: literals("row", "cell"),
|
|
350
|
+
selectionAppearance: literals("row", "cell"),
|
|
351
|
+
selectionColumn: t.boolean,
|
|
187
352
|
rules: checked("Grid.rules", object),
|
|
188
353
|
empty: checked("Grid.empty", view),
|
|
354
|
+
loading: checked("Grid.loading", view),
|
|
355
|
+
loadingMessage: t.string,
|
|
356
|
+
error: checked("Grid.error", v => v === undefined || fn(v)),
|
|
357
|
+
autoFetch: t.boolean,
|
|
358
|
+
pageSize: t.positiveInteger,
|
|
359
|
+
keepPages: t.optional(t.positiveInteger),
|
|
360
|
+
exactCount: t.boolean,
|
|
189
361
|
collection: t.optional(t.instance(Collection)),
|
|
190
362
|
selection: t.optional(t.instance(Selection)),
|
|
191
363
|
selectable: t.boolean,
|
|
364
|
+
chrome: gridChrome,
|
|
365
|
+
toolbar: gridToolbar,
|
|
366
|
+
showHeader: t.boolean,
|
|
367
|
+
showToolbar: t.boolean,
|
|
368
|
+
showFilterRow: t.boolean,
|
|
369
|
+
showHeaderMenu: t.boolean,
|
|
370
|
+
showPagingBar: t.boolean,
|
|
192
371
|
}),
|
|
193
372
|
TileGrid: element()("aster-tile-grid", {
|
|
194
373
|
collection: t.optional(t.instance(Collection)),
|
|
@@ -215,6 +394,18 @@ export const asterComponents = Object.freeze({
|
|
|
215
394
|
model: t.optional(t.instance(EditModel)),
|
|
216
395
|
sections,
|
|
217
396
|
headingLevel: literals(2, 3, 4),
|
|
397
|
+
policy: formPolicy,
|
|
398
|
+
readOnly: t.boolean,
|
|
399
|
+
disabled: t.boolean,
|
|
400
|
+
chrome: formChrome,
|
|
401
|
+
columns: t.positiveInteger,
|
|
402
|
+
titleOrientation: literals("top", "start"),
|
|
403
|
+
showInlineErrors: t.boolean,
|
|
404
|
+
errorSummary: t.boolean,
|
|
405
|
+
autoFocus: t.boolean,
|
|
406
|
+
features: formFeatures,
|
|
407
|
+
validateOn: literals("submit", "change", "exit"),
|
|
408
|
+
saveOnEnter: t.boolean,
|
|
218
409
|
}),
|
|
219
410
|
ListEditor: element()("aster-list-editor", {
|
|
220
411
|
list: t.optional(t.instance(ListEdit)),
|
|
@@ -238,6 +429,7 @@ export const asterComponents = Object.freeze({
|
|
|
238
429
|
model: t.optional(t.instance(FilterDraft)),
|
|
239
430
|
label: t.string,
|
|
240
431
|
readOnlyReason: t.optional(t.string),
|
|
432
|
+
operators: checked("FilterBuilder.operators", v => v === undefined || fn(v)),
|
|
241
433
|
}),
|
|
242
434
|
TreeNavigator: element()("aster-tree-navigator", {
|
|
243
435
|
tree: t.optional(t.instance(Tree)),
|
|
@@ -246,6 +438,7 @@ export const asterComponents = Object.freeze({
|
|
|
246
438
|
choosable: checked("TreeNavigator.choosable", v => v === undefined || fn(v)),
|
|
247
439
|
headingLevel: literals(1, 2, 3, 4, 5, 6),
|
|
248
440
|
chooseLabel: t.string,
|
|
441
|
+
search: t.boolean,
|
|
249
442
|
}),
|
|
250
443
|
Tabs: element()("aster-tabs", {
|
|
251
444
|
renderPanel: checked("Tabs.renderPanel", v => v === undefined || fn(v)),
|
|
@@ -257,12 +450,16 @@ export const asterComponents = Object.freeze({
|
|
|
257
450
|
}),
|
|
258
451
|
Section: element()("aster-section", {
|
|
259
452
|
actions: checked("Section.actions", view),
|
|
453
|
+
chrome: sectionChrome,
|
|
454
|
+
collapsible: t.boolean,
|
|
260
455
|
disabled: t.boolean,
|
|
456
|
+
footer: checked("Section.footer", view),
|
|
261
457
|
heading: t.string,
|
|
262
458
|
open: t.boolean,
|
|
263
459
|
body: checked("Section.body", view),
|
|
264
460
|
level: literals(2, 3, 4),
|
|
265
461
|
summary: checked("Section.summary", view),
|
|
462
|
+
canClose: t.boolean,
|
|
266
463
|
}),
|
|
267
464
|
Split: element()("aster-split", {
|
|
268
465
|
orientation: literals("horizontal", "vertical"), size: t.number, collapsible: t.boolean, collapsed: t.boolean, responsive: t.boolean,
|
|
@@ -296,10 +493,41 @@ export const asterComponents = Object.freeze({
|
|
|
296
493
|
managed: t.boolean,
|
|
297
494
|
preserveContent: t.boolean,
|
|
298
495
|
beforeClose: checked("Window.beforeClose", v => v === undefined || fn(v)),
|
|
496
|
+
chrome: windowChrome,
|
|
497
|
+
footer: checked("Window.footer", view),
|
|
498
|
+
status: checked("Window.status", view),
|
|
499
|
+
initialPlacement: windowPlacement,
|
|
500
|
+
modal: t.boolean,
|
|
501
|
+
dismissOnEscape: t.boolean,
|
|
502
|
+
keepInParentRect: t.boolean,
|
|
503
|
+
autoCenter: t.boolean,
|
|
504
|
+
showHeader: t.boolean,
|
|
505
|
+
showTitle: t.boolean,
|
|
506
|
+
showCloseButton: t.boolean,
|
|
507
|
+
showMinimizeButton: t.boolean,
|
|
508
|
+
showMaximizeButton: t.boolean,
|
|
509
|
+
showResizer: t.boolean,
|
|
510
|
+
showFooter: t.boolean,
|
|
511
|
+
showStatusBar: t.boolean,
|
|
299
512
|
}),
|
|
300
513
|
Dialog: element()("aster-dialog", {
|
|
301
514
|
content: checked("Dialog.content", view),
|
|
302
515
|
labelledBy: t.optional(t.string),
|
|
516
|
+
title: t.string,
|
|
517
|
+
header: t.boolean,
|
|
518
|
+
footer: checked("Dialog.footer", view),
|
|
519
|
+
showCloseButton: t.boolean,
|
|
520
|
+
modal: t.boolean,
|
|
521
|
+
dismissOnEscape: t.boolean,
|
|
522
|
+
dismissOnBackdrop: t.boolean,
|
|
523
|
+
initialFocus,
|
|
524
|
+
}),
|
|
525
|
+
Menu: element()("aster-menu", {
|
|
526
|
+
showIcons: t.boolean,
|
|
527
|
+
showShortcuts: t.boolean,
|
|
528
|
+
submenuDirection: literals("auto", "start", "end"),
|
|
529
|
+
autoDismiss: t.boolean,
|
|
530
|
+
emptyMessage: t.string,
|
|
303
531
|
}),
|
|
304
532
|
Dashboard: element()("aster-dashboard", {
|
|
305
533
|
label: t.string,
|
package/dist/date/calendar.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface CalendarProps {
|
|
|
16
16
|
readonly value: string | undefined;
|
|
17
17
|
readonly today: string;
|
|
18
18
|
readonly label: string;
|
|
19
|
+
/** Inclusive bounds, `YYYY-MM-DD`; days outside are shown but can't be chosen. */
|
|
20
|
+
readonly min?: string | undefined;
|
|
21
|
+
readonly max?: string | undefined;
|
|
19
22
|
readonly choose: (date: string) => void;
|
|
20
23
|
/** Closed without a choice: Escape, or a press outside. */
|
|
21
24
|
readonly dismiss: (refocus: boolean) => void;
|
package/dist/date/calendar.js
CHANGED
|
@@ -59,6 +59,11 @@ export function calendarView(state, p) {
|
|
|
59
59
|
queueMicrotask(() => document.getElementById(`${p.id}-${to}`)?.focus());
|
|
60
60
|
};
|
|
61
61
|
const page = (by) => (state.focused.value = shift(focused, by));
|
|
62
|
+
const allowed = (day) => (p.min === undefined || day >= p.min) && (p.max === undefined || day <= p.max);
|
|
63
|
+
const choose = (day) => {
|
|
64
|
+
if (allowed(day))
|
|
65
|
+
p.choose(day);
|
|
66
|
+
};
|
|
62
67
|
const grid = (e) => {
|
|
63
68
|
const to = gridKey(e, focused);
|
|
64
69
|
if (to) {
|
|
@@ -67,7 +72,7 @@ export function calendarView(state, p) {
|
|
|
67
72
|
}
|
|
68
73
|
else if (e.key === "Enter" || e.key === " ") {
|
|
69
74
|
e.preventDefault();
|
|
70
|
-
|
|
75
|
+
choose(focused);
|
|
71
76
|
}
|
|
72
77
|
};
|
|
73
78
|
const dialog = (e) => {
|
|
@@ -106,8 +111,9 @@ export function calendarView(state, p) {
|
|
|
106
111
|
? html `<td></td>`
|
|
107
112
|
: html `<td role="gridcell" id=${`${p.id}-${day}`} tabindex=${day === focused ? "0" : "-1"}
|
|
108
113
|
aria-selected=${day === p.value ? "true" : nothing} aria-current=${day === p.today ? "date" : nothing}
|
|
114
|
+
aria-disabled=${allowed(day) ? nothing : "true"}
|
|
109
115
|
aria-label=${DAY.format(Date.parse(`${day}T12:00:00Z`))}
|
|
110
|
-
@click=${() =>
|
|
116
|
+
@click=${() => choose(day)}>${Number(day.slice(8))}</td>`)}</tr>`)}
|
|
111
117
|
</tbody>
|
|
112
118
|
</table>
|
|
113
119
|
</div>`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Field as FieldMeta, Value } from "@getaster/data";
|
|
1
2
|
import { type View } from "../base/element.ts";
|
|
2
3
|
import { BoundField, type ControlContext } from "../field/bound-field.ts";
|
|
4
|
+
import { type DateFormat } from "./dates.ts";
|
|
3
5
|
/**
|
|
4
6
|
* A date field: typed dates in the forms people write them ("7 Oct",
|
|
5
7
|
* "7/10/2026", "2026-10-07"), shown as "7 Oct 2026", with a calendar
|
|
@@ -7,12 +9,30 @@ import { BoundField, type ControlContext } from "../field/bound-field.ts";
|
|
|
7
9
|
* typed while the input has focus; text that isn't a date stays text, and
|
|
8
10
|
* validation says so.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* `min` and `max` bound the calendar and the value; `format` chooses how a
|
|
13
|
+
* date is written. The calendar button takes focus on press in every engine
|
|
14
|
+
* (macOS engines don't focus buttons on click), so focus never falls to the
|
|
15
|
+
* body between the input and the calendar: a grid row being edited would end
|
|
16
|
+
* its edit.
|
|
13
17
|
*/
|
|
14
18
|
export declare class DateField extends BoundField {
|
|
15
19
|
#private;
|
|
20
|
+
/** The earliest date that can be chosen, `YYYY-MM-DD`, inclusive. */
|
|
21
|
+
get min(): string | undefined;
|
|
22
|
+
set min(v: string | undefined);
|
|
23
|
+
/** The latest date that can be chosen, `YYYY-MM-DD`, inclusive. */
|
|
24
|
+
get max(): string | undefined;
|
|
25
|
+
set max(v: string | undefined);
|
|
26
|
+
/** How a date is written in the input: `"medium"` (default), `"short"` or `"iso"`. */
|
|
27
|
+
get format(): DateFormat;
|
|
28
|
+
set format(v: DateFormat);
|
|
29
|
+
/** Placeholder text. Default: "None" for a nullable field, otherwise a date hint. */
|
|
30
|
+
get placeholder(): string;
|
|
31
|
+
set placeholder(v: string);
|
|
32
|
+
/** Whether choosing a day closes the calendar. Default `true`. */
|
|
33
|
+
get dismissOnSelect(): boolean;
|
|
34
|
+
set dismissOnSelect(v: boolean);
|
|
16
35
|
focus(options?: FocusOptions): void;
|
|
36
|
+
protected validation(_meta: FieldMeta | undefined, value: Value | undefined): string | undefined;
|
|
17
37
|
protected control(c: ControlContext): View;
|
|
18
38
|
}
|
package/dist/date/date-field.js
CHANGED
|
@@ -4,7 +4,7 @@ import { define } from "../base/element.js";
|
|
|
4
4
|
import { BoundField } from "../field/bound-field.js";
|
|
5
5
|
import { anchorStyle } from "../overlay/anchor.js";
|
|
6
6
|
import { CalendarState, calendarView, closeOutside } from "./calendar.js";
|
|
7
|
-
import { formatDate, parseDate, today } from "./dates.js";
|
|
7
|
+
import { formatDate, isIsoDate, parseDate, today } from "./dates.js";
|
|
8
8
|
/**
|
|
9
9
|
* A date field: typed dates in the forms people write them ("7 Oct",
|
|
10
10
|
* "7/10/2026", "2026-10-07"), shown as "7 Oct 2026", with a calendar
|
|
@@ -12,22 +12,85 @@ import { formatDate, parseDate, today } from "./dates.js";
|
|
|
12
12
|
* typed while the input has focus; text that isn't a date stays text, and
|
|
13
13
|
* validation says so.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* `min` and `max` bound the calendar and the value; `format` chooses how a
|
|
16
|
+
* date is written. The calendar button takes focus on press in every engine
|
|
17
|
+
* (macOS engines don't focus buttons on click), so focus never falls to the
|
|
18
|
+
* body between the input and the calendar: a grid row being edited would end
|
|
19
|
+
* its edit.
|
|
18
20
|
*/
|
|
19
21
|
export class DateField extends BoundField {
|
|
20
22
|
#calendar = new CalendarState();
|
|
21
23
|
/** What's being typed, while the input has focus. */
|
|
22
24
|
#draft = this.prop(undefined);
|
|
25
|
+
#min = this.prop(undefined);
|
|
26
|
+
#max = this.prop(undefined);
|
|
27
|
+
#format = this.prop("medium");
|
|
28
|
+
#placeholder = this.prop("");
|
|
29
|
+
#dismissOnSelect = this.prop(true);
|
|
30
|
+
/** The earliest date that can be chosen, `YYYY-MM-DD`, inclusive. */
|
|
31
|
+
get min() {
|
|
32
|
+
return this.#min.value;
|
|
33
|
+
}
|
|
34
|
+
set min(v) {
|
|
35
|
+
if (v !== undefined && !isIsoDate(v))
|
|
36
|
+
throw new TypeError("min must be a date, like 2026-10-07");
|
|
37
|
+
this.#min.value = v;
|
|
38
|
+
}
|
|
39
|
+
/** The latest date that can be chosen, `YYYY-MM-DD`, inclusive. */
|
|
40
|
+
get max() {
|
|
41
|
+
return this.#max.value;
|
|
42
|
+
}
|
|
43
|
+
set max(v) {
|
|
44
|
+
if (v !== undefined && !isIsoDate(v))
|
|
45
|
+
throw new TypeError("max must be a date, like 2026-10-07");
|
|
46
|
+
this.#max.value = v;
|
|
47
|
+
}
|
|
48
|
+
/** How a date is written in the input: `"medium"` (default), `"short"` or `"iso"`. */
|
|
49
|
+
get format() {
|
|
50
|
+
return this.#format.value;
|
|
51
|
+
}
|
|
52
|
+
set format(v) {
|
|
53
|
+
if (v !== "medium" && v !== "short" && v !== "iso")
|
|
54
|
+
throw new TypeError('format must be "medium", "short" or "iso"');
|
|
55
|
+
this.#format.value = v;
|
|
56
|
+
}
|
|
57
|
+
/** Placeholder text. Default: "None" for a nullable field, otherwise a date hint. */
|
|
58
|
+
get placeholder() {
|
|
59
|
+
return this.#placeholder.value;
|
|
60
|
+
}
|
|
61
|
+
set placeholder(v) {
|
|
62
|
+
this.#placeholder.value = v;
|
|
63
|
+
}
|
|
64
|
+
/** Whether choosing a day closes the calendar. Default `true`. */
|
|
65
|
+
get dismissOnSelect() {
|
|
66
|
+
return this.#dismissOnSelect.value;
|
|
67
|
+
}
|
|
68
|
+
set dismissOnSelect(v) {
|
|
69
|
+
this.#dismissOnSelect.value = v;
|
|
70
|
+
}
|
|
23
71
|
focus(options) {
|
|
24
72
|
this.querySelector("input")?.focus(options);
|
|
25
73
|
}
|
|
74
|
+
validation(_meta, value) {
|
|
75
|
+
if (typeof value !== "string" || !isIsoDate(value))
|
|
76
|
+
return undefined;
|
|
77
|
+
const min = this.#min.value;
|
|
78
|
+
const max = this.#max.value;
|
|
79
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
80
|
+
return "The earliest date is after the latest date.";
|
|
81
|
+
if (min !== undefined && value < min)
|
|
82
|
+
return `Choose ${formatDate(min)} or later.`;
|
|
83
|
+
if (max !== undefined && value > max)
|
|
84
|
+
return `Choose ${formatDate(max)} or earlier.`;
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
26
87
|
control(c) {
|
|
27
88
|
const value = typeof c.value === "string" ? c.value : undefined;
|
|
28
89
|
const readOnly = c.state.readOnly;
|
|
90
|
+
const disabled = !c.state.enabled;
|
|
29
91
|
const open = this.#calendar.open.value;
|
|
30
|
-
const text = this.#draft.value ?? (value === undefined ? "" : formatDate(value));
|
|
92
|
+
const text = this.#draft.value ?? (value === undefined ? "" : formatDate(value, this.#format.value));
|
|
93
|
+
const placeholder = this.#placeholder.value || (c.meta?.nullable ? "None" : "A date, like 7 Oct");
|
|
31
94
|
const button = `${c.id}-calendar`;
|
|
32
95
|
const close = (refocus) => {
|
|
33
96
|
this.#calendar.open.value = false;
|
|
@@ -37,8 +100,8 @@ export class DateField extends BoundField {
|
|
|
37
100
|
return html `<div class="aster-field aster-date" data-ring-owner style=${anchorStyle(c.id)}>
|
|
38
101
|
<input id=${c.id} .value=${live(text)}
|
|
39
102
|
aria-label=${this.labelled ? nothing : c.label}
|
|
40
|
-
placeholder=${
|
|
41
|
-
?readonly=${readOnly}
|
|
103
|
+
placeholder=${placeholder}
|
|
104
|
+
?readonly=${readOnly} ?disabled=${disabled}
|
|
42
105
|
aria-required=${c.state.required ? "true" : nothing}
|
|
43
106
|
aria-invalid=${c.invalid ? "true" : nothing}
|
|
44
107
|
aria-describedby=${c.describedBy ?? nothing}
|
|
@@ -53,6 +116,7 @@ export class DateField extends BoundField {
|
|
|
53
116
|
? nothing
|
|
54
117
|
: html `<button type="button" class="aster-date-button" id=${button} aria-label=${`Choose ${c.label}`}
|
|
55
118
|
aria-haspopup="dialog" aria-expanded=${open ? "true" : "false"} aria-controls=${`${c.id}-dialog`}
|
|
119
|
+
?disabled=${disabled}
|
|
56
120
|
@pointerdown=${(e) => e.currentTarget.focus()}
|
|
57
121
|
@mousedown=${(e) => e.preventDefault()}
|
|
58
122
|
@click=${() => (open ? close(false) : this.#open(value))}>
|
|
@@ -65,10 +129,14 @@ export class DateField extends BoundField {
|
|
|
65
129
|
value,
|
|
66
130
|
today: today(),
|
|
67
131
|
label: `Choose ${c.label}`,
|
|
132
|
+
min: this.#min.value,
|
|
133
|
+
max: this.#max.value,
|
|
68
134
|
choose: (date) => {
|
|
69
135
|
this.write(date);
|
|
70
|
-
|
|
71
|
-
|
|
136
|
+
if (this.#dismissOnSelect.peek()) {
|
|
137
|
+
close(false);
|
|
138
|
+
this.querySelector("input")?.focus();
|
|
139
|
+
}
|
|
72
140
|
},
|
|
73
141
|
dismiss: close,
|
|
74
142
|
})}`;
|
package/dist/date/dates.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export declare function iso(year: number, month: number, day: number): string | undefined;
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
2
|
+
/** How a date is written in a field's input. */
|
|
3
|
+
export type DateFormat = "medium" | "short" | "iso";
|
|
4
|
+
/**
|
|
5
|
+
* A date as written: "7 Oct 2026" (`medium`, the default), "7 Oct 26"
|
|
6
|
+
* (`short`) or `2026-10-07` (`iso`). Other text unchanged. Month names are
|
|
7
|
+
* ours, the same in every engine.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatDate(value: string, format?: DateFormat): string;
|
|
10
|
+
/** Whether text is a real `YYYY-MM-DD` calendar date. */
|
|
11
|
+
export declare function isIsoDate(text: string): boolean;
|
|
4
12
|
/**
|
|
5
13
|
* A typed date: `2026-10-07`, `7/10/2026` (day first), `7 Oct 2026`,
|
|
6
14
|
* `Oct 7 2026`, or `7 Oct` (this year). `undefined` when it isn't one.
|
package/dist/date/dates.js
CHANGED
|
@@ -7,10 +7,30 @@ export function iso(year, month, day) {
|
|
|
7
7
|
return undefined;
|
|
8
8
|
return d.toISOString().slice(0, 10);
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* A date as written: "7 Oct 2026" (`medium`, the default), "7 Oct 26"
|
|
12
|
+
* (`short`) or `2026-10-07` (`iso`). Other text unchanged. Month names are
|
|
13
|
+
* ours, the same in every engine.
|
|
14
|
+
*/
|
|
15
|
+
export function formatDate(value, format = "medium") {
|
|
12
16
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
13
|
-
|
|
17
|
+
if (!m)
|
|
18
|
+
return value;
|
|
19
|
+
switch (format) {
|
|
20
|
+
case "iso":
|
|
21
|
+
return value;
|
|
22
|
+
case "short":
|
|
23
|
+
return `${Number(m[3])} ${SHORT[Number(m[2]) - 1]} ${m[1].slice(2)}`;
|
|
24
|
+
case "medium":
|
|
25
|
+
return `${Number(m[3])} ${SHORT[Number(m[2]) - 1]} ${m[1]}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Whether text is a real `YYYY-MM-DD` calendar date. */
|
|
29
|
+
export function isIsoDate(text) {
|
|
30
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(text))
|
|
31
|
+
return false;
|
|
32
|
+
const [y, m, d] = text.split("-").map(Number);
|
|
33
|
+
return iso(y, m, d) === text;
|
|
14
34
|
}
|
|
15
35
|
/**
|
|
16
36
|
* A typed date: `2026-10-07`, `7/10/2026` (day first), `7 Oct 2026`,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** A header appears when the dialog has a title, unless `header` says otherwise. */
|
|
2
|
+
export declare function dialogHeaderShown(title: string | undefined, header: boolean | undefined): boolean;
|
|
3
|
+
/** The close control belongs to the header and is off by default, preserving today's dialogs. */
|
|
4
|
+
export declare function dialogCloseShown(header: boolean, showCloseButton: boolean | undefined): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A header appears when the dialog has a title, unless `header` says otherwise. */
|
|
2
|
+
export function dialogHeaderShown(title, header) {
|
|
3
|
+
return header ?? (title !== undefined && title !== "");
|
|
4
|
+
}
|
|
5
|
+
/** The close control belongs to the header and is off by default, preserving today's dialogs. */
|
|
6
|
+
export function dialogCloseShown(header, showCloseButton) {
|
|
7
|
+
return header && showCloseButton === true;
|
|
8
|
+
}
|