@aglyn/shared-ui-jsx-forms 1.0.0-beta.143

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.
Files changed (150) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +52 -0
  4. package/src/index.d.ts +17 -0
  5. package/src/index.js +18 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/color-picker-tokens.d.ts +135 -0
  8. package/src/lib/components/color-picker-tokens.js +431 -0
  9. package/src/lib/components/color-picker-tokens.js.map +1 -0
  10. package/src/lib/components/color-picker.component.d.ts +64 -0
  11. package/src/lib/components/color-picker.component.js +322 -0
  12. package/src/lib/components/color-picker.component.js.map +1 -0
  13. package/src/lib/components/create-artifact-drawer.component.d.ts +100 -0
  14. package/src/lib/components/create-artifact-drawer.component.js +184 -0
  15. package/src/lib/components/create-artifact-drawer.component.js.map +1 -0
  16. package/src/lib/components/grid-form-template.component.d.ts +21 -0
  17. package/src/lib/components/grid-form-template.component.js +81 -0
  18. package/src/lib/components/grid-form-template.component.js.map +1 -0
  19. package/src/lib/components/icon-select.component.d.ts +52 -0
  20. package/src/lib/components/icon-select.component.js +453 -0
  21. package/src/lib/components/icon-select.component.js.map +1 -0
  22. package/src/lib/components/select.component.d.ts +58 -0
  23. package/src/lib/components/select.component.js +107 -0
  24. package/src/lib/components/select.component.js.map +1 -0
  25. package/src/lib/components/switch.component.d.ts +38 -0
  26. package/src/lib/components/switch.component.js +76 -0
  27. package/src/lib/components/switch.component.js.map +1 -0
  28. package/src/lib/components/text-field.component.d.ts +45 -0
  29. package/src/lib/components/text-field.component.js +77 -0
  30. package/src/lib/components/text-field.component.js.map +1 -0
  31. package/src/lib/components/textarea.component.d.ts +45 -0
  32. package/src/lib/components/textarea.component.js +61 -0
  33. package/src/lib/components/textarea.component.js.map +1 -0
  34. package/src/lib/components/toggle-button.component.d.ts +27 -0
  35. package/src/lib/components/toggle-button.component.js +116 -0
  36. package/src/lib/components/toggle-button.component.js.map +1 -0
  37. package/src/lib/constants/component-mappers.d.ts +42 -0
  38. package/src/lib/constants/component-mappers.js +70 -0
  39. package/src/lib/constants/component-mappers.js.map +1 -0
  40. package/src/lib/constants/dynamic-fields.d.ts +45 -0
  41. package/src/lib/constants/dynamic-fields.js +65 -0
  42. package/src/lib/constants/dynamic-fields.js.map +1 -0
  43. package/src/lib/constants/field-configurations.d.ts +53 -0
  44. package/src/lib/constants/field-configurations.js +121 -0
  45. package/src/lib/constants/field-configurations.js.map +1 -0
  46. package/src/lib/constants/flags.d.ts +72 -0
  47. package/src/lib/constants/flags.js +69 -0
  48. package/src/lib/constants/flags.js.map +1 -0
  49. package/src/lib/hocs/with-grid-item.d.ts +25 -0
  50. package/src/lib/hocs/with-grid-item.js +48 -0
  51. package/src/lib/hocs/with-grid-item.js.map +1 -0
  52. package/src/lib/jsx-forms.d.ts +41 -0
  53. package/src/lib/jsx-forms.js +42 -0
  54. package/src/lib/jsx-forms.js.map +1 -0
  55. package/src/lib/mapper/breakpoint-span.d.ts +73 -0
  56. package/src/lib/mapper/breakpoint-span.js +346 -0
  57. package/src/lib/mapper/breakpoint-span.js.map +1 -0
  58. package/src/lib/mapper/checkbox.d.ts +43 -0
  59. package/src/lib/mapper/checkbox.js +111 -0
  60. package/src/lib/mapper/checkbox.js.map +1 -0
  61. package/src/lib/mapper/css-border.d.ts +113 -0
  62. package/src/lib/mapper/css-border.js +410 -0
  63. package/src/lib/mapper/css-border.js.map +1 -0
  64. package/src/lib/mapper/css-dimension.d.ts +89 -0
  65. package/src/lib/mapper/css-dimension.js +327 -0
  66. package/src/lib/mapper/css-dimension.js.map +1 -0
  67. package/src/lib/mapper/css-gradient.d.ts +110 -0
  68. package/src/lib/mapper/css-gradient.js +720 -0
  69. package/src/lib/mapper/css-gradient.js.map +1 -0
  70. package/src/lib/mapper/data-table.d.ts +23 -0
  71. package/src/lib/mapper/data-table.js +296 -0
  72. package/src/lib/mapper/data-table.js.map +1 -0
  73. package/src/lib/mapper/date-picker.d.ts +26 -0
  74. package/src/lib/mapper/date-picker.js +69 -0
  75. package/src/lib/mapper/date-picker.js.map +1 -0
  76. package/src/lib/mapper/dual-list-select.d.ts +115 -0
  77. package/src/lib/mapper/dual-list-select.js +353 -0
  78. package/src/lib/mapper/dual-list-select.js.map +1 -0
  79. package/src/lib/mapper/field-array.d.ts +71 -0
  80. package/src/lib/mapper/field-array.js +326 -0
  81. package/src/lib/mapper/field-array.js.map +1 -0
  82. package/src/lib/mapper/form-field-grid.d.ts +155 -0
  83. package/src/lib/mapper/form-field-grid.js +194 -0
  84. package/src/lib/mapper/form-field-grid.js.map +1 -0
  85. package/src/lib/mapper/index.d.ts +50 -0
  86. package/src/lib/mapper/index.js +49 -0
  87. package/src/lib/mapper/index.js.map +1 -0
  88. package/src/lib/mapper/multiple-choice-list.d.ts +32 -0
  89. package/src/lib/mapper/multiple-choice-list.js +105 -0
  90. package/src/lib/mapper/multiple-choice-list.js.map +1 -0
  91. package/src/lib/mapper/plain-text.d.ts +31 -0
  92. package/src/lib/mapper/plain-text.js +48 -0
  93. package/src/lib/mapper/plain-text.js.map +1 -0
  94. package/src/lib/mapper/preset-choice.d.ts +106 -0
  95. package/src/lib/mapper/preset-choice.js +267 -0
  96. package/src/lib/mapper/preset-choice.js.map +1 -0
  97. package/src/lib/mapper/radio.d.ts +40 -0
  98. package/src/lib/mapper/radio.js +122 -0
  99. package/src/lib/mapper/radio.js.map +1 -0
  100. package/src/lib/mapper/select.d.ts +60 -0
  101. package/src/lib/mapper/select.js +203 -0
  102. package/src/lib/mapper/select.js.map +1 -0
  103. package/src/lib/mapper/slider.d.ts +45 -0
  104. package/src/lib/mapper/slider.js +119 -0
  105. package/src/lib/mapper/slider.js.map +1 -0
  106. package/src/lib/mapper/stored-field-value.d.ts +23 -0
  107. package/src/lib/mapper/stored-field-value.js +29 -0
  108. package/src/lib/mapper/stored-field-value.js.map +1 -0
  109. package/src/lib/mapper/sub-form.d.ts +39 -0
  110. package/src/lib/mapper/sub-form.js +108 -0
  111. package/src/lib/mapper/sub-form.js.map +1 -0
  112. package/src/lib/mapper/switch.d.ts +47 -0
  113. package/src/lib/mapper/switch.js +93 -0
  114. package/src/lib/mapper/switch.js.map +1 -0
  115. package/src/lib/mapper/tabs.d.ts +34 -0
  116. package/src/lib/mapper/tabs.js +64 -0
  117. package/src/lib/mapper/tabs.js.map +1 -0
  118. package/src/lib/mapper/text-field.d.ts +33 -0
  119. package/src/lib/mapper/text-field.js +88 -0
  120. package/src/lib/mapper/text-field.js.map +1 -0
  121. package/src/lib/mapper/textarea.d.ts +31 -0
  122. package/src/lib/mapper/textarea.js +68 -0
  123. package/src/lib/mapper/textarea.js.map +1 -0
  124. package/src/lib/mapper/theme-scale.d.ts +79 -0
  125. package/src/lib/mapper/theme-scale.js +162 -0
  126. package/src/lib/mapper/theme-scale.js.map +1 -0
  127. package/src/lib/mapper/time-picker.d.ts +26 -0
  128. package/src/lib/mapper/time-picker.js +69 -0
  129. package/src/lib/mapper/time-picker.js.map +1 -0
  130. package/src/lib/mapper/types.d.ts +43 -0
  131. package/src/lib/mapper/types.js +22 -0
  132. package/src/lib/mapper/types.js.map +1 -0
  133. package/src/lib/mapper/validation-error.d.ts +24 -0
  134. package/src/lib/mapper/validation-error.js +24 -0
  135. package/src/lib/mapper/validation-error.js.map +1 -0
  136. package/src/lib/mapper/wizard.d.ts +21 -0
  137. package/src/lib/mapper/wizard.js +203 -0
  138. package/src/lib/mapper/wizard.js.map +1 -0
  139. package/src/lib/types.d.ts +19 -0
  140. package/src/lib/types.js +18 -0
  141. package/src/lib/types.js.map +1 -0
  142. package/src/lib/utils/option-is-equal-to-value.d.ts +20 -0
  143. package/src/lib/utils/option-is-equal-to-value.js +21 -0
  144. package/src/lib/utils/option-is-equal-to-value.js.map +1 -0
  145. package/src/lib/utils/validation-message.d.ts +22 -0
  146. package/src/lib/utils/validation-message.js +24 -0
  147. package/src/lib/utils/validation-message.js.map +1 -0
  148. package/src/lib/vendor/data-driven-forms.d.ts +33 -0
  149. package/src/lib/vendor/data-driven-forms.js +29 -0
  150. package/src/lib/vendor/data-driven-forms.js.map +1 -0
