@adcops/autocore-react 3.5.7 → 3.5.9
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/components/forms/useEditDraft.d.ts +49 -0
- package/dist/components/forms/useEditDraft.d.ts.map +1 -0
- package/dist/components/forms/useEditDraft.js +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/tis/TestDataView.d.ts +8 -0
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis/TestRawDataView.d.ts.map +1 -1
- package/dist/components/tis/TestRawDataView.js +1 -1
- package/dist/components/tis/chartViews.d.ts +57 -0
- package/dist/components/tis/chartViews.d.ts.map +1 -0
- package/dist/components/tis/chartViews.js +1 -0
- package/dist/components/tis-editor/editor/ChartViewDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/ChartViewDialog.js +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.js +1 -1
- package/dist/components/tis-editor/editor/ViewsEditor.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/ViewsEditor.js +1 -1
- package/dist/components/tis-editor/types.d.ts +6 -0
- package/dist/components/tis-editor/types.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/components/forms/useEditDraft.ts +0 -0
- package/src/components/index.ts +3 -0
- package/src/components/tis/TestDataView.tsx +16 -10
- package/src/components/tis/TestRawDataView.tsx +6 -7
- package/src/components/tis/chartViews.ts +104 -0
- package/src/components/tis-editor/editor/ChartViewDialog.tsx +24 -10
- package/src/components/tis-editor/editor/TestFieldDialog.tsx +25 -12
- package/src/components/tis-editor/editor/ViewsEditor.tsx +31 -5
- package/src/components/tis-editor/types.ts +6 -0
- package/tools/tests/dist/tis-editor.entry.css +90 -0
- package/tools/tests/dist/tis-editor.entry.js +41431 -0
- package/tools/tests/tis-editor.entry.tsx +46 -0
- package/tools/tests/tis-editor.test.mjs +133 -0
- package/tools/tests/vite.config.mjs +30 -0
|
@@ -24,6 +24,7 @@ import { Line } from 'react-chartjs-2';
|
|
|
24
24
|
import type { ChartView, TestMethod } from './TestDataView';
|
|
25
25
|
import { useTis } from './TisProvider';
|
|
26
26
|
import { useRawCycleData } from './useRawCycleData';
|
|
27
|
+
import { orderedViews } from './chartViews';
|
|
27
28
|
|
|
28
29
|
ChartJS.register(
|
|
29
30
|
CategoryScale, LinearScale, PointElement, LineElement,
|
|
@@ -55,13 +56,11 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
55
56
|
const chartRef = useRef<any>(null);
|
|
56
57
|
|
|
57
58
|
// raw_trace-capable views only — cycle scatter lives in <TestDataView>.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return out;
|
|
64
|
-
}, [schema]);
|
|
59
|
+
// In the author's order, same as the unified panel's picker.
|
|
60
|
+
const traceViews = useMemo(
|
|
61
|
+
() => orderedViews(schema?.views as Record<string, ChartView> | undefined, 'raw_trace'),
|
|
62
|
+
[schema],
|
|
63
|
+
);
|
|
65
64
|
|
|
66
65
|
const [selectedView, setSelectedView] = useState<string | null>(
|
|
67
66
|
traceViews.length > 0 ? traceViews[0].name : null,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart-view ordering.
|
|
3
|
+
*
|
|
4
|
+
* `views` is a JSON object and the server holds it in a `BTreeMap`, so what
|
|
5
|
+
* reaches the HMI is sorted by view id — never by the order anyone chose. That
|
|
6
|
+
* order is a real setting: the Data tab opens on the FIRST view, so whichever
|
|
7
|
+
* id happens to sort first becomes the default chart. Renaming views to spell
|
|
8
|
+
* an order is not an option (the id is the wire key, and codegen emits it).
|
|
9
|
+
*
|
|
10
|
+
* So the order is carried explicitly, per view, as `order`: an integer,
|
|
11
|
+
* ascending, renumbered densely by the editor's Move up / Move down.
|
|
12
|
+
*
|
|
13
|
+
* `order` is deliberately NOT modelled by the Rust `ChartView`. It rides in
|
|
14
|
+
* that struct's flattened `extra` map, the way `x_decimals`, `smooth` and
|
|
15
|
+
* `regions` already do, so a `tis.put_method` → `tis.save_config` round-trip
|
|
16
|
+
* preserves it with no server-side change to deploy.
|
|
17
|
+
*
|
|
18
|
+
* A view with no `order` sorts after every ordered one, by id — so a method
|
|
19
|
+
* that has never been through the editor keeps exactly the order it shows
|
|
20
|
+
* today, and a newly added view lands last instead of jumping to the front.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Minimal shape this module needs; both the editor's and the runtime's
|
|
24
|
+
* `ChartView` satisfy it. */
|
|
25
|
+
export interface OrderableView {
|
|
26
|
+
order?: number;
|
|
27
|
+
type?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A view together with its id — the id is the wire key, not a field. */
|
|
31
|
+
export interface NamedView<V> {
|
|
32
|
+
name: string;
|
|
33
|
+
view: V;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Sort key for a view that declares no `order`: after all ordered views. */
|
|
37
|
+
const UNORDERED = Number.MAX_SAFE_INTEGER;
|
|
38
|
+
|
|
39
|
+
const orderOf = (v: OrderableView | undefined): number =>
|
|
40
|
+
typeof v?.order === 'number' && Number.isFinite(v.order) ? v.order : UNORDERED;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The method's views in display order: by `order` ascending, then by id.
|
|
44
|
+
*
|
|
45
|
+
* @param views The method's `views` map, as received from the server.
|
|
46
|
+
* @param ofType Optional `type` filter (e.g. `'raw_trace'`).
|
|
47
|
+
*/
|
|
48
|
+
export function orderedViews<V extends OrderableView>(
|
|
49
|
+
views?: Record<string, V> | null,
|
|
50
|
+
ofType?: string,
|
|
51
|
+
): NamedView<V>[] {
|
|
52
|
+
const out: NamedView<V>[] = [];
|
|
53
|
+
for (const [name, view] of Object.entries(views ?? {})) {
|
|
54
|
+
if (ofType && view?.type !== ofType) continue;
|
|
55
|
+
out.push({ name, view });
|
|
56
|
+
}
|
|
57
|
+
out.sort((a, b) => {
|
|
58
|
+
const d = orderOf(a.view) - orderOf(b.view);
|
|
59
|
+
return d !== 0 ? d : a.name.localeCompare(b.name);
|
|
60
|
+
});
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Stamp `order` 0..n-1 onto `views` following `orderedIds`.
|
|
66
|
+
*
|
|
67
|
+
* Every view is renumbered, not just the moved one: dense integers mean the
|
|
68
|
+
* next move is a swap of two adjacent numbers, and a method that was
|
|
69
|
+
* previously unordered comes out fully ordered after one Move rather than
|
|
70
|
+
* half-and-half.
|
|
71
|
+
*
|
|
72
|
+
* Ids in `views` but missing from `orderedIds` keep their existing `order`
|
|
73
|
+
* and land after the renumbered ones.
|
|
74
|
+
*/
|
|
75
|
+
export function renumberViews<V extends OrderableView>(
|
|
76
|
+
views: Record<string, V>,
|
|
77
|
+
orderedIds: string[],
|
|
78
|
+
): Record<string, V> {
|
|
79
|
+
const out: Record<string, V> = {};
|
|
80
|
+
orderedIds.forEach((id, i) => {
|
|
81
|
+
const v = views[id];
|
|
82
|
+
if (v) out[id] = { ...v, order: i };
|
|
83
|
+
});
|
|
84
|
+
for (const [id, v] of Object.entries(views)) {
|
|
85
|
+
if (!(id in out)) out[id] = v;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Move the view at `index` by `dir` (-1 up, +1 down) and renumber.
|
|
92
|
+
* Out-of-range moves return the map unchanged.
|
|
93
|
+
*/
|
|
94
|
+
export function moveView<V extends OrderableView>(
|
|
95
|
+
views: Record<string, V>,
|
|
96
|
+
index: number,
|
|
97
|
+
dir: -1 | 1,
|
|
98
|
+
): Record<string, V> {
|
|
99
|
+
const ids = orderedViews(views).map(v => v.name);
|
|
100
|
+
const j = index + dir;
|
|
101
|
+
if (index < 0 || index >= ids.length || j < 0 || j >= ids.length) return views;
|
|
102
|
+
[ids[index], ids[j]] = [ids[j], ids[index]];
|
|
103
|
+
return renumberViews(views, ids);
|
|
104
|
+
}
|
|
@@ -5,6 +5,7 @@ import { InputText } from 'primereact/inputtext';
|
|
|
5
5
|
import { Dropdown } from 'primereact/dropdown';
|
|
6
6
|
import { InputNumber } from 'primereact/inputnumber';
|
|
7
7
|
import { FormRow } from '../../forms/FormRow';
|
|
8
|
+
import { useEditDraft } from '../../forms/useEditDraft';
|
|
8
9
|
import { DEFAULT_X_DECIMALS } from '../../tis/TestDataView';
|
|
9
10
|
import type { ChartAxis, ChartSeries, ChartView } from '../types';
|
|
10
11
|
|
|
@@ -43,17 +44,30 @@ const blank = (): ChartView => ({
|
|
|
43
44
|
export const ChartViewDialog: React.FC<ChartViewDialogProps> = ({
|
|
44
45
|
visible, initial, onCancel, onSave, knownKeys, siblingIds,
|
|
45
46
|
}) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// One draft covering both edited values (the id is editable too), seeded
|
|
48
|
+
// once per open. Keyed on the view id rather than on `initial` — the
|
|
49
|
+
// parent rebuilds that object on every render of its own, and this editor
|
|
50
|
+
// re-renders with the live tag traffic underneath it, so an identity-keyed
|
|
51
|
+
// reseed threw away every keystroke as it was typed.
|
|
52
|
+
const { draft: form, setDraft: setForm, token } = useEditDraft(
|
|
53
|
+
visible,
|
|
54
|
+
initial?.viewId ?? '',
|
|
55
|
+
() => ({
|
|
56
|
+
viewId: initial?.viewId ?? '',
|
|
57
|
+
view: initial
|
|
58
|
+
? (JSON.parse(JSON.stringify(initial.view)) as ChartView)
|
|
59
|
+
: blank(),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
const viewId = form.viewId;
|
|
63
|
+
const draft = form.view;
|
|
64
|
+
const setViewId = (id: string) => setForm(f => ({ ...f, viewId: id }));
|
|
65
|
+
const setDraft = (view: ChartView) => setForm(f => ({ ...f, view }));
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
setError(null);
|
|
55
|
-
}
|
|
56
|
-
}, [visible, initial]);
|
|
67
|
+
const [error, setError] = useState<string | null>(null);
|
|
68
|
+
// Clears on open and on a switch to another view — `token` changes on
|
|
69
|
+
// exactly those transitions and on no other render.
|
|
70
|
+
useEffect(() => { setError(null); }, [token]);
|
|
57
71
|
|
|
58
72
|
const validate = (): string | null => {
|
|
59
73
|
if (!viewId.trim()) return 'View ID is required.';
|
|
@@ -7,6 +7,7 @@ import { Dropdown } from 'primereact/dropdown';
|
|
|
7
7
|
import { Checkbox } from 'primereact/checkbox';
|
|
8
8
|
import { InputNumber } from 'primereact/inputnumber';
|
|
9
9
|
import { FormRow } from '../../forms/FormRow';
|
|
10
|
+
import { useEditDraft } from '../../forms/useEditDraft';
|
|
10
11
|
import type { TestField, AggFn, AggFrom, AggScope, AggregateSpec } from '../types';
|
|
11
12
|
// One home for bound parsing/formatting — shared with the operator-facing
|
|
12
13
|
// form so the editor and the runtime can never disagree on what a bound is.
|
|
@@ -82,11 +83,31 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
|
|
|
82
83
|
isResultsField = false, isCycleConfigField = false,
|
|
83
84
|
cycleFieldNames = [], resultsFieldNames = [],
|
|
84
85
|
}) => {
|
|
85
|
-
|
|
86
|
-
// `default` is edited as free text so a numeric literal, a
|
|
87
|
-
// `${fqdn}` token can all be entered in one field; it's
|
|
88
|
-
|
|
86
|
+
// The field under edit plus its free-text Default, as one draft seeded
|
|
87
|
+
// once per open. `default` is edited as free text so a numeric literal, a
|
|
88
|
+
// string, or an `${fqdn}` token can all be entered in one field; it's
|
|
89
|
+
// coerced on save.
|
|
90
|
+
//
|
|
91
|
+
// Keyed on the field NAME, not on the `initial` object: this editor
|
|
92
|
+
// re-renders with the live tag traffic underneath it, and an
|
|
93
|
+
// identity-keyed reseed discards the operator's typing (see
|
|
94
|
+
// useEditDraft).
|
|
95
|
+
const { draft: form, setDraft: setForm, token } = useEditDraft(
|
|
96
|
+
visible,
|
|
97
|
+
initial?.name ?? '',
|
|
98
|
+
() => ({
|
|
99
|
+
field: initial ? { ...initial } : { ...blank },
|
|
100
|
+
defaultText: initial?.default == null ? '' : String(initial.default),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
const draft = form.field;
|
|
104
|
+
const defaultText = form.defaultText;
|
|
105
|
+
const setDraft = (field: TestField) => setForm(f => ({ ...f, field }));
|
|
106
|
+
const setDefaultText = (defaultText: string) => setForm(f => ({ ...f, defaultText }));
|
|
107
|
+
|
|
89
108
|
const [error, setError] = useState<string | null>(null);
|
|
109
|
+
// Clears on open and on a switch to another field, and on no other render.
|
|
110
|
+
useEffect(() => { setError(null); }, [token]);
|
|
90
111
|
|
|
91
112
|
// The quantity names this machine actually declares, from the resolved
|
|
92
113
|
// units table. Offered as a dropdown so an author picks a row that exists
|
|
@@ -99,14 +120,6 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
|
|
|
99
120
|
[scales],
|
|
100
121
|
);
|
|
101
122
|
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (visible) {
|
|
104
|
-
setDraft(initial ? { ...initial } : { ...blank });
|
|
105
|
-
setDefaultText(initial?.default == null ? '' : String(initial.default));
|
|
106
|
-
setError(null);
|
|
107
|
-
}
|
|
108
|
-
}, [visible, initial]);
|
|
109
|
-
|
|
110
123
|
// Turn the free-text Default into its stored form: an `${fqdn}` token and
|
|
111
124
|
// other non-numerics stay strings; numeric/bool literals are stored typed.
|
|
112
125
|
const coerceDefault = (s: string): unknown => {
|
|
@@ -3,6 +3,7 @@ import { DataTable } from 'primereact/datatable';
|
|
|
3
3
|
import { Column } from 'primereact/column';
|
|
4
4
|
import { Button } from 'primereact/button';
|
|
5
5
|
import { FormSection } from '../../forms/FormSection';
|
|
6
|
+
import { moveView, orderedViews } from '../../tis/chartViews';
|
|
6
7
|
import { ChartViewDialog } from './ChartViewDialog';
|
|
7
8
|
import type { ChartView, TestField, TestMethod } from '../types';
|
|
8
9
|
|
|
@@ -19,8 +20,11 @@ interface ViewRow {
|
|
|
19
20
|
|
|
20
21
|
export const ViewsEditor: React.FC<ViewsEditorProps> = ({ method, onChange }) => {
|
|
21
22
|
const views = (method.views as Record<string, ChartView>) ?? {};
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
// Display order, not id order — the operator sets it with the arrows and
|
|
24
|
+
// the runtime reads the same `order` key. The first row is the view the
|
|
25
|
+
// Data tab opens on.
|
|
26
|
+
const rows: ViewRow[] = orderedViews(views).map(({ name, view }) => ({
|
|
27
|
+
id: name, title: view.title ?? '', type: view.type ?? '',
|
|
24
28
|
}));
|
|
25
29
|
|
|
26
30
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
@@ -57,6 +61,14 @@ export const ViewsEditor: React.FC<ViewsEditorProps> = ({ method, onChange }) =>
|
|
|
57
61
|
setDialogOpen(false);
|
|
58
62
|
};
|
|
59
63
|
|
|
64
|
+
// Move up / down renumbers `order` densely across every view, so the next
|
|
65
|
+
// move is a swap of adjacent integers whatever the method started as.
|
|
66
|
+
const handleMove = (idx: number, dir: -1 | 1) => {
|
|
67
|
+
const next = moveView(views, idx, dir);
|
|
68
|
+
if (next === views) return;
|
|
69
|
+
onChange({ ...method, views: next });
|
|
70
|
+
};
|
|
71
|
+
|
|
60
72
|
const handleRemove = (id: string) => {
|
|
61
73
|
if (!window.confirm(`Remove view "${id}"?`)) return;
|
|
62
74
|
const next = { ...views };
|
|
@@ -64,8 +76,22 @@ export const ViewsEditor: React.FC<ViewsEditorProps> = ({ method, onChange }) =>
|
|
|
64
76
|
onChange({ ...method, views: next });
|
|
65
77
|
};
|
|
66
78
|
|
|
67
|
-
const rowActions = (r: ViewRow) => (
|
|
79
|
+
const rowActions = (r: ViewRow, opts: { rowIndex: number }) => (
|
|
68
80
|
<div style={{ display: 'flex', gap: '0.25rem' }}>
|
|
81
|
+
<Button
|
|
82
|
+
icon="pi pi-arrow-up"
|
|
83
|
+
className="p-button-text p-button-sm"
|
|
84
|
+
disabled={opts.rowIndex === 0}
|
|
85
|
+
onClick={() => handleMove(opts.rowIndex, -1)}
|
|
86
|
+
aria-label="Move up"
|
|
87
|
+
/>
|
|
88
|
+
<Button
|
|
89
|
+
icon="pi pi-arrow-down"
|
|
90
|
+
className="p-button-text p-button-sm"
|
|
91
|
+
disabled={opts.rowIndex === rows.length - 1}
|
|
92
|
+
onClick={() => handleMove(opts.rowIndex, 1)}
|
|
93
|
+
aria-label="Move down"
|
|
94
|
+
/>
|
|
69
95
|
<Button
|
|
70
96
|
icon="pi pi-pencil"
|
|
71
97
|
className="p-button-text p-button-sm"
|
|
@@ -85,14 +111,14 @@ export const ViewsEditor: React.FC<ViewsEditorProps> = ({ method, onChange }) =>
|
|
|
85
111
|
<>
|
|
86
112
|
<FormSection
|
|
87
113
|
title="Views"
|
|
88
|
-
description="Named chart definitions rendered by <TestDataView> (cycle_scatter) and <TestRawDataView> (raw_trace)."
|
|
114
|
+
description="Named chart definitions rendered by <TestDataView> (cycle_scatter) and <TestRawDataView> (raw_trace). Top to bottom is the order the operator sees in the view picker, and the first one is the chart the Data tab opens on."
|
|
89
115
|
actions={<Button label="Add view" icon="pi pi-plus" size="small" onClick={openNew} />}
|
|
90
116
|
>
|
|
91
117
|
<DataTable value={rows} dataKey="id" emptyMessage="No views defined.">
|
|
92
118
|
<Column field="id" header="View ID" />
|
|
93
119
|
<Column field="title" header="Title" />
|
|
94
120
|
<Column field="type" header="Type" style={{ width: '8rem' }} />
|
|
95
|
-
<Column header="" body={rowActions} style={{ width: '
|
|
121
|
+
<Column header="" body={rowActions} style={{ width: '10rem' }} />
|
|
96
122
|
</DataTable>
|
|
97
123
|
</FormSection>
|
|
98
124
|
<ChartViewDialog
|
|
@@ -104,6 +104,12 @@ export interface ChartView {
|
|
|
104
104
|
* prints 17-digit ticks.
|
|
105
105
|
*/
|
|
106
106
|
x_decimals?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Position in the view picker, ascending. Set by the editor's Move
|
|
109
|
+
* up / Move down; see `components/tis/chartViews.ts` for why the order
|
|
110
|
+
* cannot just be the order of the keys.
|
|
111
|
+
*/
|
|
112
|
+
order?: number;
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
export type RawColumnSource = 'time' | 'derived' | string; // also `ni.<daq>.channels.<name>`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.ac-formsection {
|
|
2
|
+
border: 1px solid var(--surface-d, #e2e8f0);
|
|
3
|
+
border-radius: 6px;
|
|
4
|
+
background: var(--surface-card, #fff);
|
|
5
|
+
margin-bottom: 1rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ac-formsection__header {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: flex-start;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
padding: 0.5rem 1rem;
|
|
13
|
+
border-bottom: 1px solid var(--surface-d, #e2e8f0);
|
|
14
|
+
background: var(--surface-b, #f8fafc);
|
|
15
|
+
border-top-left-radius: 6px;
|
|
16
|
+
border-top-right-radius: 6px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ac-formsection__title {
|
|
20
|
+
margin: 0;
|
|
21
|
+
font-size: 0.95rem;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ac-formsection__desc {
|
|
26
|
+
color: var(--text-color-secondary, #64748b);
|
|
27
|
+
display: block;
|
|
28
|
+
margin-top: 0.15rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.ac-formsection__actions {
|
|
32
|
+
display: flex;
|
|
33
|
+
gap: 0.5rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ac-formsection__body {
|
|
37
|
+
padding: 0.75rem 1rem;
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: 0.5rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ac-formrow {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-columns: minmax(8rem, 14rem) 1fr;
|
|
46
|
+
gap: 0.5rem 1rem;
|
|
47
|
+
align-items: start;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.ac-formrow--error .ac-formrow__field input,
|
|
51
|
+
.ac-formrow--error .ac-formrow__field .p-inputtext {
|
|
52
|
+
border-color: #dc2626;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ac-formrow__label {
|
|
56
|
+
font-weight: 500;
|
|
57
|
+
padding-top: 0.4rem;
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ac-formrow__required {
|
|
63
|
+
color: #dc2626;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ac-formrow__hint {
|
|
67
|
+
color: var(--text-color-secondary, #64748b);
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
font-size: 0.75rem;
|
|
70
|
+
margin-top: 0.15rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ac-formrow__field {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: 0.25rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ac-formrow__field > input,
|
|
80
|
+
.ac-formrow__field > .p-inputtext,
|
|
81
|
+
.ac-formrow__field > .p-dropdown,
|
|
82
|
+
.ac-formrow__field > .p-inputtextarea {
|
|
83
|
+
width: 100%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ac-formrow__error {
|
|
87
|
+
color: #dc2626;
|
|
88
|
+
font-size: 0.75rem;
|
|
89
|
+
}
|
|
90
|
+
/*$vite$:1*/
|