@zeyos/zx 2.1.0 → 2.2.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/CHANGELOG.md +39 -0
- package/README.md +15 -0
- package/dist/types/compat/base.d.ts +156 -0
- package/dist/types/compat/globals.d.ts +20 -0
- package/dist/types/compat/index.d.ts +128 -0
- package/dist/types/compat/map/basic.d.ts +175 -0
- package/dist/types/compat/map/data.d.ts +102 -0
- package/dist/types/compat/map/date-time.d.ts +80 -0
- package/dist/types/compat/map/factory.d.ts +9 -0
- package/dist/types/compat/map/form.d.ts +98 -0
- package/dist/types/compat/map/helpers.d.ts +12 -0
- package/dist/types/compat/map/http.d.ts +36 -0
- package/dist/types/compat/map/layout.d.ts +106 -0
- package/dist/types/compat/map/options.d.ts +80 -0
- package/dist/types/compat/map/select.d.ts +95 -0
- package/dist/types/compat/map/stubs.d.ts +32 -0
- package/dist/types/compat/map/utilities.d.ts +37 -0
- package/dist/types/compat-entry.d.ts +5 -0
- package/dist/types/components/badge/badge.d.ts +46 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +200 -0
- package/dist/types/components/button/button.d.ts +42 -0
- package/dist/types/components/check-button/check-button.d.ts +98 -0
- package/dist/types/components/checklist/checklist.d.ts +143 -0
- package/dist/types/components/checklist/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +130 -0
- package/dist/types/components/copy/copy-button.d.ts +48 -0
- package/dist/types/components/copy/copy-input.d.ts +91 -0
- package/dist/types/components/data-filter/data-filter.d.ts +250 -0
- package/dist/types/components/data-filter/filter-core.d.ts +52 -0
- package/dist/types/components/date-picker/date-picker.d.ts +137 -0
- package/dist/types/components/date-picker/date-range-picker.d.ts +350 -0
- package/dist/types/components/date-picker/field-adapter.d.ts +6 -0
- package/dist/types/components/date-picker/month-picker.d.ts +75 -0
- package/dist/types/components/date-picker/range-field-adapter.d.ts +7 -0
- package/dist/types/components/date-picker/time-picker.d.ts +127 -0
- package/dist/types/components/datebox/date-range-box.d.ts +218 -0
- package/dist/types/components/datebox/datebox.d.ts +160 -0
- package/dist/types/components/datebox/field-adapter.d.ts +5 -0
- package/dist/types/components/dialog/dialog.d.ts +284 -0
- package/dist/types/components/dropdown/dropdown.d.ts +118 -0
- package/dist/types/components/empty-state/empty-state.d.ts +38 -0
- package/dist/types/components/field/field.d.ts +231 -0
- package/dist/types/components/field-upload/field-adapter.d.ts +5 -0
- package/dist/types/components/field-upload/field-upload.d.ts +213 -0
- package/dist/types/components/fieldset/fieldset.d.ts +125 -0
- package/dist/types/components/finder/finder.d.ts +252 -0
- package/dist/types/components/form/form.d.ts +194 -0
- package/dist/types/components/groupbox/groupbox.d.ts +89 -0
- package/dist/types/components/layout/layout.d.ts +132 -0
- package/dist/types/components/loading/inline-loading.d.ts +86 -0
- package/dist/types/components/loading/progress-bar.d.ts +168 -0
- package/dist/types/components/loading/spinner.d.ts +34 -0
- package/dist/types/components/master-panel/master-panel.d.ts +112 -0
- package/dist/types/components/menu-button/menu-button.d.ts +142 -0
- package/dist/types/components/message/message.d.ts +212 -0
- package/dist/types/components/modal/modal.d.ts +105 -0
- package/dist/types/components/multi-value-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +66 -0
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +229 -0
- package/dist/types/components/number-field/field-adapter.d.ts +5 -0
- package/dist/types/components/number-field/number-field.d.ts +292 -0
- package/dist/types/components/pagination/pagination.d.ts +267 -0
- package/dist/types/components/panel/panel.d.ts +194 -0
- package/dist/types/components/permission/permission.d.ts +93 -0
- package/dist/types/components/rating/field-adapter.d.ts +5 -0
- package/dist/types/components/rating/rating.d.ts +213 -0
- package/dist/types/components/search/search.d.ts +98 -0
- package/dist/types/components/select/field-adapter.d.ts +5 -0
- package/dist/types/components/select/filter.d.ts +11 -0
- package/dist/types/components/select/select.d.ts +235 -0
- package/dist/types/components/skeleton/skeleton.d.ts +97 -0
- package/dist/types/components/slider/field-adapter.d.ts +5 -0
- package/dist/types/components/slider/slider.d.ts +216 -0
- package/dist/types/components/split-view/split-view.d.ts +335 -0
- package/dist/types/components/stepper/stepper.d.ts +314 -0
- package/dist/types/components/stepper/steps.d.ts +131 -0
- package/dist/types/components/tabbox/tabbox.d.ts +230 -0
- package/dist/types/components/table/sort.d.ts +49 -0
- package/dist/types/components/table/table.d.ts +872 -0
- package/dist/types/components/tag-picker/field-adapter.d.ts +5 -0
- package/dist/types/components/tag-picker/tag-picker.d.ts +375 -0
- package/dist/types/components/timebox/field-adapter.d.ts +5 -0
- package/dist/types/components/timebox/timebox.d.ts +121 -0
- package/dist/types/components/toggle/field-adapter.d.ts +5 -0
- package/dist/types/components/toggle/toggle.d.ts +94 -0
- package/dist/types/components/toolbar/toolbar.d.ts +230 -0
- package/dist/types/components/tooltip/tooltip.d.ts +183 -0
- package/dist/types/components/tree/hierarchy.d.ts +113 -0
- package/dist/types/components/tree/tree.d.ts +383 -0
- package/dist/types/components/truncate/truncate.d.ts +61 -0
- package/dist/types/components/value-list/field-adapter.d.ts +5 -0
- package/dist/types/components/value-list/value-list.d.ts +143 -0
- package/dist/types/core/breakpoint.d.ts +68 -0
- package/dist/types/core/component.d.ts +115 -0
- package/dist/types/core/date.d.ts +50 -0
- package/dist/types/core/dom.d.ts +86 -0
- package/dist/types/core/export.d.ts +70 -0
- package/dist/types/core/fontawesome.d.ts +107 -0
- package/dist/types/core/format.d.ts +107 -0
- package/dist/types/core/http.d.ts +122 -0
- package/dist/types/core/i18n.d.ts +35 -0
- package/dist/types/core/icons.d.ts +137 -0
- package/dist/types/core/keyboard.d.ts +72 -0
- package/dist/types/core/position.d.ts +45 -0
- package/dist/types/core/storage.d.ts +46 -0
- package/dist/types/core/util.d.ts +117 -0
- package/dist/types/elements/define.d.ts +73 -0
- package/dist/types/elements/reflect.d.ts +27 -0
- package/dist/types/index.d.ts +79 -0
- package/dist/types/zeyos/connect.d.ts +65 -0
- package/dist/types/zeyos/form.d.ts +140 -0
- package/dist/types/zeyos/icons.d.ts +104 -0
- package/dist/types/zeyos/index.d.ts +12 -0
- package/dist/types/zeyos/modules.d.ts +110 -0
- package/dist/types/zeyos/query.d.ts +157 -0
- package/dist/types/zeyos/schema.d.ts +79 -0
- package/dist/types/zeyos/select.d.ts +167 -0
- package/dist/types/zeyos/table.d.ts +205 -0
- package/dist/zx-compat.esm.js +362 -33
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +359 -67
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +342 -43
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +68 -6
- package/dist/zx.esm.js +346 -67
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +346 -67
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +48 -2
- package/docs/llms.md +28 -0
- package/package.json +37 -9
- package/src/compat/base.js +291 -0
- package/src/compat/globals.js +165 -0
- package/src/compat/index.js +84 -0
- package/src/compat/map/basic.js +464 -0
- package/src/compat/map/data.js +232 -0
- package/src/compat/map/date-time.js +177 -0
- package/src/compat/map/factory.js +45 -0
- package/src/compat/map/form.js +330 -0
- package/src/compat/map/helpers.js +46 -0
- package/src/compat/map/http.js +117 -0
- package/src/compat/map/layout.js +256 -0
- package/src/compat/map/options.js +267 -0
- package/src/compat/map/select.js +198 -0
- package/src/compat/map/stubs.js +26 -0
- package/src/compat/map/utilities.js +113 -0
- package/src/compat-entry.js +11 -0
- package/src/components/badge/badge.css +147 -0
- package/src/components/badge/badge.js +92 -0
- package/src/components/breadcrumb/breadcrumb.css +105 -0
- package/src/components/breadcrumb/breadcrumb.js +331 -0
- package/src/components/button/button.css +104 -0
- package/src/components/button/button.js +76 -0
- package/src/components/check-button/check-button.css +66 -0
- package/src/components/check-button/check-button.js +167 -0
- package/src/components/checklist/checklist.css +65 -0
- package/src/components/checklist/checklist.js +282 -0
- package/src/components/checklist/field-adapter.js +46 -0
- package/src/components/context-menu/context-menu.css +54 -0
- package/src/components/context-menu/context-menu.js +334 -0
- package/src/components/copy/copy-button.css +27 -0
- package/src/components/copy/copy-button.js +78 -0
- package/src/components/copy/copy-input.css +59 -0
- package/src/components/copy/copy-input.js +123 -0
- package/src/components/data-filter/data-filter.css +53 -0
- package/src/components/data-filter/data-filter.js +400 -0
- package/src/components/data-filter/filter-core.js +128 -0
- package/src/components/date-picker/date-picker.css +181 -0
- package/src/components/date-picker/date-picker.js +500 -0
- package/src/components/date-picker/date-range-picker.css +264 -0
- package/src/components/date-picker/date-range-picker.js +990 -0
- package/src/components/date-picker/field-adapter.js +36 -0
- package/src/components/date-picker/month-picker.js +223 -0
- package/src/components/date-picker/range-field-adapter.js +31 -0
- package/src/components/date-picker/time-picker.css +191 -0
- package/src/components/date-picker/time-picker.js +406 -0
- package/src/components/datebox/date-range-box.css +128 -0
- package/src/components/datebox/date-range-box.js +492 -0
- package/src/components/datebox/datebox.css +108 -0
- package/src/components/datebox/datebox.js +358 -0
- package/src/components/datebox/field-adapter.js +34 -0
- package/src/components/dialog/dialog.css +74 -0
- package/src/components/dialog/dialog.js +450 -0
- package/src/components/dropdown/dropdown.css +24 -0
- package/src/components/dropdown/dropdown.js +288 -0
- package/src/components/empty-state/empty-state.css +80 -0
- package/src/components/empty-state/empty-state.js +92 -0
- package/src/components/field/field.css +127 -0
- package/src/components/field/field.js +605 -0
- package/src/components/field-upload/field-adapter.js +21 -0
- package/src/components/field-upload/field-upload.css +108 -0
- package/src/components/field-upload/field-upload.js +475 -0
- package/src/components/fieldset/fieldset.css +37 -0
- package/src/components/fieldset/fieldset.js +307 -0
- package/src/components/finder/finder.css +98 -0
- package/src/components/finder/finder.js +509 -0
- package/src/components/form/form.css +32 -0
- package/src/components/form/form.js +302 -0
- package/src/components/groupbox/groupbox.css +51 -0
- package/src/components/groupbox/groupbox.js +199 -0
- package/src/components/layout/layout.css +117 -0
- package/src/components/layout/layout.js +136 -0
- package/src/components/loading/inline-loading.css +34 -0
- package/src/components/loading/inline-loading.js +140 -0
- package/src/components/loading/progress-bar.css +115 -0
- package/src/components/loading/progress-bar.js +259 -0
- package/src/components/loading/spinner.css +58 -0
- package/src/components/loading/spinner.js +57 -0
- package/src/components/master-panel/master-panel.css +51 -0
- package/src/components/master-panel/master-panel.js +203 -0
- package/src/components/menu-button/menu-button.css +49 -0
- package/src/components/menu-button/menu-button.js +294 -0
- package/src/components/message/message.css +122 -0
- package/src/components/message/message.js +499 -0
- package/src/components/modal/modal.css +47 -0
- package/src/components/modal/modal.js +203 -0
- package/src/components/multi-value-editor/field-adapter.js +44 -0
- package/src/components/multi-value-editor/multi-value-editor.css +56 -0
- package/src/components/multi-value-editor/multi-value-editor.js +170 -0
- package/src/components/navigation-bar/navigation-bar.css +130 -0
- package/src/components/navigation-bar/navigation-bar.js +450 -0
- package/src/components/number-field/field-adapter.js +40 -0
- package/src/components/number-field/number-field.css +77 -0
- package/src/components/number-field/number-field.js +545 -0
- package/src/components/pagination/pagination.css +95 -0
- package/src/components/pagination/pagination.js +481 -0
- package/src/components/panel/panel.css +123 -0
- package/src/components/panel/panel.js +336 -0
- package/src/components/permission/permission.css +47 -0
- package/src/components/permission/permission.js +198 -0
- package/src/components/rating/field-adapter.js +34 -0
- package/src/components/rating/rating.css +97 -0
- package/src/components/rating/rating.js +395 -0
- package/src/components/search/search.css +70 -0
- package/src/components/search/search.js +193 -0
- package/src/components/select/field-adapter.js +24 -0
- package/src/components/select/filter.js +39 -0
- package/src/components/select/select.css +180 -0
- package/src/components/select/select.js +663 -0
- package/src/components/skeleton/skeleton.css +76 -0
- package/src/components/skeleton/skeleton.js +139 -0
- package/src/components/slider/field-adapter.js +36 -0
- package/src/components/slider/slider.css +181 -0
- package/src/components/slider/slider.js +399 -0
- package/src/components/split-view/split-view.css +139 -0
- package/src/components/split-view/split-view.js +806 -0
- package/src/components/stepper/stepper.css +234 -0
- package/src/components/stepper/stepper.js +459 -0
- package/src/components/stepper/steps.js +131 -0
- package/src/components/tabbox/tabbox.css +281 -0
- package/src/components/tabbox/tabbox.js +484 -0
- package/src/components/table/sort.js +85 -0
- package/src/components/table/table.css +364 -0
- package/src/components/table/table.js +1693 -0
- package/src/components/tag-picker/field-adapter.js +35 -0
- package/src/components/tag-picker/tag-picker.css +169 -0
- package/src/components/tag-picker/tag-picker.js +837 -0
- package/src/components/timebox/field-adapter.js +24 -0
- package/src/components/timebox/timebox.css +51 -0
- package/src/components/timebox/timebox.js +276 -0
- package/src/components/toggle/field-adapter.js +24 -0
- package/src/components/toggle/toggle.css +66 -0
- package/src/components/toggle/toggle.js +150 -0
- package/src/components/toolbar/toolbar.css +81 -0
- package/src/components/toolbar/toolbar.js +511 -0
- package/src/components/tooltip/tooltip.css +36 -0
- package/src/components/tooltip/tooltip.js +420 -0
- package/src/components/tree/hierarchy.js +151 -0
- package/src/components/tree/tree.css +99 -0
- package/src/components/tree/tree.js +784 -0
- package/src/components/truncate/truncate.css +20 -0
- package/src/components/truncate/truncate.js +92 -0
- package/src/components/value-list/field-adapter.js +24 -0
- package/src/components/value-list/value-list.css +108 -0
- package/src/components/value-list/value-list.js +336 -0
- package/src/core/breakpoint.js +150 -0
- package/src/core/component.js +292 -0
- package/src/core/date.js +216 -0
- package/src/core/dom.js +260 -0
- package/src/core/export.js +204 -0
- package/src/core/fontawesome.js +177 -0
- package/src/core/format.js +283 -0
- package/src/core/http.js +246 -0
- package/src/core/i18n.js +83 -0
- package/src/core/icons.js +283 -0
- package/src/core/keyboard.js +240 -0
- package/src/core/position.js +177 -0
- package/src/core/storage.js +175 -0
- package/src/core/util.js +336 -0
- package/src/elements/define.js +715 -0
- package/src/elements/reflect.js +79 -0
- package/src/index.js +131 -0
- package/src/zeyos/connect.js +142 -0
- package/src/zeyos/form.js +254 -0
- package/src/zeyos/icons.js +92 -0
- package/src/zeyos/index.js +21 -0
- package/src/zeyos/modules.js +269 -0
- package/src/zeyos/query.js +199 -0
- package/src/zeyos/schema.js +474 -0
- package/src/zeyos/select.js +175 -0
- package/src/zeyos/table.js +272 -0
- package/styles/base.css +124 -0
- package/styles/icons.css +34 -0
- package/styles/tokens/dark.css +84 -0
- package/styles/tokens/density.css +9 -0
- package/styles/tokens/global.css +80 -0
- package/styles/tokens/modules.css +109 -0
- package/styles/tokens/semantic.css +49 -0
- package/styles/zx.css +61 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribute value types supported by the custom-element wrappers.
|
|
3
|
+
* @typedef {'string'|'number'|'boolean'|'json'} AttributeType
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Sentinel returned when an attribute value cannot be safely applied. */
|
|
7
|
+
export const INVALID_ATTRIBUTE = Symbol('invalid attribute');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Coerces a serialized attribute into a component option value.
|
|
11
|
+
* Missing boolean attributes are false; other missing attributes are undefined.
|
|
12
|
+
*
|
|
13
|
+
* @param {string|null} value Attribute text, or null when the attribute is absent.
|
|
14
|
+
* @param {AttributeType} [type='string'] Requested option type.
|
|
15
|
+
* @param {(message: string) => void} [warn=console.warn] Invalid-value reporter.
|
|
16
|
+
* @returns {string|number|boolean|unknown|undefined|typeof INVALID_ATTRIBUTE}
|
|
17
|
+
*/
|
|
18
|
+
export function coerceAttribute(value, type = 'string', warn = console.warn) {
|
|
19
|
+
assertType(type);
|
|
20
|
+
if (type === 'boolean') return value !== null;
|
|
21
|
+
if (value === null) return undefined;
|
|
22
|
+
if (type === 'string') return value;
|
|
23
|
+
|
|
24
|
+
if (type === 'number') {
|
|
25
|
+
const number = Number(value);
|
|
26
|
+
if (Number.isFinite(number)) return number;
|
|
27
|
+
warn(`Ignoring invalid number attribute: ${JSON.stringify(value)}`);
|
|
28
|
+
return INVALID_ATTRIBUTE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(value);
|
|
33
|
+
} catch {
|
|
34
|
+
warn(`Ignoring invalid JSON attribute: ${JSON.stringify(value)}`);
|
|
35
|
+
return INVALID_ATTRIBUTE;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Serializes a property value for attribute reflection.
|
|
41
|
+
* A null return value means the corresponding attribute should be removed.
|
|
42
|
+
*
|
|
43
|
+
* @param {unknown} value Property value.
|
|
44
|
+
* @param {AttributeType} [type='string'] Attribute representation.
|
|
45
|
+
* @param {(message: string) => void} [warn=console.warn] Invalid-value reporter.
|
|
46
|
+
* @returns {string|null|typeof INVALID_ATTRIBUTE}
|
|
47
|
+
*/
|
|
48
|
+
export function serializeAttribute(value, type = 'string', warn = console.warn) {
|
|
49
|
+
assertType(type);
|
|
50
|
+
if (type === 'boolean') return value ? '' : null;
|
|
51
|
+
if (value === null || value === undefined) return null;
|
|
52
|
+
|
|
53
|
+
if (type === 'number') {
|
|
54
|
+
const number = Number(value);
|
|
55
|
+
if (Number.isFinite(number)) return String(number);
|
|
56
|
+
warn(`Ignoring invalid number property: ${String(value)}`);
|
|
57
|
+
return INVALID_ATTRIBUTE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (type === 'json') {
|
|
61
|
+
try {
|
|
62
|
+
const serialized = JSON.stringify(value);
|
|
63
|
+
if (serialized !== undefined) return serialized;
|
|
64
|
+
} catch {
|
|
65
|
+
// Report through the common warning below.
|
|
66
|
+
}
|
|
67
|
+
warn('Ignoring property that cannot be serialized as JSON');
|
|
68
|
+
return INVALID_ATTRIBUTE;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return String(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {unknown} type @returns {asserts type is AttributeType} */
|
|
75
|
+
function assertType(type) {
|
|
76
|
+
if (!['string', 'number', 'boolean', 'json'].includes(String(type))) {
|
|
77
|
+
throw new TypeError(`Unknown reflected attribute type: ${String(type)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zx public API.
|
|
3
|
+
* @module zx
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { registerChecklistFieldAdapter } from './components/checklist/field-adapter.js';
|
|
7
|
+
import { registerDatePickerFieldAdapters } from './components/date-picker/field-adapter.js';
|
|
8
|
+
import { registerDateRangeFieldAdapter } from './components/date-picker/range-field-adapter.js';
|
|
9
|
+
import { registerDateboxFieldAdapters } from './components/datebox/field-adapter.js';
|
|
10
|
+
import { registerFieldUploadAdapter } from './components/field-upload/field-adapter.js';
|
|
11
|
+
import { registerMultiValueEditorFieldAdapter } from './components/multi-value-editor/field-adapter.js';
|
|
12
|
+
import { registerNumberFieldAdapter } from './components/number-field/field-adapter.js';
|
|
13
|
+
import { registerRatingFieldAdapter } from './components/rating/field-adapter.js';
|
|
14
|
+
import { registerSelectFieldAdapter } from './components/select/field-adapter.js';
|
|
15
|
+
import { registerSliderFieldAdapter } from './components/slider/field-adapter.js';
|
|
16
|
+
import { registerTagPickerFieldAdapter } from './components/tag-picker/field-adapter.js';
|
|
17
|
+
import { registerTimeboxFieldAdapter } from './components/timebox/field-adapter.js';
|
|
18
|
+
import { registerToggleFieldAdapter } from './components/toggle/field-adapter.js';
|
|
19
|
+
import { registerValueListFieldAdapter } from './components/value-list/field-adapter.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers every component-backed Field adapter. Calling this function repeatedly is safe.
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
export function registerFieldAdapters() {
|
|
26
|
+
registerSelectFieldAdapter();
|
|
27
|
+
registerChecklistFieldAdapter();
|
|
28
|
+
registerDateboxFieldAdapters();
|
|
29
|
+
registerDatePickerFieldAdapters();
|
|
30
|
+
registerDateRangeFieldAdapter();
|
|
31
|
+
registerTimeboxFieldAdapter();
|
|
32
|
+
registerValueListFieldAdapter();
|
|
33
|
+
registerMultiValueEditorFieldAdapter();
|
|
34
|
+
registerFieldUploadAdapter();
|
|
35
|
+
registerToggleFieldAdapter();
|
|
36
|
+
registerNumberFieldAdapter();
|
|
37
|
+
registerRatingFieldAdapter();
|
|
38
|
+
registerTagPickerFieldAdapter();
|
|
39
|
+
registerSliderFieldAdapter();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
registerFieldAdapters();
|
|
43
|
+
|
|
44
|
+
export { Component } from './core/component.js';
|
|
45
|
+
export { h, htmlEscape, resolveElement, highlightMatch } from './core/dom.js';
|
|
46
|
+
export { position } from './core/position.js';
|
|
47
|
+
export { breakpoints, breakpointOf, matchBreakpoint, onBreakpoint } from './core/breakpoint.js';
|
|
48
|
+
export { Http, zeyosService, parseResult } from './core/http.js';
|
|
49
|
+
export { setTranslator, setLanguage, getLanguage, translate, printf } from './core/i18n.js';
|
|
50
|
+
export { focusTrap, rovingTabindex, typeahead } from './core/keyboard.js';
|
|
51
|
+
export {
|
|
52
|
+
formatDate, parseDate, clampDate, isSameDay, addDays, addMonths, getWeekStart
|
|
53
|
+
} from './core/date.js';
|
|
54
|
+
export {
|
|
55
|
+
icon, icons, iconNames, registerIcons,
|
|
56
|
+
configureIcons, getIconConfig, useFontAwesome, useBuiltinIcons, loadFontAwesome
|
|
57
|
+
} from './core/icons.js';
|
|
58
|
+
export {
|
|
59
|
+
faStyles, faFamilies, faNames, faIconClasses, parseIconSpec, kitUrl, loadFontAwesomeKit
|
|
60
|
+
} from './core/fontawesome.js';
|
|
61
|
+
export {
|
|
62
|
+
debounce, throttle, uid, deepMerge, isElement, clamp, toArray, groupBy, sortBy, uniqueBy,
|
|
63
|
+
escapeRegExp
|
|
64
|
+
} from './core/util.js';
|
|
65
|
+
export {
|
|
66
|
+
formatNumber, formatCurrency, formatPercent, formatFileSize, formatRelativeTime
|
|
67
|
+
} from './core/format.js';
|
|
68
|
+
export { storage } from './core/storage.js';
|
|
69
|
+
export { toCsv, downloadBlob, copyToClipboard } from './core/export.js';
|
|
70
|
+
export { defineElements } from './elements/define.js';
|
|
71
|
+
export { button, buttonGroup } from './components/button/button.js';
|
|
72
|
+
export { badge, badgeGroup } from './components/badge/badge.js';
|
|
73
|
+
export { CheckButton } from './components/check-button/check-button.js';
|
|
74
|
+
export { emptyState } from './components/empty-state/empty-state.js';
|
|
75
|
+
export { spinner } from './components/loading/spinner.js';
|
|
76
|
+
export { ProgressBar } from './components/loading/progress-bar.js';
|
|
77
|
+
export { InlineLoading } from './components/loading/inline-loading.js';
|
|
78
|
+
export { skeleton, skeletonText, skeletonTable } from './components/skeleton/skeleton.js';
|
|
79
|
+
export { stack, grid, aspect } from './components/layout/layout.js';
|
|
80
|
+
export { truncate, isTruncated } from './components/truncate/truncate.js';
|
|
81
|
+
export { copyButton } from './components/copy/copy-button.js';
|
|
82
|
+
export { CopyInput } from './components/copy/copy-input.js';
|
|
83
|
+
export { Toolbar } from './components/toolbar/toolbar.js';
|
|
84
|
+
export { Toggle } from './components/toggle/toggle.js';
|
|
85
|
+
export { Groupbox } from './components/groupbox/groupbox.js';
|
|
86
|
+
export { Panel } from './components/panel/panel.js';
|
|
87
|
+
export { MasterPanel } from './components/master-panel/master-panel.js';
|
|
88
|
+
export {
|
|
89
|
+
SplitView, clampSize, resolveSize, snapSize
|
|
90
|
+
} from './components/split-view/split-view.js';
|
|
91
|
+
export { Tabbox } from './components/tabbox/tabbox.js';
|
|
92
|
+
export { NavigationBar } from './components/navigation-bar/navigation-bar.js';
|
|
93
|
+
export { Stepper } from './components/stepper/stepper.js';
|
|
94
|
+
export { Breadcrumb } from './components/breadcrumb/breadcrumb.js';
|
|
95
|
+
export { Pagination, paginationRange } from './components/pagination/pagination.js';
|
|
96
|
+
export { Search } from './components/search/search.js';
|
|
97
|
+
export { Message } from './components/message/message.js';
|
|
98
|
+
export { Modal } from './components/modal/modal.js';
|
|
99
|
+
export { Dialog } from './components/dialog/dialog.js';
|
|
100
|
+
export { Dropdown } from './components/dropdown/dropdown.js';
|
|
101
|
+
export { Tooltip, tooltip, describe } from './components/tooltip/tooltip.js';
|
|
102
|
+
export { MenuButton } from './components/menu-button/menu-button.js';
|
|
103
|
+
export { ContextMenu } from './components/context-menu/context-menu.js';
|
|
104
|
+
export { Select } from './components/select/select.js';
|
|
105
|
+
export { Checklist } from './components/checklist/checklist.js';
|
|
106
|
+
export { TagPicker } from './components/tag-picker/tag-picker.js';
|
|
107
|
+
export { NumberField, parseNumber, snapNumber } from './components/number-field/number-field.js';
|
|
108
|
+
export { Rating } from './components/rating/rating.js';
|
|
109
|
+
export { Slider, stepPrecision } from './components/slider/slider.js';
|
|
110
|
+
export { Permission } from './components/permission/permission.js';
|
|
111
|
+
export { DatePicker } from './components/date-picker/date-picker.js';
|
|
112
|
+
export { MonthPicker } from './components/date-picker/month-picker.js';
|
|
113
|
+
export { TimePicker } from './components/date-picker/time-picker.js';
|
|
114
|
+
export { Datebox, DateTimeBox } from './components/datebox/datebox.js';
|
|
115
|
+
export {
|
|
116
|
+
DateRangePicker, clampRange, defaultDateRangePresets, normalizeRange, rangeNights, rangeStateOf
|
|
117
|
+
} from './components/date-picker/date-range-picker.js';
|
|
118
|
+
export {
|
|
119
|
+
DateRangeBox, formatRangeText, parseRangeText
|
|
120
|
+
} from './components/datebox/date-range-box.js';
|
|
121
|
+
export { Timebox, splitTime, joinTime } from './components/timebox/timebox.js';
|
|
122
|
+
export { Table } from './components/table/table.js';
|
|
123
|
+
export { TreeView } from './components/tree/tree.js';
|
|
124
|
+
export { Finder } from './components/finder/finder.js';
|
|
125
|
+
export { DataFilter } from './components/data-filter/data-filter.js';
|
|
126
|
+
export { Field } from './components/field/field.js';
|
|
127
|
+
export { Fieldset } from './components/fieldset/fieldset.js';
|
|
128
|
+
export { Form } from './components/form/form.js';
|
|
129
|
+
export { ValueList } from './components/value-list/value-list.js';
|
|
130
|
+
export { MultiValueEditor } from './components/multi-value-editor/multi-value-editor.js';
|
|
131
|
+
export { FieldUpload } from './components/field-upload/field-upload.js';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Message, setLanguage } from '../index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} ConnectOptions
|
|
5
|
+
* @property {(error: unknown, context?: {operation?: string, resource?: string}) => unknown} [onError] Error reporter.
|
|
6
|
+
* @property {string} [locale] BCP 47 locale applied to Zx i18n/date formatting.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} ZeyosFacade
|
|
10
|
+
* @property {Record<string, any>} client Injected client instance.
|
|
11
|
+
* @property {(resource: string, params?: Record<string, unknown>) => Promise<unknown>} list List a resource.
|
|
12
|
+
* @property {(resource: string, idOrParams: unknown, params?: Record<string, unknown>) => Promise<unknown>} get Fetch one record.
|
|
13
|
+
* @property {(resource: string, data: Record<string, unknown>) => Promise<unknown>} create Create one record.
|
|
14
|
+
* @property {(resource: string, idOrInput: unknown, data?: Record<string, unknown>) => Promise<unknown>} update Update one record.
|
|
15
|
+
* @property {(error: unknown, context?: {operation?: string, resource?: string}) => unknown} reportError Report an API error.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Connects an existing injected `@zeyos/client` instance to Zx infrastructure. This function never
|
|
20
|
+
* creates or imports the client package. It applies a supplied/derivable locale and returns thin
|
|
21
|
+
* operation helpers that report and rethrow failures.
|
|
22
|
+
* @param {Record<string, any>} client Injected ZeyOS client instance.
|
|
23
|
+
* @param {ConnectOptions} [options={}] Wiring options.
|
|
24
|
+
* @returns {ZeyosFacade} Bound facade.
|
|
25
|
+
*/
|
|
26
|
+
export function connect(client, { onError, locale } = {}) {
|
|
27
|
+
if (!client || typeof client !== 'object' || !client.api || typeof client.api !== 'object') {
|
|
28
|
+
throw new TypeError('connect() requires a ZeyOS client with an api namespace');
|
|
29
|
+
}
|
|
30
|
+
if (onError != null && typeof onError !== 'function') throw new TypeError('onError must be a function');
|
|
31
|
+
|
|
32
|
+
const language = locale ?? deriveLocale(client);
|
|
33
|
+
if (language) setLanguage(String(language));
|
|
34
|
+
|
|
35
|
+
const reportError = (error, context) => {
|
|
36
|
+
if (onError) return onError(error, context);
|
|
37
|
+
return Message.error(errorMessage(error));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const invoke = async (action, resource, input) => {
|
|
41
|
+
const operation = operationFor(client, action, resource);
|
|
42
|
+
try {
|
|
43
|
+
return await client.api[operation](input);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
try { reportError(error, { operation, resource }); } catch { /* Preserve the API failure. */ }
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
client,
|
|
52
|
+
list: (resource, params = {}) => invoke('list', resource, params),
|
|
53
|
+
get: (resource, idOrParams, params = {}) => invoke('get', resource,
|
|
54
|
+
isRecord(idOrParams) ? idOrParams : { ID: idOrParams, ...params }),
|
|
55
|
+
create: (resource, data) => invoke('create', resource, data),
|
|
56
|
+
update: (resource, idOrInput, data) => invoke('update', resource,
|
|
57
|
+
data === undefined && isRecord(idOrInput) ? idOrInput : { ID: idOrInput, body: data ?? {} }),
|
|
58
|
+
reportError
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {Record<string, any>} client @returns {unknown} */
|
|
63
|
+
function deriveLocale(client) {
|
|
64
|
+
return client.locale ?? client.language ?? client.config?.locale ?? client.metadata?.locale ?? null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {Record<string, any>} client @param {'list'|'get'|'create'|'update'} action @param {string} resource @returns {string} */
|
|
68
|
+
function operationFor(client, action, resource) {
|
|
69
|
+
const name = String(resource ?? '').trim();
|
|
70
|
+
if (!name) throw new TypeError('ZeyOS resource must not be empty');
|
|
71
|
+
const resourceOperations = typeof client.schema?.operations === 'function'
|
|
72
|
+
? client.schema.operations(name)
|
|
73
|
+
: [];
|
|
74
|
+
if (Array.isArray(resourceOperations) && resourceOperations.length) {
|
|
75
|
+
const exactResourceMatches = resourceOperations
|
|
76
|
+
.filter((operation) => typeof operation === 'string' && operation.toLowerCase().startsWith(action))
|
|
77
|
+
.sort((left, right) => left.length - right.length);
|
|
78
|
+
if (exactResourceMatches.length) return exactResourceMatches[0];
|
|
79
|
+
}
|
|
80
|
+
const known = Array.isArray(resourceOperations) && resourceOperations.length
|
|
81
|
+
? resourceOperations
|
|
82
|
+
: (typeof client.schema?.operationIds === 'function'
|
|
83
|
+
? client.schema.operationIds()
|
|
84
|
+
: Object.keys(client.api));
|
|
85
|
+
if (known.includes(name) && name.toLowerCase().startsWith(action)) return name;
|
|
86
|
+
|
|
87
|
+
const normalizedResource = normalizeResource(name);
|
|
88
|
+
const singular = singularize(normalizedResource);
|
|
89
|
+
const matches = known.filter((operation) => {
|
|
90
|
+
if (typeof operation !== 'string' || !operation.toLowerCase().startsWith(action)) return false;
|
|
91
|
+
const suffix = normalizeResource(operation.slice(action.length));
|
|
92
|
+
return suffix === normalizedResource || suffix === singular || singularize(suffix) === singular;
|
|
93
|
+
});
|
|
94
|
+
if (matches.length) return matches.sort((left, right) => left.length - right.length)[0];
|
|
95
|
+
|
|
96
|
+
const candidates = action === 'list'
|
|
97
|
+
? [`list${pascalCase(name)}`]
|
|
98
|
+
: [`${action}${pascalCase(singularizeName(name))}`, `${action}${pascalCase(name)}`];
|
|
99
|
+
const apiKeys = new Set(Object.keys(client.api));
|
|
100
|
+
const fallback = candidates.find((candidate) => apiKeys.has(candidate));
|
|
101
|
+
if (fallback) return fallback;
|
|
102
|
+
throw new RangeError(`No ${action} operation found for ZeyOS resource "${name}"`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {unknown} value @returns {boolean} */
|
|
106
|
+
function isRecord(value) {
|
|
107
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @param {unknown} error @returns {string} */
|
|
111
|
+
function errorMessage(error) {
|
|
112
|
+
if (error instanceof Error && error.message) return error.message;
|
|
113
|
+
if (error && typeof error === 'object' && typeof error.message === 'string') return error.message;
|
|
114
|
+
return String(error ?? 'ZeyOS request failed');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @param {string} value @returns {string} */
|
|
118
|
+
function normalizeResource(value) {
|
|
119
|
+
return String(value).replace(/[^a-z\d]/gi, '').toLowerCase();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @param {string} value @returns {string} */
|
|
123
|
+
function singularize(value) {
|
|
124
|
+
if (value.endsWith('ies')) return `${value.slice(0, -3)}y`;
|
|
125
|
+
if (value.endsWith('sses')) return value.slice(0, -2);
|
|
126
|
+
if (value.endsWith('s') && !value.endsWith('ss')) return value.slice(0, -1);
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @param {string} value @returns {string} */
|
|
131
|
+
function singularizeName(value) {
|
|
132
|
+
const source = String(value);
|
|
133
|
+
if (/ies$/i.test(source)) return `${source.slice(0, -3)}y`;
|
|
134
|
+
if (/s$/i.test(source) && !/ss$/i.test(source)) return source.slice(0, -1);
|
|
135
|
+
return source;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @param {string} value @returns {string} */
|
|
139
|
+
function pascalCase(value) {
|
|
140
|
+
return String(value).split(/[^a-z\d]+/i).filter(Boolean)
|
|
141
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
|
|
142
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Form, Message, Select } from '../index.js';
|
|
2
|
+
import { connect } from './connect.js';
|
|
3
|
+
import { dateToUnixSeconds, unixSecondsToDate } from './query.js';
|
|
4
|
+
import { fieldToZxField, resolveFields } from './schema.js';
|
|
5
|
+
import { buildZeyosSelectConfig } from './select.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} ZeyosFormOptions
|
|
9
|
+
* @property {string[]} [fields] Curated ordered field allow-list.
|
|
10
|
+
* @property {string[]} [exclude] Field ids to omit.
|
|
11
|
+
* @property {Record<string, string>} [labels] Per-field label overrides.
|
|
12
|
+
* @property {string} [title=''] Fieldset legend.
|
|
13
|
+
* @property {1|2|3} [columns=2] Fieldset column count.
|
|
14
|
+
* @property {Record<string, unknown>} [value] Initial record using ZeyOS storage values.
|
|
15
|
+
* @property {unknown} [id] Initial record id to load.
|
|
16
|
+
* @property {(record: Record<string, unknown>) => unknown|Promise<unknown>} [onSaved] Success callback.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} ZeyosFormConfig
|
|
21
|
+
* @property {Record<string, any>[]} fieldMeta Ordered normalized schema metadata.
|
|
22
|
+
* @property {Record<string, Record<string, any>>} fields Field descriptors keyed by id.
|
|
23
|
+
* @property {Array<{id: string, resource: string}>} entityFields Referenced-resource fields.
|
|
24
|
+
* @property {Record<string, any>} formOptions Plain Zx Form options.
|
|
25
|
+
* @property {(record: Record<string, unknown>) => Record<string, unknown>} mapFromRecord Maps Unix seconds to Date values.
|
|
26
|
+
* @property {(values: Record<string, unknown>) => Record<string, unknown>} mapToRecord Maps Date and enum values to API storage values.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} ZeyosFormBinding
|
|
31
|
+
* @property {Form} form Generated Zx form.
|
|
32
|
+
* @property {Promise<Record<string, unknown>|null>} ready Initial value/load readiness.
|
|
33
|
+
* @property {(id: unknown) => Promise<Record<string, unknown>>} load Loads and maps one record.
|
|
34
|
+
* @property {() => Promise<Record<string, unknown>|null>} save Validates and creates or updates the record.
|
|
35
|
+
* @property {() => Form} getForm Returns the generated Zx Form.
|
|
36
|
+
* @property {() => void} destroy Destroys the form and all owned fields/selects.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds the DOM-free form configuration consumed by {@link zeyosForm}. Entity descriptors retain
|
|
41
|
+
* the `zxselect` type and receive the same async options used by {@link zeyosSelect}; enum options
|
|
42
|
+
* come from normalized schema metadata, and the returned mappers handle Unix-second dates.
|
|
43
|
+
* @param {Record<string, any>} client Injected ZeyOS client instance.
|
|
44
|
+
* @param {string} resource Resource name such as `transactions`.
|
|
45
|
+
* @param {ZeyosFormOptions} [opts={}] Schema curation and layout options.
|
|
46
|
+
* @returns {ZeyosFormConfig} Plain generator configuration and value mappers.
|
|
47
|
+
*/
|
|
48
|
+
export function buildZeyosFormConfig(client, resource, opts = {}) {
|
|
49
|
+
const fieldMeta = resolveFields(client, resource, opts);
|
|
50
|
+
const fields = {};
|
|
51
|
+
const entityFields = [];
|
|
52
|
+
|
|
53
|
+
for (const meta of fieldMeta) {
|
|
54
|
+
const descriptor = fieldToZxField(meta);
|
|
55
|
+
const referencedResource = descriptor.type === 'zxselect' && typeof descriptor.props?.entity === 'string'
|
|
56
|
+
? descriptor.props.entity
|
|
57
|
+
: null;
|
|
58
|
+
if (referencedResource) {
|
|
59
|
+
entityFields.push({ id: String(meta.id), resource: referencedResource });
|
|
60
|
+
descriptor.props = {
|
|
61
|
+
...buildZeyosSelectConfig(client, referencedResource, {
|
|
62
|
+
clearable: !descriptor.required,
|
|
63
|
+
placeholder: `Select ${String(descriptor.label).toLowerCase()}`
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
fields[String(meta.id)] = descriptor;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
fieldMeta,
|
|
72
|
+
fields,
|
|
73
|
+
entityFields,
|
|
74
|
+
formOptions: {
|
|
75
|
+
fieldsets: [{
|
|
76
|
+
title: String(opts.title ?? ''),
|
|
77
|
+
columns: normalizeColumns(opts.columns),
|
|
78
|
+
fields
|
|
79
|
+
}]
|
|
80
|
+
},
|
|
81
|
+
mapFromRecord: (record) => mapFromRecord(record, fieldMeta),
|
|
82
|
+
mapToRecord: (values) => mapToRecord(values, fieldMeta)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generates a schema-driven Zx Form and a small async binding object. `load(id)` hydrates entity
|
|
88
|
+
* labels and converts Unix seconds to Date objects. `save()` validates, maps values back to API
|
|
89
|
+
* storage types, calls create/update through the WP14 connection facade, shows a success toast,
|
|
90
|
+
* and invokes `onSaved(record)`. Await `ready` when `opts.id` or `opts.value` is supplied.
|
|
91
|
+
* @param {Record<string, any>} client Injected ZeyOS client instance.
|
|
92
|
+
* @param {string} resource Resource name such as `transactions`.
|
|
93
|
+
* @param {ZeyosFormOptions} [opts={}] Schema, initial value, and save options.
|
|
94
|
+
* @returns {ZeyosFormBinding} Generated form and lifecycle/data methods.
|
|
95
|
+
*/
|
|
96
|
+
export function zeyosForm(client, resource, opts = {}) {
|
|
97
|
+
if (opts.onSaved != null && typeof opts.onSaved !== 'function') {
|
|
98
|
+
throw new TypeError('zeyosForm onSaved must be a function');
|
|
99
|
+
}
|
|
100
|
+
const config = buildZeyosFormConfig(client, resource, opts);
|
|
101
|
+
const facade = connect(client);
|
|
102
|
+
const form = new Form(null, config.formOptions);
|
|
103
|
+
let currentId = opts.value?.ID ?? opts.id ?? null;
|
|
104
|
+
let destroyed = false;
|
|
105
|
+
let savePending = null;
|
|
106
|
+
|
|
107
|
+
const applyRecord = async (record) => {
|
|
108
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
109
|
+
throw new TypeError('ZeyOS form value must be a record');
|
|
110
|
+
}
|
|
111
|
+
await Promise.all(config.entityFields.map(async ({ id, resource: referencedResource }) => {
|
|
112
|
+
const value = record[id];
|
|
113
|
+
if (value == null) return;
|
|
114
|
+
const selected = value && typeof value === 'object'
|
|
115
|
+
? value
|
|
116
|
+
: normalizeRecord(await facade.get(referencedResource, value));
|
|
117
|
+
const select = Select.from(form.getField(id)?.getInput());
|
|
118
|
+
if (select instanceof Select) select.setItems([selected]);
|
|
119
|
+
}));
|
|
120
|
+
if (!destroyed) form.setValues(config.mapFromRecord(record), { silent: true });
|
|
121
|
+
return record;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const load = async (id) => {
|
|
125
|
+
if (id == null || id === '') throw new TypeError('zeyosForm.load() requires an id');
|
|
126
|
+
const record = normalizeRecord(await facade.get(resource, id));
|
|
127
|
+
currentId = record.ID ?? id;
|
|
128
|
+
await applyRecord(record);
|
|
129
|
+
return record;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const save = () => {
|
|
133
|
+
if (destroyed) return Promise.reject(new Error('Cannot save a destroyed ZeyOS form'));
|
|
134
|
+
if (savePending) return savePending;
|
|
135
|
+
if (!form.submit()) return Promise.resolve(null);
|
|
136
|
+
|
|
137
|
+
savePending = (async () => {
|
|
138
|
+
let payload;
|
|
139
|
+
try {
|
|
140
|
+
payload = config.mapToRecord(form.getValues());
|
|
141
|
+
delete payload.ID;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
facade.reportError(error, { operation: 'serialize', resource });
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const result = currentId == null
|
|
148
|
+
? await facade.create(resource, payload)
|
|
149
|
+
: await facade.update(resource, { ID: currentId, ...payload });
|
|
150
|
+
const record = normalizeRecord(result);
|
|
151
|
+
currentId = record.ID ?? currentId;
|
|
152
|
+
if (opts.onSaved) {
|
|
153
|
+
try {
|
|
154
|
+
await opts.onSaved(record);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
facade.reportError(error, { operation: 'onSaved', resource });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
Message.success(`${recordLabel(record, resource)} saved successfully.`);
|
|
161
|
+
return record;
|
|
162
|
+
})().finally(() => {
|
|
163
|
+
savePending = null;
|
|
164
|
+
});
|
|
165
|
+
return savePending;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const ready = Object.prototype.hasOwnProperty.call(opts, 'id')
|
|
169
|
+
? load(opts.id)
|
|
170
|
+
: (opts.value ? applyRecord(opts.value) : Promise.resolve(null));
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
form,
|
|
174
|
+
ready,
|
|
175
|
+
load,
|
|
176
|
+
save,
|
|
177
|
+
getForm: () => form,
|
|
178
|
+
destroy() {
|
|
179
|
+
if (destroyed) return;
|
|
180
|
+
destroyed = true;
|
|
181
|
+
form.destroy();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** @param {Record<string, unknown>} record @param {Record<string, any>[]} meta @returns {Record<string, unknown>} */
|
|
187
|
+
function mapFromRecord(record, meta) {
|
|
188
|
+
const values = {};
|
|
189
|
+
for (const field of meta) {
|
|
190
|
+
const id = String(field.id);
|
|
191
|
+
const value = record[id];
|
|
192
|
+
values[id] = isDateField(field) && value != null && value !== ''
|
|
193
|
+
? unixSecondsToDate(/** @type {number|string} */ (value))
|
|
194
|
+
: value;
|
|
195
|
+
}
|
|
196
|
+
return values;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @param {Record<string, unknown>} values @param {Record<string, any>[]} meta @returns {Record<string, unknown>} */
|
|
200
|
+
function mapToRecord(values, meta) {
|
|
201
|
+
const record = {};
|
|
202
|
+
for (const field of meta) {
|
|
203
|
+
const id = String(field.id);
|
|
204
|
+
let value = values[id];
|
|
205
|
+
if (isDateField(field)) {
|
|
206
|
+
value = value == null || value === '' ? null : dateToUnixSeconds(/** @type {Date} */ (value));
|
|
207
|
+
} else if (hasEnum(field) && isNumericType(field.type) && value !== '' && value != null) {
|
|
208
|
+
const numeric = Number(value);
|
|
209
|
+
if (Number.isFinite(numeric)) value = numeric;
|
|
210
|
+
}
|
|
211
|
+
record[id] = value;
|
|
212
|
+
}
|
|
213
|
+
return record;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** @param {Record<string, any>} meta @returns {boolean} */
|
|
217
|
+
function isDateField(meta) {
|
|
218
|
+
const descriptor = fieldToZxField(meta);
|
|
219
|
+
return descriptor.type === 'date' || descriptor.type === 'datetime';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** @param {Record<string, any>} meta @returns {boolean} */
|
|
223
|
+
function hasEnum(meta) {
|
|
224
|
+
return meta.enums != null || meta.enum != null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** @param {unknown} type @returns {boolean} */
|
|
228
|
+
function isNumericType(type) {
|
|
229
|
+
return /^(?:smallint|integer|bigint|int|int2|int4|int8|numeric|decimal)$/i.test(String(type ?? ''));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** @param {unknown} value @returns {1|2|3} */
|
|
233
|
+
function normalizeColumns(value) {
|
|
234
|
+
const number = Number(value ?? 2);
|
|
235
|
+
return /** @type {1|2|3} */ ([1, 2, 3].includes(number) ? number : 2);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** @param {unknown} result @returns {Record<string, unknown>} */
|
|
239
|
+
function normalizeRecord(result) {
|
|
240
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
241
|
+
const nested = result.data;
|
|
242
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested)) return nested;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
throw new TypeError('ZeyOS operation did not return a record');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** @param {Record<string, unknown>} record @param {string} resource @returns {string} */
|
|
249
|
+
function recordLabel(record, resource) {
|
|
250
|
+
for (const key of ['transactionnum', 'name', 'lastname', 'customernum', 'number', 'ID']) {
|
|
251
|
+
if (record[key] != null && record[key] !== '') return String(record[key]);
|
|
252
|
+
}
|
|
253
|
+
return String(resource);
|
|
254
|
+
}
|