@@ -0,0 +1,720 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import { ColorPicker as AglynColorPicker } from "@aglyn/shared-ui-color-picker";
20
+ import { buildCssGradient, parseCssGradient, parsePaletteTokenCssVar, paletteTokenToCssVar } from "@aglyn/shared-data-enums";
21
+ import { Box, Button, ButtonBase, ClickAwayListener, FormHelperText, IconButton, MenuItem, Paper, Popper, Stack, TextField as MuiTextField, Typography } from "@mui/material";
22
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
+ import { ColorTokenGrid, rgbColorToCss, TokenSwatch, useColorPickerTokenOptions } from "../components/color-picker-tokens.js";
24
+ import { useFieldApi } from "../vendor/data-driven-forms.js";
25
+ import FormFieldGrid, { buildFieldClear } from "./form-field-grid.js";
26
+ import { validationError } from "./validation-error.js";
27
+ /**
28
+ * CssGradient (AGL-1331): the Background Fill editor.
29
+ *
30
+ * The Styles panel's Colors group offered *Text Color* and *Background
31
+ * Color* and nothing else, so a gradient could not be authored at all —
32
+ * and typing one into Background Color was accepted, stored as
33
+ * `background-color: linear-gradient(…)`, and dropped by the CSS parser
34
+ * with no error anywhere. This is the control that expresses the value,
35
+ * and `describeCssColorProblem` is the guard that stops the silent store;
36
+ * the two ship together on purpose.
37
+ *
38
+ * The PERSISTED value is ONE CSS string under `backgroundImage` — the same
39
+ * contract as `CssDimensionField`, so nothing downstream has to know
40
+ * this editor exists. A **Solid** fill writes the explicit keyword
41
+ * `none`, which paints no image and leaves the neighbouring Background
42
+ * Color field to do the job; the two never fight over one property.
43
+ * **Default** writes `''`, which clears the property entirely.
44
+ *
45
+ * Solid and Default are two states, not one (AGL-1338). Solid shipped as
46
+ * "write nothing" — and on a reusable-component INSTANCE, where the panel
47
+ * edits an override slice, writing nothing IS "no override": the field
48
+ * could not be used to take a gradient off one placement, because the
49
+ * absence it produced was indistinguishable from never having touched it.
50
+ * `none` is a value, so it merges over the component's gradient and wins.
51
+ *
52
+ * Stops bind to palette TOKENS or to literals, and a single gradient can
53
+ * mix them: the marketing CTA's endpoints are `primary.main` /
54
+ * `secondary.main` while its mid stop `#7A5CF0` has no token at all. A
55
+ * token stop persists as `var(--mui-palette-primary-main, #00B0FF)` and is
56
+ * substituted against the site theme by the node renderer
57
+ * (`resolvePaletteVarsSx`) — see that module for why the reference is not
58
+ * left to the browser.
59
+ *
60
+ * Anything the editor cannot model — `conic-gradient`, `to bottom right`,
61
+ * a comma-stacked image list — falls back to a free-text box holding the
62
+ * raw string, so no existing value is destroyed by an editor that cannot
63
+ * show it. The stacked case reaches that box only because `parseCssGradient`
64
+ * splits top-level layers first (AGL-1336); until it did, a tint over a
65
+ * photo opened in the STOP editor and the author's first edit deleted the
66
+ * photo.
67
+ */ /**
68
+ * The CSS an explicit **Solid** fill persists — `background-image`'s own
69
+ * initial value, so it renders identically to no declaration at all while
70
+ * remaining a VALUE that can override an inherited one (AGL-1338).
71
+ */ export const SOLID_FILL_VALUE = 'none';
72
+ const DEFAULT_ANGLE = 180;
73
+ /** No value at all — the property stays off the node. */ const emptyDraft = {
74
+ fill: '',
75
+ angle: '',
76
+ positionX: '',
77
+ positionY: '',
78
+ stops: []
79
+ };
80
+ /** An explicit `background-image: none`. */ const solidDraft = {
81
+ fill: 'solid',
82
+ angle: '',
83
+ positionX: '',
84
+ positionY: '',
85
+ stops: []
86
+ };
87
+ export const seedGradientDraft = (value)=>{
88
+ const text = typeof value === 'string' ? value.trim() : '';
89
+ if (!text) return emptyDraft;
90
+ // `none` is this control's own Solid value, not a background it cannot
91
+ // model: reading it as raw CSS would strand the author in the text box
92
+ // on a value the control itself wrote.
93
+ if (text.toLowerCase() === SOLID_FILL_VALUE) return solidDraft;
94
+ const parsed = parseCssGradient(text);
95
+ // A non-gradient background image (`url(…)`) is still someone's value.
96
+ if (!parsed) return _extends({}, emptyDraft, {
97
+ fill: 'linear',
98
+ raw: text
99
+ });
100
+ if (parsed.raw !== undefined) {
101
+ return {
102
+ fill: parsed.type,
103
+ angle: '',
104
+ positionX: '',
105
+ positionY: '',
106
+ stops: [],
107
+ raw: parsed.raw
108
+ };
109
+ }
110
+ return {
111
+ fill: parsed.type,
112
+ angle: parsed.angle === undefined ? '' : `${parsed.angle}`,
113
+ positionX: parsed.position ? `${parsed.position.x}` : '',
114
+ positionY: parsed.position ? `${parsed.position.y}` : '',
115
+ stops: parsed.stops
116
+ };
117
+ };
118
+ export const serializeGradientDraft = (draft)=>{
119
+ if (draft.raw !== undefined) return draft.raw;
120
+ if (draft.fill === '') return '';
121
+ if (draft.fill === 'solid') return SOLID_FILL_VALUE;
122
+ const angle = draft.angle.trim();
123
+ // Both boxes or neither: `at 50%` alone is a different CSS value (it
124
+ // defaults Y to centre), and writing one while the author has only filled
125
+ // the other would move the glow the moment they tabbed out of the first.
126
+ const x = draft.positionX.trim();
127
+ const y = draft.positionY.trim();
128
+ const hasPosition = draft.fill === 'radial' && x !== '' && y !== '';
129
+ const gradient = {
130
+ type: draft.fill,
131
+ angle: angle === '' ? undefined : Number(angle),
132
+ position: hasPosition ? {
133
+ x: Number(x),
134
+ y: Number(y)
135
+ } : undefined,
136
+ stops: draft.stops
137
+ };
138
+ return buildCssGradient(gradient);
139
+ };
140
+ /** The two stops a fresh gradient starts from — brand ends when available. */ function defaultStops(options) {
141
+ var _start_light, _end_light;
142
+ const pick = (path)=>options.find((option)=>option.value === path);
143
+ const start = pick('primary.main');
144
+ const end = pick('secondary.main');
145
+ return [
146
+ {
147
+ color: start ? paletteTokenToCssVar(start.value, (_start_light = start.light) != null ? _start_light : start.dark) : '#ffffff',
148
+ position: 0
149
+ },
150
+ {
151
+ color: end ? paletteTokenToCssVar(end.value, (_end_light = end.light) != null ? _end_light : end.dark) : '#000000',
152
+ position: 100
153
+ }
154
+ ];
155
+ }
156
+ /** The token a stop is bound to, or undefined when it is a literal. */ function stopToken(color, options) {
157
+ const ref = parsePaletteTokenCssVar(color);
158
+ if (!ref) return undefined;
159
+ return options.find((option)=>option.value === ref.path);
160
+ }
161
+ /**
162
+ * A preview-safe copy of the value: token references resolve to the SITE
163
+ * theme's colour rather than to whatever `--mui-palette-*` the surrounding
164
+ * app happens to define (the console defines its own).
165
+ */ function previewGradient(draft, options) {
166
+ const resolved = _extends({}, draft, {
167
+ stops: draft.stops.map((stop)=>{
168
+ var _ref, _ref1, _ref2;
169
+ const ref = parsePaletteTokenCssVar(stop.color);
170
+ if (!ref) return stop;
171
+ const token = options.find((option)=>option.value === ref.path);
172
+ const color = (_ref = (_ref1 = (_ref2 = token == null ? void 0 : token.light) != null ? _ref2 : token == null ? void 0 : token.dark) != null ? _ref1 : ref.fallback) != null ? _ref : stop.color;
173
+ return _extends({}, stop, {
174
+ color
175
+ });
176
+ })
177
+ });
178
+ return serializeGradientDraft(resolved);
179
+ }
180
+ /** Swatch + label button that opens one stop's two-stage colour picker. */ const GradientStopColor = (props)=>{
181
+ var _ref, _ref1, _ref2, _ref3;
182
+ var _parsePaletteTokenCssVar;
183
+ const { color, label, disabled, presetColors, ColorPickerProps, onChange } = props;
184
+ const options = useColorPickerTokenOptions();
185
+ const token = stopToken(color, options);
186
+ const literal = (_ref = (_parsePaletteTokenCssVar = parsePaletteTokenCssVar(color)) == null ? void 0 : _parsePaletteTokenCssVar.fallback) != null ? _ref : color;
187
+ const [anchor, setAnchor] = useState(null);
188
+ const [stage, setStage] = useState('tokens');
189
+ const handleOpen = useCallback((event)=>{
190
+ // Tokens first, exactly like the colour field's two-stage picker —
191
+ // unless the theme offers none, in which case the token stage would
192
+ // be an empty grid.
193
+ setStage(options.length ? 'tokens' : 'custom');
194
+ setAnchor((previous)=>previous ? null : event.currentTarget);
195
+ }, [
196
+ options.length
197
+ ]);
198
+ return /*#__PURE__*/ _jsx(ClickAwayListener, {
199
+ onClickAway: ()=>setAnchor(null),
200
+ children: /*#__PURE__*/ _jsxs(Box, {
201
+ sx: {
202
+ flexGrow: 1,
203
+ minWidth: 0
204
+ },
205
+ children: [
206
+ /*#__PURE__*/ _jsxs(ButtonBase, {
207
+ "aria-label": label,
208
+ disabled: disabled,
209
+ onClick: handleOpen,
210
+ sx: {
211
+ width: '100%',
212
+ justifyContent: 'flex-start',
213
+ gap: 0.75,
214
+ px: 1,
215
+ py: 0.75,
216
+ border: 1,
217
+ borderColor: 'divider',
218
+ borderRadius: 1
219
+ },
220
+ children: [
221
+ token ? /*#__PURE__*/ _jsx(TokenSwatch, {
222
+ light: token.light,
223
+ dark: token.dark,
224
+ size: 18
225
+ }) : /*#__PURE__*/ _jsx(Box, {
226
+ sx: {
227
+ width: 18,
228
+ height: 18,
229
+ flexShrink: 0,
230
+ borderRadius: '50%',
231
+ border: 1,
232
+ borderColor: 'divider',
233
+ backgroundColor: literal
234
+ }
235
+ }),
236
+ /*#__PURE__*/ _jsx(Typography, {
237
+ variant: "caption",
238
+ noWrap: true,
239
+ children: token ? token.label : literal
240
+ })
241
+ ]
242
+ }),
243
+ /*#__PURE__*/ _jsx(Popper, {
244
+ open: Boolean(anchor),
245
+ anchorEl: anchor,
246
+ sx: {
247
+ zIndex: 'tooltip',
248
+ maxWidth: 280
249
+ },
250
+ disablePortal: true,
251
+ children: stage === 'tokens' && options.length ? /*#__PURE__*/ _jsx(ColorTokenGrid, {
252
+ options: options,
253
+ value: (_ref1 = token == null ? void 0 : token.value) != null ? _ref1 : '',
254
+ onSelect: (path)=>{
255
+ var _ref;
256
+ const picked = options.find((option)=>option.value === path);
257
+ onChange(paletteTokenToCssVar(path, (_ref = picked == null ? void 0 : picked.light) != null ? _ref : picked == null ? void 0 : picked.dark));
258
+ setAnchor(null);
259
+ },
260
+ onCustom: ()=>setStage('custom')
261
+ }) : /*#__PURE__*/ _jsxs(Paper, {
262
+ sx: {
263
+ p: 0.5
264
+ },
265
+ children: [
266
+ options.length ? /*#__PURE__*/ _jsx(Button, {
267
+ size: "small",
268
+ fullWidth: true,
269
+ sx: {
270
+ mb: 0.5
271
+ },
272
+ onClick: ()=>setStage('tokens'),
273
+ children: '‹ Theme colors'
274
+ }) : null,
275
+ /*#__PURE__*/ _jsx(AglynColorPicker, _extends({}, ColorPickerProps, {
276
+ // A `var()` reference is not parseable by the picker; seed
277
+ // it with the colour that reference resolves to.
278
+ color: (_ref2 = (_ref3 = token == null ? void 0 : token.light) != null ? _ref3 : token == null ? void 0 : token.dark) != null ? _ref2 : literal,
279
+ onChange: (picked)=>onChange(rgbColorToCss(picked.rgb)),
280
+ presetColors: presetColors
281
+ }))
282
+ ]
283
+ })
284
+ })
285
+ ]
286
+ })
287
+ });
288
+ };
289
+ GradientStopColor.displayName = 'AglynGradientStopColor';
290
+ export const CssGradientField = (props)=>{
291
+ const _useFieldApi = useFieldApi(props), { input, isReadOnly, isDisabled, label, helperText, description, validateOnMount, meta, help, presetColors, unsetLabel = 'Default', clearable, ColorPickerProps, FormFieldGridProps = {}, // Free-text leftovers from an attribute schema that must never reach
292
+ // the DOM.
293
+ inputProps: _inputProps, InputProps: _InputProps, multiline: _multiline, placeholder: _placeholder } = _useFieldApi, rest = _object_without_properties_loose(_useFieldApi, [
294
+ "input",
295
+ "isReadOnly",
296
+ "isDisabled",
297
+ "label",
298
+ "helperText",
299
+ "description",
300
+ "validateOnMount",
301
+ "meta",
302
+ "help",
303
+ "presetColors",
304
+ "unsetLabel",
305
+ "clearable",
306
+ "ColorPickerProps",
307
+ "FormFieldGridProps",
308
+ "inputProps",
309
+ "InputProps",
310
+ "multiline",
311
+ "placeholder"
312
+ ]);
313
+ const invalid = validationError(meta, validateOnMount);
314
+ const options = useColorPickerTokenOptions();
315
+ const value = typeof input.value === 'string' ? input.value : '';
316
+ const locked = Boolean(isDisabled || isReadOnly);
317
+ const [draft, setDraft] = useState(()=>seedGradientDraft(value));
318
+ // Re-seed only when the value changed OUTSIDE this field (a different
319
+ // node selected, an undo) — re-seeding from our own emits would fight
320
+ // the caret in the angle and position boxes.
321
+ const emittedRef = useRef(value);
322
+ useEffect(()=>{
323
+ if (value === emittedRef.current) return;
324
+ emittedRef.current = value;
325
+ setDraft(seedGradientDraft(value));
326
+ }, [
327
+ value
328
+ ]);
329
+ const commit = useCallback((next)=>{
330
+ const serialized = serializeGradientDraft(next);
331
+ emittedRef.current = serialized;
332
+ setDraft(next);
333
+ input.onChange(serialized);
334
+ }, [
335
+ input
336
+ ]);
337
+ const handleFillChange = useCallback((event)=>{
338
+ var _event_target_value;
339
+ const fill = (_event_target_value = event.target.value) != null ? _event_target_value : '';
340
+ // Neither non-gradient choice keeps stops, but they persist
341
+ // differently: Solid writes `none`, Default writes nothing.
342
+ if (fill === '' || fill === 'solid') {
343
+ commit(_extends({}, emptyDraft, {
344
+ fill
345
+ }));
346
+ return;
347
+ }
348
+ // Switching INTO a gradient with nothing to show yet starts from the
349
+ // brand ends, so the author sees a gradient immediately instead of an
350
+ // empty stop list that serializes to nothing.
351
+ const stops = draft.stops.length >= 2 ? draft.stops : defaultStops(options);
352
+ const angle = fill === 'linear' ? draft.angle || `${DEFAULT_ANGLE}` : '';
353
+ // Position belongs to radial; carried across a radial→radial change
354
+ // (Solid and back) and dropped when the fill is no longer radial, so a
355
+ // linear gradient cannot persist a centre no CSS would use.
356
+ const positionX = fill === 'radial' ? draft.positionX : '';
357
+ const positionY = fill === 'radial' ? draft.positionY : '';
358
+ commit({
359
+ fill,
360
+ angle,
361
+ positionX,
362
+ positionY,
363
+ stops,
364
+ raw: undefined
365
+ });
366
+ }, [
367
+ commit,
368
+ draft.angle,
369
+ draft.positionX,
370
+ draft.positionY,
371
+ draft.stops,
372
+ options
373
+ ]);
374
+ const handlePositionChange = useCallback((axis)=>(event)=>{
375
+ commit(_extends({}, draft, {
376
+ [axis]: event.target.value
377
+ }));
378
+ }, [
379
+ commit,
380
+ draft
381
+ ]);
382
+ const handleAngleChange = useCallback((event)=>{
383
+ commit(_extends({}, draft, {
384
+ angle: event.target.value
385
+ }));
386
+ }, [
387
+ commit,
388
+ draft
389
+ ]);
390
+ const handleStopColor = useCallback((index)=>(color)=>{
391
+ const stops = draft.stops.map((stop, i)=>i === index ? _extends({}, stop, {
392
+ color
393
+ }) : stop);
394
+ commit(_extends({}, draft, {
395
+ stops
396
+ }));
397
+ }, [
398
+ commit,
399
+ draft
400
+ ]);
401
+ const handleStopPosition = useCallback((index)=>(event)=>{
402
+ const text = event.target.value.trim();
403
+ const position = text === '' ? undefined : Number(text);
404
+ const stops = draft.stops.map((stop, i)=>i === index ? _extends({}, stop, {
405
+ position
406
+ }) : stop);
407
+ commit(_extends({}, draft, {
408
+ stops
409
+ }));
410
+ }, [
411
+ commit,
412
+ draft
413
+ ]);
414
+ const handleAddStop = useCallback(()=>{
415
+ var _ref;
416
+ const last = draft.stops[draft.stops.length - 1];
417
+ const previous = draft.stops[draft.stops.length - 2];
418
+ const between = typeof (last == null ? void 0 : last.position) === 'number' && typeof (previous == null ? void 0 : previous.position) === 'number' ? Math.round((last.position + previous.position) / 2) : undefined;
419
+ const stop = {
420
+ color: (_ref = last == null ? void 0 : last.color) != null ? _ref : '#ffffff',
421
+ position: between
422
+ };
423
+ // The new stop lands BEFORE the last one when it can be positioned
424
+ // between the two — the CTA's mid stop is that shape.
425
+ const stops = between === undefined ? [
426
+ ...draft.stops,
427
+ stop
428
+ ] : [
429
+ ...draft.stops.slice(0, -1),
430
+ stop,
431
+ last
432
+ ];
433
+ commit(_extends({}, draft, {
434
+ stops
435
+ }));
436
+ }, [
437
+ commit,
438
+ draft
439
+ ]);
440
+ const handleRemoveStop = useCallback((index)=>()=>{
441
+ commit(_extends({}, draft, {
442
+ stops: draft.stops.filter((_, i)=>i !== index)
443
+ }));
444
+ }, [
445
+ commit,
446
+ draft
447
+ ]);
448
+ const handleRawChange = useCallback((event)=>{
449
+ const text = event.target.value;
450
+ // Edited back into a gradient this editor CAN model? Take the
451
+ // structured controls back rather than stranding the author in the
452
+ // text box (the css-dimension rule).
453
+ const reseeded = seedGradientDraft(text);
454
+ commit(reseeded.raw === undefined && text.trim() !== '' ? reseeded : _extends({}, draft, {
455
+ raw: text
456
+ }));
457
+ }, [
458
+ commit,
459
+ draft
460
+ ]);
461
+ const preview = useMemo(()=>previewGradient(draft, options), [
462
+ draft,
463
+ options
464
+ ]);
465
+ // Back to unset in one click (AGL-2486) — including out of the raw-CSS
466
+ // fallback, which the fill-type select cannot leave on its own because it
467
+ // is disabled while a raw value is held.
468
+ const clear = buildFieldClear({
469
+ clearable,
470
+ label,
471
+ hasValue: value !== '',
472
+ locked,
473
+ onClear: ()=>commit(_extends({}, emptyDraft, {
474
+ raw: undefined
475
+ }))
476
+ });
477
+ return /*#__PURE__*/ _jsxs(FormFieldGrid, _extends({
478
+ help: help,
479
+ clear: clear
480
+ }, FormFieldGridProps, {
481
+ children: [
482
+ /*#__PURE__*/ _jsxs(MuiTextField, _extends({}, rest, {
483
+ select: true,
484
+ fullWidth: true,
485
+ size: "small",
486
+ name: input.name,
487
+ label: label,
488
+ value: draft.raw !== undefined ? 'custom' : draft.fill,
489
+ onChange: handleFillChange,
490
+ disabled: locked || draft.raw !== undefined,
491
+ error: !!invalid,
492
+ helperText: invalid || (meta.touched || validateOnMount) && meta.warning || helperText || description,
493
+ slotProps: {
494
+ htmlInput: {
495
+ 'aria-label': 'Fill type'
496
+ },
497
+ // Without displayEmpty a MUI Select renders NOTHING for `''`,
498
+ // which would show the unset state as a broken control.
499
+ select: {
500
+ displayEmpty: true
501
+ },
502
+ // …and `displayEmpty` alone prints the label ON TOP of the option
503
+ // it just drew (AGL-2486). MUI shrinks a label when the input
504
+ // reports itself FILLED, and an empty Select reports the
505
+ // opposite no matter what it is rendering — so the unset choice
506
+ // and the field name landed in the same few pixels and neither
507
+ // could be read. A rendered option is content; say so.
508
+ inputLabel: {
509
+ shrink: true
510
+ }
511
+ },
512
+ children: [
513
+ /*#__PURE__*/ _jsx(MenuItem, {
514
+ value: "",
515
+ children: unsetLabel
516
+ }),
517
+ /*#__PURE__*/ _jsx(MenuItem, {
518
+ value: "solid",
519
+ children: "Solid color"
520
+ }),
521
+ /*#__PURE__*/ _jsx(MenuItem, {
522
+ value: "linear",
523
+ children: "Linear gradient"
524
+ }),
525
+ /*#__PURE__*/ _jsx(MenuItem, {
526
+ value: "radial",
527
+ children: "Radial gradient"
528
+ }),
529
+ draft.raw !== undefined ? /*#__PURE__*/ _jsx(MenuItem, {
530
+ value: "custom",
531
+ children: "Custom CSS"
532
+ }) : null
533
+ ]
534
+ })),
535
+ draft.raw !== undefined ? /*#__PURE__*/ _jsxs(_Fragment, {
536
+ children: [
537
+ /*#__PURE__*/ _jsx(MuiTextField, {
538
+ fullWidth: true,
539
+ size: "small",
540
+ multiline: true,
541
+ label: "Background image",
542
+ value: draft.raw,
543
+ onChange: handleRawChange,
544
+ disabled: locked,
545
+ sx: {
546
+ mt: 1
547
+ },
548
+ slotProps: {
549
+ htmlInput: {
550
+ 'aria-label': 'Background image CSS'
551
+ }
552
+ }
553
+ }),
554
+ /*#__PURE__*/ _jsx(FormHelperText, {
555
+ children: 'This background is richer than the stop editor can show — ' + 'stacked layers, a conic gradient, a direction keyword. It is ' + 'kept exactly as written; editing it back to a plain linear ' + 'or radial gradient brings the controls back.'
556
+ })
557
+ ]
558
+ }) : draft.fill !== 'linear' && draft.fill !== 'radial' ? null : /*#__PURE__*/ _jsxs(_Fragment, {
559
+ children: [
560
+ /*#__PURE__*/ _jsx(Box, {
561
+ "aria-label": "Gradient preview",
562
+ sx: {
563
+ mt: 1,
564
+ height: 28,
565
+ borderRadius: 1,
566
+ border: 1,
567
+ borderColor: 'divider',
568
+ backgroundImage: preview || 'none'
569
+ }
570
+ }),
571
+ draft.fill === 'linear' ? /*#__PURE__*/ _jsx(MuiTextField, {
572
+ fullWidth: true,
573
+ size: "small",
574
+ type: "number",
575
+ label: "Angle",
576
+ value: draft.angle,
577
+ onChange: handleAngleChange,
578
+ disabled: locked,
579
+ sx: {
580
+ mt: 1
581
+ },
582
+ slotProps: {
583
+ htmlInput: {
584
+ 'aria-label': 'Angle',
585
+ inputMode: 'numeric'
586
+ },
587
+ input: {
588
+ endAdornment: /*#__PURE__*/ _jsx(Typography, {
589
+ variant: "caption",
590
+ children: "deg"
591
+ })
592
+ }
593
+ }
594
+ }) : null,
595
+ draft.fill === 'radial' ? /*#__PURE__*/ _jsxs(Stack, {
596
+ direction: "row",
597
+ spacing: 0.5,
598
+ sx: {
599
+ mt: 1
600
+ },
601
+ children: [
602
+ /*#__PURE__*/ _jsx(MuiTextField, {
603
+ fullWidth: true,
604
+ size: "small",
605
+ type: "number",
606
+ label: "Center X",
607
+ value: draft.positionX,
608
+ onChange: handlePositionChange('positionX'),
609
+ disabled: locked,
610
+ slotProps: {
611
+ htmlInput: {
612
+ 'aria-label': 'Center X',
613
+ inputMode: 'numeric'
614
+ },
615
+ input: {
616
+ endAdornment: /*#__PURE__*/ _jsx(Typography, {
617
+ variant: "caption",
618
+ children: '%'
619
+ })
620
+ }
621
+ }
622
+ }),
623
+ /*#__PURE__*/ _jsx(MuiTextField, {
624
+ fullWidth: true,
625
+ size: "small",
626
+ type: "number",
627
+ label: "Center Y",
628
+ value: draft.positionY,
629
+ onChange: handlePositionChange('positionY'),
630
+ disabled: locked,
631
+ slotProps: {
632
+ htmlInput: {
633
+ 'aria-label': 'Center Y',
634
+ inputMode: 'numeric'
635
+ },
636
+ input: {
637
+ endAdornment: /*#__PURE__*/ _jsx(Typography, {
638
+ variant: "caption",
639
+ children: '%'
640
+ })
641
+ }
642
+ }
643
+ })
644
+ ]
645
+ }) : null,
646
+ /*#__PURE__*/ _jsx(Stack, {
647
+ spacing: 0.5,
648
+ sx: {
649
+ mt: 1
650
+ },
651
+ children: draft.stops.map((stop, index)=>{
652
+ var _stop_position;
653
+ return /*#__PURE__*/ _jsxs(Stack, {
654
+ direction: "row",
655
+ spacing: 0.5,
656
+ sx: {
657
+ alignItems: 'center'
658
+ },
659
+ children: [
660
+ /*#__PURE__*/ _jsx(GradientStopColor, {
661
+ color: stop.color,
662
+ label: `Stop ${index + 1} color`,
663
+ disabled: locked,
664
+ presetColors: presetColors,
665
+ ColorPickerProps: ColorPickerProps,
666
+ onChange: handleStopColor(index)
667
+ }),
668
+ /*#__PURE__*/ _jsx(MuiTextField, {
669
+ size: "small",
670
+ type: "number",
671
+ value: (_stop_position = stop.position) != null ? _stop_position : '',
672
+ onChange: handleStopPosition(index),
673
+ disabled: locked,
674
+ sx: {
675
+ width: 88
676
+ },
677
+ slotProps: {
678
+ htmlInput: {
679
+ 'aria-label': `Stop ${index + 1} position`,
680
+ inputMode: 'numeric'
681
+ },
682
+ input: {
683
+ endAdornment: /*#__PURE__*/ _jsx(Typography, {
684
+ variant: "caption",
685
+ children: "%"
686
+ })
687
+ }
688
+ }
689
+ }),
690
+ /*#__PURE__*/ _jsx(IconButton, {
691
+ size: "small",
692
+ "aria-label": `Remove stop ${index + 1}`,
693
+ // A gradient needs two ends; the last two are not
694
+ // removable, so the control can never serialize to a
695
+ // value that silently clears the background.
696
+ disabled: locked || draft.stops.length <= 2,
697
+ onClick: handleRemoveStop(index),
698
+ children: '×'
699
+ })
700
+ ]
701
+ }, index);
702
+ })
703
+ }),
704
+ /*#__PURE__*/ _jsx(Button, {
705
+ size: "small",
706
+ onClick: handleAddStop,
707
+ disabled: locked,
708
+ sx: {
709
+ mt: 0.5
710
+ },
711
+ children: "Add stop"
712
+ })
713
+ ]
714
+ })
715
+ ]
716
+ }));
717
+ };
718
+ export default CssGradientField;
719
+
720
+ //# sourceMappingURL=css-gradient.js.map