@stonecrop/aform 0.31.0 → 0.33.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/dist/aform.js +3024 -2256
- package/dist/aform.js.map +1 -1
- package/dist/assets/index.css +1170 -1
- package/dist/src/components/AForm.vue.d.ts +20 -0
- package/dist/src/components/AForm.vue.d.ts.map +1 -0
- package/dist/src/components/AFormLoading.vue.d.ts +14 -0
- package/dist/src/components/AFormLoading.vue.d.ts.map +1 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts +7 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts.map +1 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts +7 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts.map +1 -0
- package/dist/src/components/form/ABadge.vue.d.ts +14 -0
- package/dist/src/components/form/ABadge.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts +15 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts +20 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADate.vue.d.ts +14 -0
- package/dist/src/components/form/ADate.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts +28 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateRange.vue.d.ts +18 -0
- package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts +52 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTime.vue.d.ts +17 -0
- package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts +30 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADropdown.vue.d.ts +20 -0
- package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADuration.vue.d.ts +18 -0
- package/dist/src/components/form/ADuration.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFieldset.vue.d.ts +28 -0
- package/dist/src/components/form/AFieldset.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts +5 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFormLink.vue.d.ts +40 -0
- package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts +14 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts +20 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts +23 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextInput.vue.d.ts +14 -0
- package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts +21 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -0
- package/dist/src/components/utilities/Login.vue.d.ts +14 -0
- package/dist/src/components/utilities/Login.vue.d.ts.map +1 -0
- package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
- package/package.json +35 -30
- package/dist/aform.d.ts +0 -441
- package/dist/aform.tsbuildinfo +0 -1
- package/dist/src/directives/mask.js +0 -107
- package/dist/src/index.js +0 -58
- package/dist/src/types/index.js +0 -0
- package/dist/src/utils/badge.js +0 -71
- package/dist/src/utils/columns.js +0 -33
- package/dist/src/utils/deserialize.js +0 -20
- package/src/components/AForm.vue +0 -314
- package/src/components/AFormLoading.vue +0 -76
- package/src/components/base/CollapseButton.vue +0 -36
- package/src/components/base/ExpandButton.vue +0 -34
- package/src/components/form/ABadge.vue +0 -135
- package/src/components/form/ACheckbox.vue +0 -62
- package/src/components/form/ACurrencyInput.vue +0 -357
- package/src/components/form/ADate.vue +0 -93
- package/src/components/form/ADatePicker.vue +0 -470
- package/src/components/form/ADateRange.vue +0 -133
- package/src/components/form/ADateSelection.vue +0 -174
- package/src/components/form/ADateTime.vue +0 -149
- package/src/components/form/ADateTimeInput.vue +0 -299
- package/src/components/form/ADropdown.vue +0 -222
- package/src/components/form/ADuration.vue +0 -124
- package/src/components/form/AFieldset.vue +0 -72
- package/src/components/form/AFileAttach.vue +0 -97
- package/src/components/form/AFormLink.vue +0 -368
- package/src/components/form/ANumericInput.vue +0 -32
- package/src/components/form/AQuantityInput.vue +0 -349
- package/src/components/form/ASegmentedControl.vue +0 -342
- package/src/components/form/ATextInput.vue +0 -40
- package/src/components/form/ATextboxInput.vue +0 -66
- package/src/components/utilities/Login.vue +0 -189
- package/src/directives/mask.ts +0 -122
- package/src/index.ts +0 -92
- package/src/shims-vue.d.ts +0 -5
- package/src/types/index.ts +0 -333
- package/src/utils/badge.ts +0 -94
- package/src/utils/columns.ts +0 -38
- package/src/utils/deserialize.ts +0 -20
package/dist/src/index.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
export { deserializeFunction } from './utils/deserialize';
|
|
2
|
-
export { badgeInputAccentStyle, resolveFieldBadge } from './utils/badge';
|
|
3
|
-
export { resolvedFieldsToColumns } from './utils/columns';
|
|
4
|
-
import { install as installATable } from '@stonecrop/atable';
|
|
5
|
-
import ACheckbox from './components/form/ACheckbox.vue';
|
|
6
|
-
import ACurrencyInput from './components/form/ACurrencyInput.vue';
|
|
7
|
-
import ADate from './components/form/ADate.vue';
|
|
8
|
-
import ADropdown from './components/form/ADropdown.vue';
|
|
9
|
-
import ASegmentedControl from './components/form/ASegmentedControl.vue';
|
|
10
|
-
import ABadge from './components/form/ABadge.vue';
|
|
11
|
-
import ADatePicker from './components/form/ADatePicker.vue';
|
|
12
|
-
import ADateTime from './components/form/ADateTime.vue';
|
|
13
|
-
import ADateTimeInput from './components/form/ADateTimeInput.vue';
|
|
14
|
-
import ADateSelection from './components/form/ADateSelection.vue';
|
|
15
|
-
import ADuration from './components/form/ADuration.vue';
|
|
16
|
-
import ADateRange from './components/form/ADateRange.vue';
|
|
17
|
-
import AFieldset from './components/form/AFieldset.vue';
|
|
18
|
-
import AFileAttach from './components/form/AFileAttach.vue';
|
|
19
|
-
import AForm from './components/AForm.vue';
|
|
20
|
-
import AFormLink from './components/form/AFormLink.vue';
|
|
21
|
-
import ANumericInput from './components/form/ANumericInput.vue';
|
|
22
|
-
import AQuantityInput from './components/form/AQuantityInput.vue';
|
|
23
|
-
import ATextInput from './components/form/ATextInput.vue';
|
|
24
|
-
import ATextboxInput from './components/form/ATextboxInput.vue';
|
|
25
|
-
import Login from './components/utilities/Login.vue';
|
|
26
|
-
import AFormLoading from './components/AFormLoading.vue';
|
|
27
|
-
import ExpandButton from './components/base/ExpandButton.vue';
|
|
28
|
-
/**
|
|
29
|
-
* Install all AForm components
|
|
30
|
-
* @param app - Vue app instance
|
|
31
|
-
* @public
|
|
32
|
-
*/
|
|
33
|
-
function install(app /* options */) {
|
|
34
|
-
app.use(installATable); // Install ATable components for use within AForm
|
|
35
|
-
app.component('ACheckbox', ACheckbox);
|
|
36
|
-
app.component('ACurrencyInput', ACurrencyInput);
|
|
37
|
-
app.component('ADate', ADate);
|
|
38
|
-
app.component('ADropdown', ADropdown);
|
|
39
|
-
app.component('ASegmentedControl', ASegmentedControl);
|
|
40
|
-
app.component('ABadge', ABadge);
|
|
41
|
-
app.component('ADatePicker', ADatePicker);
|
|
42
|
-
app.component('ADateTime', ADateTime);
|
|
43
|
-
app.component('ADateTimeInput', ADateTimeInput);
|
|
44
|
-
app.component('ADateRange', ADateRange);
|
|
45
|
-
app.component('ADateSelection', ADateSelection);
|
|
46
|
-
app.component('AFieldset', AFieldset);
|
|
47
|
-
app.component('AFileAttach', AFileAttach);
|
|
48
|
-
app.component('AForm', AForm);
|
|
49
|
-
app.component('AFormLink', AFormLink);
|
|
50
|
-
app.component('ANumericInput', ANumericInput);
|
|
51
|
-
app.component('AQuantityInput', AQuantityInput);
|
|
52
|
-
app.component('ATextInput', ATextInput);
|
|
53
|
-
app.component('ATextboxInput', ATextboxInput);
|
|
54
|
-
app.component('ADuration', ADuration);
|
|
55
|
-
app.component('AFormLoading', AFormLoading);
|
|
56
|
-
app.component('ExpandButton', ExpandButton);
|
|
57
|
-
}
|
|
58
|
-
export { ACheckbox, ACurrencyInput, ADate, ADropdown, ASegmentedControl, ABadge, ADatePicker, ADateRange, ADateSelection, ADuration, ADateTime, ADateTimeInput, AFieldset, AFileAttach, AForm, AFormLink, ANumericInput, AQuantityInput, ATextInput, ATextboxInput, Login, AFormLoading, ExpandButton, install, };
|
package/dist/src/types/index.js
DELETED
|
File without changes
|
package/dist/src/utils/badge.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { isBadgeDescriptor, lookupBadge } from '@stonecrop/schema';
|
|
2
|
-
import { deserializeFunction } from './deserialize';
|
|
3
|
-
/**
|
|
4
|
-
* Resolve a field value to a badge descriptor using format (if present) then options map.
|
|
5
|
-
* @public
|
|
6
|
-
*/
|
|
7
|
-
export function resolveFieldBadge(value, options, format, context = {}) {
|
|
8
|
-
if (format) {
|
|
9
|
-
let formatted;
|
|
10
|
-
if (typeof format === 'function') {
|
|
11
|
-
formatted = format(value, context);
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
const formatFn = deserializeFunction(format);
|
|
15
|
-
formatted = formatFn(value, context);
|
|
16
|
-
}
|
|
17
|
-
if (isBadgeDescriptor(formatted)) {
|
|
18
|
-
const label = formatted.label.trim();
|
|
19
|
-
return label ? formatted : undefined;
|
|
20
|
-
}
|
|
21
|
-
if (typeof formatted === 'string' && formatted !== '') {
|
|
22
|
-
return lookupBadge(options, formatted) ?? { label: formatted, variant: 'neutral' };
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const key = badgeLookupKey(value);
|
|
26
|
-
if (key === undefined)
|
|
27
|
-
return undefined;
|
|
28
|
-
return lookupBadge(options, key);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Stored choice value as an options-map key. Only primitives can match a declared choice, so
|
|
32
|
-
* anything else yields undefined rather than the '[object Object]' that String() would produce.
|
|
33
|
-
*/
|
|
34
|
-
function badgeLookupKey(value) {
|
|
35
|
-
switch (typeof value) {
|
|
36
|
-
case 'string':
|
|
37
|
-
return value === '' ? undefined : value;
|
|
38
|
-
case 'number':
|
|
39
|
-
case 'boolean':
|
|
40
|
-
case 'bigint':
|
|
41
|
-
return String(value);
|
|
42
|
-
default:
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const BADGE_VARIANTS = new Set(['neutral', 'success', 'warning', 'danger', 'brand']);
|
|
47
|
-
/**
|
|
48
|
-
* CSS custom properties for input-accent styling on a native input.
|
|
49
|
-
* @public
|
|
50
|
-
*/
|
|
51
|
-
export function badgeInputAccentStyle(descriptor) {
|
|
52
|
-
if (!descriptor?.label?.trim())
|
|
53
|
-
return undefined;
|
|
54
|
-
const variant = descriptor.variant ?? 'neutral';
|
|
55
|
-
if (descriptor.color) {
|
|
56
|
-
return {
|
|
57
|
-
borderLeftWidth: '4px',
|
|
58
|
-
borderLeftStyle: 'solid',
|
|
59
|
-
borderLeftColor: descriptor.color,
|
|
60
|
-
paddingLeft: 'calc(1ch - 4px)',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (!BADGE_VARIANTS.has(variant))
|
|
64
|
-
return undefined;
|
|
65
|
-
return {
|
|
66
|
-
borderLeftWidth: '4px',
|
|
67
|
-
borderLeftStyle: 'solid',
|
|
68
|
-
borderLeftColor: `var(--sc-badge-${variant}-accent)`,
|
|
69
|
-
paddingLeft: 'calc(1ch - 4px)',
|
|
70
|
-
};
|
|
71
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The resolved fields that can be columns in a list or table view, in declaration order.
|
|
3
|
-
*
|
|
4
|
-
* A cell renders one value, so only `kind: 'field'` qualifies. The other three kinds are
|
|
5
|
-
* containers: a `fieldset` groups fields for layout and has no value of its own, while a `link`
|
|
6
|
-
* and a `table` hold a nested record and an array of them. Neither has a `cellComponent`, so a
|
|
7
|
-
* container reaching `ACell` falls through to plain-text rendering and stringifies its children —
|
|
8
|
-
* no error and no log, just a wrong column.
|
|
9
|
-
*
|
|
10
|
-
* A fieldset's *children* are real columns, so it is flattened rather than dropped; losing them is
|
|
11
|
-
* the same silent defect in the other direction.
|
|
12
|
-
*
|
|
13
|
-
* One definition, called by both consumers: `Registry.buildTableConfig` (a child table's columns,
|
|
14
|
-
* from its target's resolved schema) and Desktop's records list. Re-deriving it at either call
|
|
15
|
-
* site produced exactly one of the two failures above at each.
|
|
16
|
-
*
|
|
17
|
-
* @param fields - resolved fields, as produced by `resolveSchema`
|
|
18
|
-
* @returns column definitions, with the `kind` discriminator stripped
|
|
19
|
-
* @public
|
|
20
|
-
*/
|
|
21
|
-
export function resolvedFieldsToColumns(fields) {
|
|
22
|
-
const columns = [];
|
|
23
|
-
for (const field of fields) {
|
|
24
|
-
if (field.kind === 'fieldset') {
|
|
25
|
-
columns.push(...resolvedFieldsToColumns(field.schema));
|
|
26
|
-
}
|
|
27
|
-
else if (field.kind === 'field') {
|
|
28
|
-
const { kind: _kind, ...column } = field;
|
|
29
|
-
columns.push(column);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return columns;
|
|
33
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deserializes a stringified function expression into a typed callable.
|
|
3
|
-
*
|
|
4
|
-
* Throws if the string cannot be parsed as a function (SyntaxError) or if the
|
|
5
|
-
* resulting expression is not callable (TypeError), or if the expression references
|
|
6
|
-
* an undefined variable (ReferenceError). Callers are responsible for try/catch.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const fn = deserializeFunction<(x: number) => number>('(x) => x * 2')
|
|
11
|
-
* fn(5) // 10
|
|
12
|
-
* ```
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
// T is intentionally a caller-supplied return type — this utility is unsafe by design (Function constructor).
|
|
16
|
-
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters
|
|
17
|
-
export function deserializeFunction(source) {
|
|
18
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion, typescript/no-implied-eval
|
|
19
|
-
return Function(`"use strict"; return (${source})`)();
|
|
20
|
-
}
|
package/src/components/AForm.vue
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<form class="aform">
|
|
3
|
-
<template v-for="(componentObj, key) in schema" :key="key">
|
|
4
|
-
<!-- Nested schema field (Doctype or any field with resolved schema) -->
|
|
5
|
-
<div v-if="isNestedSection(componentObj)" class="aform-nested-section">
|
|
6
|
-
<!-- Suppress h4 for fieldsets — they render their own legend inside AFieldset -->
|
|
7
|
-
<h4 v-if="componentObj.label && componentObj.kind !== 'fieldset'" class="aform-nested-label">
|
|
8
|
-
{{ componentObj.label }}
|
|
9
|
-
</h4>
|
|
10
|
-
<component
|
|
11
|
-
:is="componentObj.component ?? 'AForm'"
|
|
12
|
-
:data="nestedData[componentObj.fieldname]"
|
|
13
|
-
:mode="resolvedMode(componentObj)"
|
|
14
|
-
:schema="componentObj.schema"
|
|
15
|
-
:label="componentObj.label"
|
|
16
|
-
:collapsible="componentObj.kind === 'fieldset' ? componentObj.collapsible : undefined"
|
|
17
|
-
:errors="errors"
|
|
18
|
-
@update:data="(val: any) => updateNestedData(componentObj.fieldname, val)" />
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<!-- Regular field -->
|
|
22
|
-
<component
|
|
23
|
-
:is="componentObj.component"
|
|
24
|
-
v-else-if="!componentObj.hidden"
|
|
25
|
-
v-model="childModels[key].value"
|
|
26
|
-
:style="fieldStyle(componentObj)"
|
|
27
|
-
:schema="componentObj"
|
|
28
|
-
:data="dataModel[componentObj.fieldname]"
|
|
29
|
-
:mode="resolvedMode(componentObj)"
|
|
30
|
-
:errors="errors?.[componentObj.fieldname]"
|
|
31
|
-
v-bind="componentProps(componentObj)">
|
|
32
|
-
</component>
|
|
33
|
-
</template>
|
|
34
|
-
</form>
|
|
35
|
-
</template>
|
|
36
|
-
|
|
37
|
-
<script setup lang="ts">
|
|
38
|
-
import { computed, watchEffect, watch, ref } from 'vue'
|
|
39
|
-
|
|
40
|
-
import type { ResolvedField, ResolvedLink, ResolvedFieldset } from '../types'
|
|
41
|
-
import type { InteractionMode } from '@stonecrop/schema'
|
|
42
|
-
|
|
43
|
-
const emit = defineEmits(['update:schema', 'update:data'])
|
|
44
|
-
const dataModel = defineModel<Record<string, any>>('data', { required: true })
|
|
45
|
-
const {
|
|
46
|
-
schema,
|
|
47
|
-
mode = 'edit',
|
|
48
|
-
errors,
|
|
49
|
-
} = defineProps<{
|
|
50
|
-
schema: ResolvedField[]
|
|
51
|
-
mode?: InteractionMode
|
|
52
|
-
/** Inline validation errors keyed by fieldname. Fed by the host; the form stays store-agnostic. */
|
|
53
|
-
errors?: Record<string, string[]>
|
|
54
|
-
}>()
|
|
55
|
-
|
|
56
|
-
const isNestedSection = (componentObj: ResolvedField): componentObj is ResolvedLink | ResolvedFieldset =>
|
|
57
|
-
(componentObj.kind === 'link' || componentObj.kind === 'fieldset') &&
|
|
58
|
-
'schema' in componentObj &&
|
|
59
|
-
Array.isArray(componentObj.schema) &&
|
|
60
|
-
componentObj.schema.length > 0
|
|
61
|
-
|
|
62
|
-
// Reactive nested data refs for two-way binding with nested AForm instances
|
|
63
|
-
const nestedData = ref<Record<string, any>>({})
|
|
64
|
-
|
|
65
|
-
// Sync external dataModel changes into nestedData (one-way, no emit back).
|
|
66
|
-
// Uses a shallow watch so only top-level object replacement (e.g. parent reset)
|
|
67
|
-
// triggers a sync — property mutations from within are handled by updateNestedData.
|
|
68
|
-
watch(
|
|
69
|
-
() => dataModel.value,
|
|
70
|
-
newData => {
|
|
71
|
-
if (!schema || !newData) return
|
|
72
|
-
schema.forEach(field => {
|
|
73
|
-
if (isNestedSection(field)) {
|
|
74
|
-
nestedData.value[field.fieldname] = newData[field.fieldname] ?? {}
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
},
|
|
78
|
-
{ immediate: true }
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
// Called by the nested <AForm>'s @update:data handler.
|
|
82
|
-
// Updates nestedData locally and propagates upward in one step,
|
|
83
|
-
// avoiding the watchEffect feedback loop that occurred with v-model.
|
|
84
|
-
const updateNestedData = (fieldname: string, val: any) => {
|
|
85
|
-
nestedData.value[fieldname] = val
|
|
86
|
-
if (dataModel.value) {
|
|
87
|
-
dataModel.value[fieldname] = val
|
|
88
|
-
emit('update:data', { ...dataModel.value })
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const componentProps = (componentObj: ResolvedField) => {
|
|
93
|
-
const propsToPass: Record<string, any> = {}
|
|
94
|
-
for (const [key, value] of Object.entries(componentObj)) {
|
|
95
|
-
// 'mode' is excluded here because it is handled by resolvedMode()
|
|
96
|
-
// and passed explicitly via :mode to avoid conflicting with the form-level defaults.
|
|
97
|
-
if (!['component', 'primaryKey', 'computed', 'language', 'hidden', 'mode', 'width', 'height'].includes(key)) {
|
|
98
|
-
propsToPass[key] = value
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// A table sources its rows from the data model, never from the schema. `kind` is the only
|
|
103
|
-
// check: every path into AForm sets it (Zod's injectKind, Doctype.fromObject's
|
|
104
|
-
// normalizeFieldKind, and the registry), and hand-built ResolvedTable literals declare it.
|
|
105
|
-
if (componentObj.kind === 'table') {
|
|
106
|
-
propsToPass['rows'] = dataModel.value[componentObj.fieldname] || []
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return propsToPass
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const fieldStyle = (componentObj: ResolvedField): Record<string, string> => {
|
|
113
|
-
if (componentObj.kind !== 'field') return {}
|
|
114
|
-
const style: Record<string, string> = {}
|
|
115
|
-
if (componentObj.width) {
|
|
116
|
-
style.flexBasis = componentObj.width
|
|
117
|
-
style.width = componentObj.width
|
|
118
|
-
}
|
|
119
|
-
if (componentObj.height) {
|
|
120
|
-
style.height = componentObj.height
|
|
121
|
-
style.minHeight = '0'
|
|
122
|
-
style.flexGrow = '1'
|
|
123
|
-
style.alignSelf = 'stretch'
|
|
124
|
-
}
|
|
125
|
-
return style
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const effectiveFormMode = computed(() => mode ?? 'edit')
|
|
129
|
-
|
|
130
|
-
// Resolve the effective mode for a schema field, allowing per-field overrides
|
|
131
|
-
function resolvedMode(componentObj: ResolvedField): InteractionMode {
|
|
132
|
-
const fieldMode = componentObj.mode
|
|
133
|
-
if (fieldMode) return fieldMode
|
|
134
|
-
return effectiveFormMode.value
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Create stable computed refs array to avoid recreation on every access
|
|
138
|
-
const childModelsCache = ref<ReturnType<typeof computed>[]>([])
|
|
139
|
-
|
|
140
|
-
// Watch for schema changes and update cache (avoiding side effects in computed)
|
|
141
|
-
watchEffect(() => {
|
|
142
|
-
if (!schema) return
|
|
143
|
-
|
|
144
|
-
// Recreate cache only if length changed
|
|
145
|
-
if (childModelsCache.value.length !== schema.length) {
|
|
146
|
-
childModelsCache.value = schema.map((_val, i) => {
|
|
147
|
-
return computed({
|
|
148
|
-
get() {
|
|
149
|
-
return dataModel.value?.[schema[i].fieldname]
|
|
150
|
-
},
|
|
151
|
-
set: newValue => {
|
|
152
|
-
const fieldname = schema[i].fieldname
|
|
153
|
-
if (fieldname && dataModel.value) {
|
|
154
|
-
dataModel.value[fieldname] = newValue
|
|
155
|
-
emit('update:data', { ...dataModel.value })
|
|
156
|
-
}
|
|
157
|
-
emit('update:schema', schema)
|
|
158
|
-
},
|
|
159
|
-
})
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// Computed just returns the cached models (no side effects)
|
|
165
|
-
const childModels = computed(() => childModelsCache.value)
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
|
-
<style>
|
|
169
|
-
/* global styles for aform */
|
|
170
|
-
.aform input {
|
|
171
|
-
font-family: var(--sc-font-family);
|
|
172
|
-
border: none;
|
|
173
|
-
}
|
|
174
|
-
.aform_form-element {
|
|
175
|
-
padding: 0;
|
|
176
|
-
margin: 0;
|
|
177
|
-
position: relative;
|
|
178
|
-
box-sizing: border-box;
|
|
179
|
-
flex-grow: 1;
|
|
180
|
-
min-width: 20ch;
|
|
181
|
-
/* margin-bottom: 1rem; */
|
|
182
|
-
}
|
|
183
|
-
.aform__grid--full {
|
|
184
|
-
flex-basis: 100%;
|
|
185
|
-
width: 100%;
|
|
186
|
-
}
|
|
187
|
-
.aform_input-field {
|
|
188
|
-
outline: 1px solid var(--sc-input-border-color);
|
|
189
|
-
outline-offset: -1px;
|
|
190
|
-
font-size: 1rem;
|
|
191
|
-
padding: 0.5rem;
|
|
192
|
-
margin: 0 0 0 0;
|
|
193
|
-
border-radius: 0;
|
|
194
|
-
box-sizing: border-box;
|
|
195
|
-
width: 100%;
|
|
196
|
-
min-height: auto;
|
|
197
|
-
position: relative;
|
|
198
|
-
color: var(--sc-cell-text-color);
|
|
199
|
-
background: var(--sc-input-field-background);
|
|
200
|
-
font-family: var(--sc-font-family);
|
|
201
|
-
}
|
|
202
|
-
.aform_input-field:focus {
|
|
203
|
-
outline: 1px solid var(--sc-input-active-border-color);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.aform_display-value {
|
|
207
|
-
display: block;
|
|
208
|
-
padding: 0.5rem;
|
|
209
|
-
min-height: 2rem;
|
|
210
|
-
color: var(--sc-cell-text-color);
|
|
211
|
-
word-break: break-word;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.aform_input-field:focus + .aform_field-label {
|
|
215
|
-
color: var(--sc-input-active-label-color);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.aform_field-label {
|
|
219
|
-
color: var(--sc-input-label-color);
|
|
220
|
-
display: inline-block;
|
|
221
|
-
position: absolute;
|
|
222
|
-
padding: 0 0.25rem;
|
|
223
|
-
margin: 0rem;
|
|
224
|
-
z-index: 1;
|
|
225
|
-
font-size: 0.7rem;
|
|
226
|
-
font-weight: 300;
|
|
227
|
-
letter-spacing: 0.05rem;
|
|
228
|
-
background: linear-gradient(var(--sc-form-background) 50%, var(--sc-input-field-background) 50%);
|
|
229
|
-
width: auto;
|
|
230
|
-
box-sizing: border-box;
|
|
231
|
-
background: white;
|
|
232
|
-
margin: 0;
|
|
233
|
-
grid-row: 1;
|
|
234
|
-
top: 0;
|
|
235
|
-
left: 10px;
|
|
236
|
-
border: none;
|
|
237
|
-
line-height: 0;
|
|
238
|
-
transform: translateY(-50%);
|
|
239
|
-
}
|
|
240
|
-
.aform_input-field:disabled,
|
|
241
|
-
.aform_checkbox-container:has(.aform_checkbox:disabled) {
|
|
242
|
-
background: var(--sc-input-field-disabled-background);
|
|
243
|
-
}
|
|
244
|
-
.aform_input-field:disabled + .aform_field-label,
|
|
245
|
-
.aform_checkbox-container:has(.aform_checkbox:disabled) + .aform_field-label {
|
|
246
|
-
background: linear-gradient(var(--sc-form-background) 50%, var(--sc-input-field-disabled-background) 50%);
|
|
247
|
-
}
|
|
248
|
-
.aform_input-field:disabled ~ p.aform_error,
|
|
249
|
-
.aform_checkbox-container:has(.aform_checkbox:disabled) ~ p.aform_error {
|
|
250
|
-
background: linear-gradient(var(--sc-form-background) 50%, var(--sc-input-field-disabled-background) 50%);
|
|
251
|
-
}
|
|
252
|
-
.aform_field-label::after {
|
|
253
|
-
margin: 0;
|
|
254
|
-
padding: 0;
|
|
255
|
-
box-sizing: border-box;
|
|
256
|
-
content: '';
|
|
257
|
-
line-height: normal;
|
|
258
|
-
}
|
|
259
|
-
p.aform_error {
|
|
260
|
-
/* v-show toggles visibility per field; base display must be visible (was stuck at `none`,
|
|
261
|
-
which overrode v-show and left every field error dormant). */
|
|
262
|
-
display: inline-block;
|
|
263
|
-
background: linear-gradient(var(--sc-form-background) 50%, var(--sc-input-field-background) 50%);
|
|
264
|
-
padding: 0 0.25rem;
|
|
265
|
-
margin: 0rem;
|
|
266
|
-
width: auto;
|
|
267
|
-
color: var(--sc-brand-danger);
|
|
268
|
-
font-size: 0.7rem;
|
|
269
|
-
position: absolute;
|
|
270
|
-
right: 0;
|
|
271
|
-
top: 0;
|
|
272
|
-
line-height: 0;
|
|
273
|
-
background: white;
|
|
274
|
-
padding: 0.25rem;
|
|
275
|
-
transform: translate(-1rem, -50%);
|
|
276
|
-
margin: 0;
|
|
277
|
-
}
|
|
278
|
-
</style>
|
|
279
|
-
|
|
280
|
-
<style scoped>
|
|
281
|
-
.aform {
|
|
282
|
-
display: flex;
|
|
283
|
-
flex-wrap: wrap;
|
|
284
|
-
gap: 1rem;
|
|
285
|
-
padding: 1rem;
|
|
286
|
-
border: 1px solid var(--sc-form-border);
|
|
287
|
-
border-left: 4px solid var(--sc-form-border);
|
|
288
|
-
margin-bottom: 1rem;
|
|
289
|
-
max-width: 100%;
|
|
290
|
-
}
|
|
291
|
-
@media screen and (max-width: 400px) {
|
|
292
|
-
.aform {
|
|
293
|
-
flex-direction: column;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/* Nested form section */
|
|
298
|
-
.aform-nested-section {
|
|
299
|
-
width: 100%;
|
|
300
|
-
padding: 0.5rem 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.aform-nested-label {
|
|
304
|
-
font-size: 0.9rem;
|
|
305
|
-
font-weight: 600;
|
|
306
|
-
margin: 0 0 0.5rem 0;
|
|
307
|
-
color: var(--sc-input-label-color);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.aform-nested-section .aform {
|
|
311
|
-
border-left-width: 2px;
|
|
312
|
-
margin-left: 0.5rem;
|
|
313
|
-
}
|
|
314
|
-
</style>
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="aform-loading">
|
|
3
|
-
<div class="aform-loading__label">
|
|
4
|
-
<slot></slot>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="aform-loading__bar"></div>
|
|
7
|
-
</div>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<style scoped>
|
|
11
|
-
.aform-loading {
|
|
12
|
-
width: 100%;
|
|
13
|
-
border: 1px solid var(--sc-form-border);
|
|
14
|
-
border-left: 4px solid var(--sc-form-border);
|
|
15
|
-
padding: 1rem;
|
|
16
|
-
margin-bottom: 1rem;
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
position: relative;
|
|
19
|
-
overflow: hidden;
|
|
20
|
-
background: var(--sc-input-field-background);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.aform-loading__label {
|
|
24
|
-
font-size: 0.9rem;
|
|
25
|
-
color: var(--sc-cell-text-color);
|
|
26
|
-
z-index: 1;
|
|
27
|
-
position: relative;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.aform-loading__label:not(:empty)::after {
|
|
31
|
-
content: '...';
|
|
32
|
-
animation: ellipsis 2s infinite;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.aform-loading__bar {
|
|
36
|
-
width: 100%;
|
|
37
|
-
height: 100%;
|
|
38
|
-
position: absolute;
|
|
39
|
-
left: -100%;
|
|
40
|
-
top: 0;
|
|
41
|
-
background: linear-gradient(
|
|
42
|
-
90deg,
|
|
43
|
-
rgba(var(--sc-table-loading-color), 0) 0%,
|
|
44
|
-
rgba(var(--sc-table-loading-color), 1) 50%,
|
|
45
|
-
rgba(var(--sc-table-loading-color), 0) 100%
|
|
46
|
-
);
|
|
47
|
-
animation: bar-sweep 2s infinite;
|
|
48
|
-
z-index: 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@keyframes bar-sweep {
|
|
52
|
-
0% {
|
|
53
|
-
left: -100%;
|
|
54
|
-
}
|
|
55
|
-
100% {
|
|
56
|
-
left: 100%;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@keyframes ellipsis {
|
|
61
|
-
0%,
|
|
62
|
-
20% {
|
|
63
|
-
content: '';
|
|
64
|
-
}
|
|
65
|
-
40% {
|
|
66
|
-
content: '.';
|
|
67
|
-
}
|
|
68
|
-
60% {
|
|
69
|
-
content: '..';
|
|
70
|
-
}
|
|
71
|
-
80%,
|
|
72
|
-
100% {
|
|
73
|
-
content: '...';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<button class="collapse-button" :class="collapsed ? 'rotated' : 'unrotated'">×</button>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script setup lang="ts">
|
|
6
|
-
defineProps<{ collapsed: boolean }>()
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<style scoped>
|
|
10
|
-
.collapse-button {
|
|
11
|
-
width: 2ch;
|
|
12
|
-
min-width: calc(66px - 4ch);
|
|
13
|
-
background-color: transparent;
|
|
14
|
-
font-size: 150%;
|
|
15
|
-
text-align: center;
|
|
16
|
-
border: none;
|
|
17
|
-
margin-top: -0.5rem;
|
|
18
|
-
}
|
|
19
|
-
.rotated {
|
|
20
|
-
transform: rotate(45deg);
|
|
21
|
-
-webkit-transform: rotate(45deg);
|
|
22
|
-
-moz-transform: rotate(45deg);
|
|
23
|
-
-ms-transform: rotate(45deg);
|
|
24
|
-
-o-transform: rotate(45deg);
|
|
25
|
-
transition: transform 250ms;
|
|
26
|
-
transform-origin: center center;
|
|
27
|
-
}
|
|
28
|
-
.unrotated {
|
|
29
|
-
transform: rotate(0deg);
|
|
30
|
-
-webkit-transform: rotate(0deg);
|
|
31
|
-
-moz-transform: rotate(0deg);
|
|
32
|
-
-ms-transform: rotate(0deg);
|
|
33
|
-
-o-transform: rotate(0deg);
|
|
34
|
-
transition: transform 250ms;
|
|
35
|
-
}
|
|
36
|
-
</style>
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<button
|
|
3
|
-
type="button"
|
|
4
|
-
class="expansion-index"
|
|
5
|
-
:aria-expanded="expanded"
|
|
6
|
-
:aria-label="expanded ? 'Collapse' : 'Expand'">
|
|
7
|
-
{{ expanded ? '▼' : '►' }}
|
|
8
|
-
</button>
|
|
9
|
-
</template>
|
|
10
|
-
|
|
11
|
-
<script setup lang="ts">
|
|
12
|
-
defineProps<{
|
|
13
|
-
expanded: boolean
|
|
14
|
-
}>()
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<style scoped>
|
|
18
|
-
.expansion-index {
|
|
19
|
-
color: var(--sc-header-text-color);
|
|
20
|
-
font-weight: bold;
|
|
21
|
-
font-size: 11px;
|
|
22
|
-
line-height: 1;
|
|
23
|
-
text-align: center;
|
|
24
|
-
user-select: none;
|
|
25
|
-
width: 2ch;
|
|
26
|
-
min-width: 1.25rem;
|
|
27
|
-
height: 1.25rem;
|
|
28
|
-
padding: 0;
|
|
29
|
-
border: none;
|
|
30
|
-
background: transparent;
|
|
31
|
-
cursor: pointer;
|
|
32
|
-
box-sizing: border-box;
|
|
33
|
-
}
|
|
34
|
-
</style>
|