@hyddenlabs/hydn-ui 0.3.6 → 0.3.7
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/checkbox/checkbox.js +1 -1
- package/dist/components/forms/checkbox/checkbox.js.map +1 -1
- package/dist/components/forms/form-grid/form-grid-row.d.ts +44 -0
- package/dist/components/forms/form-grid/form-grid-row.d.ts.map +1 -0
- package/dist/components/forms/form-grid/form-grid-row.js +257 -0
- package/dist/components/forms/form-grid/form-grid-row.js.map +1 -0
- package/dist/components/forms/form-grid/form-grid.d.ts +115 -0
- package/dist/components/forms/form-grid/form-grid.d.ts.map +1 -0
- package/dist/components/forms/form-grid/form-grid.js +265 -0
- package/dist/components/forms/form-grid/form-grid.js.map +1 -0
- package/dist/components/forms/form-grid/index.d.ts +3 -0
- package/dist/components/forms/form-grid/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.js +116 -114
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -28,7 +28,7 @@ function Checkbox({
|
|
|
28
28
|
"aria-describedby": ariaDescribedby,
|
|
29
29
|
id,
|
|
30
30
|
name,
|
|
31
|
-
className: `h-5 w-5 sm:h-4 sm:w-4 rounded border-2 bg-background text-primary-foreground transition-all duration-200 ease-in-out focus:
|
|
31
|
+
className: `h-5 w-5 sm:h-4 sm:w-4 rounded border-2 bg-background text-primary-foreground transition-all duration-200 ease-in-out focus:ring-primary focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted/50 checked:bg-primary checked:border-primary checked:hover:bg-primary/90 hover:border-primary/50 hover:shadow-sm cursor-pointer accent-primary ${validationClasses[validationState]} ${className}`
|
|
32
32
|
}
|
|
33
33
|
);
|
|
34
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.js","sources":["../../../../src/components/forms/checkbox/checkbox.tsx"],"sourcesContent":["import React from 'react';\nimport { ValidationState } from '../../../theme/size-tokens';\n\nexport type CheckboxProps = {\n /** Checked state (controlled) */\n checked?: boolean;\n /** Change event handler for controlled checkbox */\n onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n /** Disables checkbox interaction and applies disabled styling */\n disabled?: boolean;\n /** Additional CSS classes applied to the checkbox */\n className?: string;\n /** Accessible label for screen readers */\n ariaLabel?: string;\n /** HTML id attribute for the checkbox */\n id?: string;\n /** HTML name attribute for form submission */\n name?: string;\n /** Visual validation state affecting border and focus ring color */\n validationState?: ValidationState;\n /** Associates the checkbox with descriptive text (e.g., helper or error messages) */\n 'aria-describedby'?: string;\n};\n\n/**\n * Accessible Checkbox component\n */\nfunction Checkbox({\n checked,\n onChange,\n disabled = false,\n className = '',\n ariaLabel,\n id,\n name,\n validationState = 'neutral',\n 'aria-describedby': ariaDescribedby\n}: Readonly<CheckboxProps>) {\n const validationClasses = {\n neutral: 'border-input focus:ring-ring',\n error: 'border-error focus:ring-error',\n success: 'border-success focus:ring-success',\n warning: 'border-warning focus:ring-warning'\n };\n\n return (\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={onChange}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-invalid={validationState === 'error'}\n aria-describedby={ariaDescribedby}\n id={id}\n name={name}\n className={`h-5 w-5 sm:h-4 sm:w-4 rounded border-2 bg-background text-primary-foreground transition-all duration-200 ease-in-out focus:
|
|
1
|
+
{"version":3,"file":"checkbox.js","sources":["../../../../src/components/forms/checkbox/checkbox.tsx"],"sourcesContent":["import React from 'react';\nimport { ValidationState } from '../../../theme/size-tokens';\n\nexport type CheckboxProps = {\n /** Checked state (controlled) */\n checked?: boolean;\n /** Change event handler for controlled checkbox */\n onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n /** Disables checkbox interaction and applies disabled styling */\n disabled?: boolean;\n /** Additional CSS classes applied to the checkbox */\n className?: string;\n /** Accessible label for screen readers */\n ariaLabel?: string;\n /** HTML id attribute for the checkbox */\n id?: string;\n /** HTML name attribute for form submission */\n name?: string;\n /** Visual validation state affecting border and focus ring color */\n validationState?: ValidationState;\n /** Associates the checkbox with descriptive text (e.g., helper or error messages) */\n 'aria-describedby'?: string;\n};\n\n/**\n * Accessible Checkbox component\n */\nfunction Checkbox({\n checked,\n onChange,\n disabled = false,\n className = '',\n ariaLabel,\n id,\n name,\n validationState = 'neutral',\n 'aria-describedby': ariaDescribedby\n}: Readonly<CheckboxProps>) {\n const validationClasses = {\n neutral: 'border-input focus:ring-ring',\n error: 'border-error focus:ring-error',\n success: 'border-success focus:ring-success',\n warning: 'border-warning focus:ring-warning'\n };\n\n return (\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={onChange}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-invalid={validationState === 'error'}\n aria-describedby={ariaDescribedby}\n id={id}\n name={name}\n className={`h-5 w-5 sm:h-4 sm:w-4 rounded border-2 bg-background text-primary-foreground transition-all duration-200 ease-in-out focus:ring-primary focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted/50 checked:bg-primary checked:border-primary checked:hover:bg-primary/90 hover:border-primary/50 hover:shadow-sm cursor-pointer accent-primary ${validationClasses[validationState]} ${className}`}\n />\n );\n}\n\nCheckbox.displayName = 'Checkbox';\n\nexport default Checkbox;\n"],"names":[],"mappings":";AA2BA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,oBAAoB;AACtB,GAA4B;AAC1B,QAAM,oBAAoB;AAAA,IACxB,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,EAAA;AAGX,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAY;AAAA,MACZ,gBAAc,oBAAoB;AAAA,MAClC,oBAAkB;AAAA,MAClB;AAAA,MACA;AAAA,MACA,WAAW,gXAAgX,kBAAkB,eAAe,CAAC,IAAI,SAAS;AAAA,IAAA;AAAA,EAAA;AAGhb;AAEA,SAAS,cAAc;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FormGridColumn, FormGridRowData } from './form-grid';
|
|
2
|
+
import { InteractiveSize, ValidationState } from '../../../theme/size-tokens';
|
|
3
|
+
export type FormGridRowProps = {
|
|
4
|
+
/** Row data */
|
|
5
|
+
row: FormGridRowData;
|
|
6
|
+
/** Column definitions */
|
|
7
|
+
columns: FormGridColumn[];
|
|
8
|
+
/** Current nesting depth (0 = root) */
|
|
9
|
+
depth: number;
|
|
10
|
+
/** Maximum allowed nesting depth */
|
|
11
|
+
maxDepth: number;
|
|
12
|
+
/** Whether in mobile layout mode */
|
|
13
|
+
isMobile: boolean;
|
|
14
|
+
/** Size variant for form controls */
|
|
15
|
+
size: InteractiveSize;
|
|
16
|
+
/** Whether sub-rows are allowed (manual add button) */
|
|
17
|
+
allowSubRows: boolean;
|
|
18
|
+
/** Auto sub-row callback (when provided, sub-rows are auto-managed, no manual button) */
|
|
19
|
+
autoSubRow?: (row: FormGridRowData) => boolean;
|
|
20
|
+
/** Whether the row is disabled */
|
|
21
|
+
disabled: boolean;
|
|
22
|
+
/** Label for add sub-row button */
|
|
23
|
+
addSubRowLabel: string;
|
|
24
|
+
/** Callback when a field value changes */
|
|
25
|
+
onValueChange: (rowId: string, key: string, value: unknown) => void;
|
|
26
|
+
/** Callback to add a sub-row */
|
|
27
|
+
onAddSubRow: (parentId: string) => void;
|
|
28
|
+
/** Callback to remove this row */
|
|
29
|
+
onRemove: (id: string) => void;
|
|
30
|
+
/** Whether this row can be removed */
|
|
31
|
+
canRemove: boolean;
|
|
32
|
+
/** Validation states for fields */
|
|
33
|
+
validationStates?: Record<string, ValidationState>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* FormGridRow - Recursive row component for FormGrid
|
|
37
|
+
* Handles rendering of field controls and nested sub-rows
|
|
38
|
+
*/
|
|
39
|
+
declare function FormGridRow({ row, columns, depth, maxDepth, isMobile, size, allowSubRows, autoSubRow, disabled, addSubRowLabel, onValueChange, onAddSubRow, onRemove, canRemove, validationStates }: Readonly<FormGridRowProps>): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
declare namespace FormGridRow {
|
|
41
|
+
var displayName: string;
|
|
42
|
+
}
|
|
43
|
+
export default FormGridRow;
|
|
44
|
+
//# sourceMappingURL=form-grid-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-grid-row.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAInF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe;IACf,GAAG,EAAE,eAAe,CAAC;IACrB,yBAAyB;IACzB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,IAAI,EAAE,eAAe,CAAC;IACtB,uDAAuD;IACvD,YAAY,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,gCAAgC;IAChC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kCAAkC;IAClC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACpD,CAAC;AAmDF;;;GAGG;AACH,iBAAS,WAAW,CAAC,EACnB,GAAG,EACH,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,WAAW,EACX,QAAQ,EACR,SAAS,EACT,gBAAqB,EACtB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,2CAmN5B;kBAnOQ,WAAW;;;AAuOpB,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import Input from "../input/input.js";
|
|
4
|
+
import Select from "../select/select.js";
|
|
5
|
+
import SelectItem from "../select/select-item.js";
|
|
6
|
+
import Checkbox from "../checkbox/checkbox.js";
|
|
7
|
+
import Textarea from "../textarea/textarea.js";
|
|
8
|
+
import IconButton from "../button/icon-button.js";
|
|
9
|
+
import { IconChevronRight, IconChevronDown } from "@tabler/icons-react";
|
|
10
|
+
import ButtonWithIcon from "../button/button-with-icon.js";
|
|
11
|
+
const depthIndentClasses = {
|
|
12
|
+
0: "",
|
|
13
|
+
1: "pl-8",
|
|
14
|
+
2: "pl-16",
|
|
15
|
+
3: "pl-24",
|
|
16
|
+
4: "pl-32",
|
|
17
|
+
5: "pl-40"
|
|
18
|
+
};
|
|
19
|
+
function getCheckboxColumnWidth(label) {
|
|
20
|
+
const minCh = 8;
|
|
21
|
+
return `${Math.max(minCh, label.length + 2)}ch`;
|
|
22
|
+
}
|
|
23
|
+
function tailwindWidthToCss(width) {
|
|
24
|
+
if (width === "flex-1" || width === "w-full") return "minmax(0, 1fr)";
|
|
25
|
+
if (width === "w-auto") return "auto";
|
|
26
|
+
const numMatch = width.match(/^w-(\d+)$/);
|
|
27
|
+
if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;
|
|
28
|
+
const fracMatch = width.match(/^w-(\d+)\/(\d+)$/);
|
|
29
|
+
if (fracMatch) return `${(parseInt(fracMatch[1]) / parseInt(fracMatch[2]) * 100).toFixed(2)}%`;
|
|
30
|
+
if (/\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {
|
|
31
|
+
return width;
|
|
32
|
+
}
|
|
33
|
+
return "auto";
|
|
34
|
+
}
|
|
35
|
+
function getDesktopGridTemplate(columns) {
|
|
36
|
+
const columnTracks = columns.map((column) => {
|
|
37
|
+
if (column.width) {
|
|
38
|
+
return tailwindWidthToCss(column.width);
|
|
39
|
+
}
|
|
40
|
+
if (column.type === "checkbox") {
|
|
41
|
+
return getCheckboxColumnWidth(column.label);
|
|
42
|
+
}
|
|
43
|
+
return "minmax(0, 1fr)";
|
|
44
|
+
});
|
|
45
|
+
return `${columnTracks.join(" ")} 4rem`;
|
|
46
|
+
}
|
|
47
|
+
function FormGridRow({
|
|
48
|
+
row,
|
|
49
|
+
columns,
|
|
50
|
+
depth,
|
|
51
|
+
maxDepth,
|
|
52
|
+
isMobile,
|
|
53
|
+
size,
|
|
54
|
+
allowSubRows,
|
|
55
|
+
autoSubRow,
|
|
56
|
+
disabled,
|
|
57
|
+
addSubRowLabel,
|
|
58
|
+
onValueChange,
|
|
59
|
+
onAddSubRow,
|
|
60
|
+
onRemove,
|
|
61
|
+
canRemove,
|
|
62
|
+
validationStates = {}
|
|
63
|
+
}) {
|
|
64
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
65
|
+
const desktopGridTemplate = getDesktopGridTemplate(columns);
|
|
66
|
+
const showAddSubRowButton = allowSubRows && depth < maxDepth;
|
|
67
|
+
const hasSubRows = row.subRows && row.subRows.length > 0;
|
|
68
|
+
const renderField = (column) => {
|
|
69
|
+
const value = row.values[column.key];
|
|
70
|
+
const validationState = validationStates[column.key] || "neutral";
|
|
71
|
+
const handleChange = (newValue) => {
|
|
72
|
+
onValueChange(row.id, column.key, newValue);
|
|
73
|
+
};
|
|
74
|
+
switch (column.type) {
|
|
75
|
+
case "input":
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
Input,
|
|
78
|
+
{
|
|
79
|
+
value: value ?? "",
|
|
80
|
+
onChange: (e) => handleChange(e.target.value),
|
|
81
|
+
placeholder: column.placeholder,
|
|
82
|
+
type: column.inputType || "text",
|
|
83
|
+
size,
|
|
84
|
+
disabled,
|
|
85
|
+
required: column.required,
|
|
86
|
+
validationState
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
case "select":
|
|
90
|
+
return /* @__PURE__ */ jsx(
|
|
91
|
+
Select,
|
|
92
|
+
{
|
|
93
|
+
value: value ?? "",
|
|
94
|
+
onChange: (e) => handleChange(e.target.value),
|
|
95
|
+
size,
|
|
96
|
+
disabled,
|
|
97
|
+
required: column.required,
|
|
98
|
+
validationState,
|
|
99
|
+
label: column.placeholder,
|
|
100
|
+
children: column.options?.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value))
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
case "checkbox":
|
|
104
|
+
return /* @__PURE__ */ jsx("div", { className: `flex items-center h-full ${!isMobile ? "justify-center" : ""}`, children: /* @__PURE__ */ jsx(
|
|
105
|
+
Checkbox,
|
|
106
|
+
{
|
|
107
|
+
checked: Boolean(value),
|
|
108
|
+
onChange: (e) => handleChange(e.target.checked),
|
|
109
|
+
disabled,
|
|
110
|
+
validationState
|
|
111
|
+
}
|
|
112
|
+
) });
|
|
113
|
+
case "textarea":
|
|
114
|
+
return /* @__PURE__ */ jsx(
|
|
115
|
+
Textarea,
|
|
116
|
+
{
|
|
117
|
+
value: value ?? "",
|
|
118
|
+
onChange: (e) => handleChange(e.target.value),
|
|
119
|
+
placeholder: column.placeholder,
|
|
120
|
+
disabled,
|
|
121
|
+
required: column.required,
|
|
122
|
+
validationState
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
case "custom":
|
|
126
|
+
if (column.render) {
|
|
127
|
+
return column.render(value, row, handleChange, 0);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
default:
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
135
|
+
/* @__PURE__ */ jsxs(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
className: `
|
|
139
|
+
${isMobile ? "flex flex-col gap-3 border border-border rounded-lg bg-muted/20 px-3 py-3" : "grid items-center gap-3 py-1"}
|
|
140
|
+
`,
|
|
141
|
+
style: !isMobile ? { gridTemplateColumns: desktopGridTemplate } : void 0,
|
|
142
|
+
children: [
|
|
143
|
+
columns.map((column) => /* @__PURE__ */ jsxs(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
className: `${isMobile ? "w-full" : "min-w-0"} ${column.type === "checkbox" && !isMobile ? "flex justify-center" : ""}`,
|
|
147
|
+
children: [
|
|
148
|
+
isMobile && /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-muted-foreground mb-1", children: [
|
|
149
|
+
column.label,
|
|
150
|
+
column.required && /* @__PURE__ */ jsx("span", { className: "text-error ml-0.5", children: "*" })
|
|
151
|
+
] }),
|
|
152
|
+
renderField(column)
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
column.key
|
|
156
|
+
)),
|
|
157
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center gap-1 ${isMobile ? "justify-end" : "w-16 shrink-0 justify-end"}`, children: [
|
|
158
|
+
showAddSubRowButton && /* @__PURE__ */ jsx(
|
|
159
|
+
IconButton,
|
|
160
|
+
{
|
|
161
|
+
icon: "hierarchy-2",
|
|
162
|
+
ariaLabel: addSubRowLabel,
|
|
163
|
+
onClick: () => onAddSubRow(row.id),
|
|
164
|
+
disabled,
|
|
165
|
+
size: "sm",
|
|
166
|
+
buttonStyle: "ghost"
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
canRemove && !isMobile && /* @__PURE__ */ jsx(
|
|
170
|
+
IconButton,
|
|
171
|
+
{
|
|
172
|
+
icon: "trash",
|
|
173
|
+
ariaLabel: "Remove row",
|
|
174
|
+
onClick: () => onRemove(row.id),
|
|
175
|
+
disabled,
|
|
176
|
+
size: "sm",
|
|
177
|
+
buttonStyle: "link",
|
|
178
|
+
className: "p-0!",
|
|
179
|
+
variant: "error"
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
canRemove && isMobile && /* @__PURE__ */ jsx(
|
|
183
|
+
ButtonWithIcon,
|
|
184
|
+
{
|
|
185
|
+
icon: "trash",
|
|
186
|
+
ariaLabel: "Remove row",
|
|
187
|
+
onClick: () => onRemove(row.id),
|
|
188
|
+
disabled,
|
|
189
|
+
size,
|
|
190
|
+
buttonStyle: "link",
|
|
191
|
+
variant: "error",
|
|
192
|
+
iconPosition: "right",
|
|
193
|
+
children: "Delete Field"
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
] })
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
hasSubRows && /* @__PURE__ */ jsxs("div", { className: `mt-1 ${depthIndentClasses[Math.min(depth + 1, 5)] || ""}`, children: [
|
|
201
|
+
/* @__PURE__ */ jsxs(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
type: "button",
|
|
205
|
+
onClick: () => setIsCollapsed(!isCollapsed),
|
|
206
|
+
className: "cursor-pointer flex items-center gap-1 mb-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
207
|
+
"aria-label": isCollapsed ? "Expand sub-rows" : "Collapse sub-rows",
|
|
208
|
+
children: [
|
|
209
|
+
isCollapsed ? /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }),
|
|
210
|
+
/* @__PURE__ */ jsx("span", { children: isCollapsed ? "Show properties" : "Hide properties" })
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
!isCollapsed && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
215
|
+
row.subRows.map((subRow, index) => /* @__PURE__ */ jsx(
|
|
216
|
+
FormGridRow,
|
|
217
|
+
{
|
|
218
|
+
row: subRow,
|
|
219
|
+
columns,
|
|
220
|
+
depth: depth + 1,
|
|
221
|
+
maxDepth,
|
|
222
|
+
isMobile,
|
|
223
|
+
size,
|
|
224
|
+
allowSubRows,
|
|
225
|
+
autoSubRow,
|
|
226
|
+
disabled,
|
|
227
|
+
addSubRowLabel,
|
|
228
|
+
onValueChange,
|
|
229
|
+
onAddSubRow,
|
|
230
|
+
onRemove,
|
|
231
|
+
canRemove: autoSubRow ? index > 0 : true,
|
|
232
|
+
validationStates
|
|
233
|
+
},
|
|
234
|
+
subRow.id
|
|
235
|
+
)),
|
|
236
|
+
depth + 1 < maxDepth && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
|
|
237
|
+
ButtonWithIcon,
|
|
238
|
+
{
|
|
239
|
+
icon: "plus",
|
|
240
|
+
buttonStyle: "link",
|
|
241
|
+
ariaLabel: "Add Property",
|
|
242
|
+
onClick: () => onAddSubRow(row.id),
|
|
243
|
+
disabled,
|
|
244
|
+
size,
|
|
245
|
+
className: "pl-0!",
|
|
246
|
+
children: "Add Property"
|
|
247
|
+
}
|
|
248
|
+
) })
|
|
249
|
+
] })
|
|
250
|
+
] })
|
|
251
|
+
] });
|
|
252
|
+
}
|
|
253
|
+
FormGridRow.displayName = "FormGridRow";
|
|
254
|
+
export {
|
|
255
|
+
FormGridRow as default
|
|
256
|
+
};
|
|
257
|
+
//# sourceMappingURL=form-grid-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-grid-row.js","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"sourcesContent":["import { useState } from 'react';\nimport Input from '../input/input';\nimport Select from '../select/select';\nimport SelectItem from '../select/select-item';\nimport Checkbox from '../checkbox/checkbox';\nimport Textarea from '../textarea/textarea';\nimport IconButton from '../button/icon-button';\nimport type { FormGridColumn, FormGridRowData } from './form-grid';\nimport type { InteractiveSize, ValidationState } from '../../../theme/size-tokens';\nimport { ButtonWithIcon } from '../button';\nimport { IconChevronDown, IconChevronRight } from '@tabler/icons-react';\n\nexport type FormGridRowProps = {\n /** Row data */\n row: FormGridRowData;\n /** Column definitions */\n columns: FormGridColumn[];\n /** Current nesting depth (0 = root) */\n depth: number;\n /** Maximum allowed nesting depth */\n maxDepth: number;\n /** Whether in mobile layout mode */\n isMobile: boolean;\n /** Size variant for form controls */\n size: InteractiveSize;\n /** Whether sub-rows are allowed (manual add button) */\n allowSubRows: boolean;\n /** Auto sub-row callback (when provided, sub-rows are auto-managed, no manual button) */\n autoSubRow?: (row: FormGridRowData) => boolean;\n /** Whether the row is disabled */\n disabled: boolean;\n /** Label for add sub-row button */\n addSubRowLabel: string;\n /** Callback when a field value changes */\n onValueChange: (rowId: string, key: string, value: unknown) => void;\n /** Callback to add a sub-row */\n onAddSubRow: (parentId: string) => void;\n /** Callback to remove this row */\n onRemove: (id: string) => void;\n /** Whether this row can be removed */\n canRemove: boolean;\n /** Validation states for fields */\n validationStates?: Record<string, ValidationState>;\n};\n\n// Depth indent classes (using pl-* for left padding)\nconst depthIndentClasses: Record<number, string> = {\n 0: '',\n 1: 'pl-8',\n 2: 'pl-16',\n 3: 'pl-24',\n 4: 'pl-32',\n 5: 'pl-40'\n};\n\nfunction getCheckboxColumnWidth(label: string): string {\n const minCh = 8;\n return `${Math.max(minCh, label.length + 2)}ch`;\n}\n\n/**\n * Convert a Tailwind width utility class to a valid CSS grid track value.\n * e.g. 'w-24' → '6rem', 'flex-1' → 'minmax(0, 1fr)', 'w-1/2' → '50%'\n */\nfunction tailwindWidthToCss(width: string): string {\n if (width === 'flex-1' || width === 'w-full') return 'minmax(0, 1fr)';\n if (width === 'w-auto') return 'auto';\n // w-{number} → n * 0.25rem\n const numMatch = width.match(/^w-(\\d+)$/);\n if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;\n // w-{num}/{den} fraction\n const fracMatch = width.match(/^w-(\\d+)\\/(\\d+)$/);\n if (fracMatch) return `${((parseInt(fracMatch[1]) / parseInt(fracMatch[2])) * 100).toFixed(2)}%`;\n // Already a valid CSS value (contains a unit or keyword)\n if (/\\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {\n return width;\n }\n return 'auto';\n}\n\nfunction getDesktopGridTemplate(columns: FormGridColumn[]): string {\n const columnTracks = columns.map((column) => {\n if (column.width) {\n return tailwindWidthToCss(column.width);\n }\n if (column.type === 'checkbox') {\n return getCheckboxColumnWidth(column.label);\n }\n return 'minmax(0, 1fr)';\n });\n\n return `${columnTracks.join(' ')} 4rem`;\n}\n\n/**\n * FormGridRow - Recursive row component for FormGrid\n * Handles rendering of field controls and nested sub-rows\n */\nfunction FormGridRow({\n row,\n columns,\n depth,\n maxDepth,\n isMobile,\n size,\n allowSubRows,\n autoSubRow,\n disabled,\n addSubRowLabel,\n onValueChange,\n onAddSubRow,\n onRemove,\n canRemove,\n validationStates = {}\n}: Readonly<FormGridRowProps>) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n const desktopGridTemplate = getDesktopGridTemplate(columns);\n // Show manual add button only if allowSubRows is true (which is false when autoSubRow is provided)\n const showAddSubRowButton = allowSubRows && depth < maxDepth;\n const hasSubRows = row.subRows && row.subRows.length > 0;\n\n // Render a single field based on column type\n const renderField = (column: FormGridColumn) => {\n const value = row.values[column.key];\n const validationState = validationStates[column.key] || 'neutral';\n\n const handleChange = (newValue: unknown) => {\n onValueChange(row.id, column.key, newValue);\n };\n\n switch (column.type) {\n case 'input':\n return (\n <Input\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n type={column.inputType || 'text'}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'select':\n return (\n <Select\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n label={column.placeholder}\n >\n {column.options?.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </Select>\n );\n\n case 'checkbox':\n return (\n <div className={`flex items-center h-full ${!isMobile ? 'justify-center' : ''}`}>\n <Checkbox\n checked={Boolean(value)}\n onChange={(e) => handleChange(e.target.checked)}\n disabled={disabled}\n validationState={validationState}\n />\n </div>\n );\n\n case 'textarea':\n return (\n <Textarea\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'custom':\n if (column.render) {\n return column.render(value, row, handleChange, 0);\n }\n return null;\n\n default:\n return null;\n }\n };\n\n return (\n <div>\n {/* Row content */}\n <div\n className={`\n ${isMobile ? 'flex flex-col gap-3 border border-border rounded-lg bg-muted/20 px-3 py-3' : 'grid items-center gap-3 py-1'}\n `}\n style={!isMobile ? { gridTemplateColumns: desktopGridTemplate } : undefined}\n >\n {/* Fields */}\n {columns.map((column) => (\n <div\n key={column.key}\n className={`${isMobile ? 'w-full' : 'min-w-0'} ${column.type === 'checkbox' && !isMobile ? 'flex justify-center' : ''}`}\n >\n {/* Mobile: show label above field */}\n {isMobile && (\n <label className=\"block text-sm font-medium text-muted-foreground mb-1\">\n {column.label}\n {column.required && <span className=\"text-error ml-0.5\">*</span>}\n </label>\n )}\n {renderField(column)}\n </div>\n ))}\n\n {/* Action buttons */}\n <div className={`flex items-center gap-1 ${isMobile ? 'justify-end' : 'w-16 shrink-0 justify-end'}`}>\n {showAddSubRowButton && (\n <IconButton\n icon=\"hierarchy-2\"\n ariaLabel={addSubRowLabel}\n onClick={() => onAddSubRow(row.id)}\n disabled={disabled}\n size=\"sm\"\n buttonStyle=\"ghost\"\n />\n )}\n {canRemove && !isMobile && (\n <IconButton\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n size=\"sm\"\n buttonStyle=\"link\"\n className=\"p-0!\"\n variant=\"error\"\n />\n )}\n {canRemove && isMobile && (\n <ButtonWithIcon\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n size={size}\n buttonStyle=\"link\"\n variant=\"error\"\n iconPosition=\"right\"\n >\n Delete Field\n </ButtonWithIcon>\n )}\n </div>\n </div>\n\n {/* Sub-rows section with collapse toggle */}\n {hasSubRows && (\n <div className={`mt-1 ${depthIndentClasses[Math.min(depth + 1, 5)] || ''}`}>\n {/* Collapse toggle at the start of sub-rows */}\n <button\n type=\"button\"\n onClick={() => setIsCollapsed(!isCollapsed)}\n className=\"cursor-pointer flex items-center gap-1 mb-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={isCollapsed ? 'Expand sub-rows' : 'Collapse sub-rows'}\n >\n {isCollapsed ? <IconChevronRight size={14} /> : <IconChevronDown size={14} />}\n <span>{isCollapsed ? 'Show properties' : 'Hide properties'}</span>\n </button>\n\n {/* Sub-rows (recursive) - collapsible */}\n {!isCollapsed && (\n <div className=\"space-y-1\">\n {row.subRows!.map((subRow, index) => (\n <FormGridRow\n key={subRow.id}\n row={subRow}\n columns={columns}\n depth={depth + 1}\n maxDepth={maxDepth}\n isMobile={isMobile}\n size={size}\n allowSubRows={allowSubRows}\n autoSubRow={autoSubRow}\n disabled={disabled}\n addSubRowLabel={addSubRowLabel}\n onValueChange={onValueChange}\n onAddSubRow={onAddSubRow}\n onRemove={onRemove}\n canRemove={autoSubRow ? index > 0 : true}\n validationStates={validationStates}\n />\n ))}\n {/* Add field button within sub-row section */}\n {depth + 1 < maxDepth && (\n <div className=\"flex items-center gap-2\">\n <ButtonWithIcon\n icon=\"plus\"\n buttonStyle=\"link\"\n ariaLabel=\"Add Property\"\n onClick={() => onAddSubRow(row.id)}\n disabled={disabled}\n size={size}\n className=\"pl-0!\"\n >\n Add Property\n </ButtonWithIcon>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nFormGridRow.displayName = 'FormGridRow';\n\nexport default FormGridRow;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
import { InteractiveSize, ValidationState } from '../../../theme/size-tokens';
|
|
3
|
+
/**
|
|
4
|
+
* Column definition for FormGrid fields
|
|
5
|
+
*/
|
|
6
|
+
export type FormGridColumn = {
|
|
7
|
+
/** Unique key identifying this column in row data */
|
|
8
|
+
key: string;
|
|
9
|
+
/** Display label for the column header */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Type of form control to render */
|
|
12
|
+
type: 'input' | 'select' | 'checkbox' | 'textarea' | 'custom';
|
|
13
|
+
/** HTML input type (only for type='input') */
|
|
14
|
+
inputType?: 'text' | 'email' | 'number' | 'tel' | 'url' | 'password';
|
|
15
|
+
/** Placeholder text for input/textarea/select */
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
/** Options for select type */
|
|
18
|
+
options?: {
|
|
19
|
+
value: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}[];
|
|
22
|
+
/** Custom render function for type='custom' */
|
|
23
|
+
render?: (value: unknown, row: FormGridRowData, onChange: (value: unknown) => void, rowIndex: number) => ReactNode;
|
|
24
|
+
/** Width of the field (Tailwind width class or 'flex-1') */
|
|
25
|
+
width?: string;
|
|
26
|
+
/** Whether this field is required */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
/** Default value for new rows */
|
|
29
|
+
defaultValue?: unknown;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Row data structure with recursive sub-rows
|
|
33
|
+
*/
|
|
34
|
+
export type FormGridRowData = {
|
|
35
|
+
/** Unique identifier for the row */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Key-value map of field values */
|
|
38
|
+
values: Record<string, unknown>;
|
|
39
|
+
/** Nested sub-rows */
|
|
40
|
+
subRows?: FormGridRowData[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Methods exposed via ref for uncontrolled usage
|
|
44
|
+
*/
|
|
45
|
+
export type FormGridRef = {
|
|
46
|
+
/** Get all row data including sub-rows */
|
|
47
|
+
getData: () => FormGridRowData[];
|
|
48
|
+
/** Add a new row at root level or as sub-row of parentId */
|
|
49
|
+
addRow: (parentId?: string) => void;
|
|
50
|
+
/** Remove a row by id (searches recursively) */
|
|
51
|
+
removeRow: (id: string) => void;
|
|
52
|
+
/** Validate all rows, returns true if valid */
|
|
53
|
+
validate: () => boolean;
|
|
54
|
+
/** Reset to initial state with one empty row */
|
|
55
|
+
reset: () => void;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Props for the FormGrid component
|
|
59
|
+
*/
|
|
60
|
+
export type FormGridProps = {
|
|
61
|
+
/** Column definitions describing each field */
|
|
62
|
+
columns: FormGridColumn[];
|
|
63
|
+
/** Initial rows data (defaults to one empty row) */
|
|
64
|
+
defaultRows?: FormGridRowData[];
|
|
65
|
+
/** Callback when data changes */
|
|
66
|
+
onChange?: (rows: FormGridRowData[]) => void;
|
|
67
|
+
/** Size variant for form controls */
|
|
68
|
+
size?: InteractiveSize;
|
|
69
|
+
/** Maximum nesting depth for sub-rows (default: 3) */
|
|
70
|
+
maxDepth?: number;
|
|
71
|
+
/** Label for add row button */
|
|
72
|
+
addRowLabel?: string;
|
|
73
|
+
/** Label for add sub-row button */
|
|
74
|
+
addSubRowLabel?: string;
|
|
75
|
+
/** Whether to allow sub-rows (manual add button) */
|
|
76
|
+
allowSubRows?: boolean;
|
|
77
|
+
/** Callback to determine if a row should automatically have sub-rows. When provided, sub-rows are auto-managed based on row data (e.g., type === 'object'). No manual add button is shown. */
|
|
78
|
+
autoSubRow?: (row: FormGridRowData) => boolean;
|
|
79
|
+
/** Whether the entire grid is disabled */
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
/** Whether to show column headers */
|
|
82
|
+
showHeaders?: boolean;
|
|
83
|
+
/** Custom class name */
|
|
84
|
+
className?: string;
|
|
85
|
+
/** Validation state map by row id and column key */
|
|
86
|
+
validationStates?: Record<string, Record<string, ValidationState>>;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* FormGrid - Dynamic form builder with support for adding/removing rows and nested sub-rows.
|
|
90
|
+
*
|
|
91
|
+
* The component manages its own state internally and exposes methods via ref for
|
|
92
|
+
* uncontrolled usage. Use `onChange` callback to react to data changes.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* const formGridRef = useRef<FormGridRef>(null);
|
|
97
|
+
*
|
|
98
|
+
* const columns: FormGridColumn[] = [
|
|
99
|
+
* { key: 'name', label: 'Field Name', type: 'input', placeholder: 'Enter name' },
|
|
100
|
+
* { key: 'type', label: 'Type', type: 'select', options: [
|
|
101
|
+
* { value: 'string', label: 'String' },
|
|
102
|
+
* { value: 'number', label: 'Number' },
|
|
103
|
+
* ]},
|
|
104
|
+
* { key: 'required', label: 'Required', type: 'checkbox' },
|
|
105
|
+
* ];
|
|
106
|
+
*
|
|
107
|
+
* <FormGrid ref={formGridRef} columns={columns} onChange={console.log} />
|
|
108
|
+
*
|
|
109
|
+
* // Get data via ref
|
|
110
|
+
* const data = formGridRef.current?.getData();
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare const FormGrid: React.ForwardRefExoticComponent<FormGridProps & React.RefAttributes<FormGridRef>>;
|
|
114
|
+
export default FormGrid;
|
|
115
|
+
//# sourceMappingURL=form-grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-grid.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAiE,SAAS,EAAE,MAAM,OAAO,CAAC;AAExG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAOnF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC9D,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,UAAU,CAAC;IACrE,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8BAA8B;IAC9B,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,+CAA+C;IAC/C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC;IACnH,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,sBAAsB;IACtB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,eAAe,EAAE,CAAC;IACjC,4DAA4D;IAC5D,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,gDAAgD;IAChD,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,gDAAgD;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,+CAA+C;IAC/C,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,oDAAoD;IACpD,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IAC7C,qCAAqC;IACrC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8LAA8L;IAC9L,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC;IAC/C,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CACpE,CAAC;AAmJF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,QAAA,MAAM,QAAQ,mFAgMb,CAAC;AAIF,eAAe,QAAQ,CAAC"}
|