@dloizides/ui-forms 1.18.7 → 1.20.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/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import React2, { useState, useCallback, useMemo, useEffect, useRef, useLayoutEffect } from 'react';
2
- import { Platform, StyleSheet, TextInput, View, Text, ScrollView, Pressable, Switch, TouchableOpacity } from 'react-native';
2
+ import { Platform, StyleSheet, TextInput, View, Text, useWindowDimensions, ScrollView, Pressable, Switch, TouchableOpacity } from 'react-native';
3
3
  import { useUi } from '@dloizides/ui-feedback';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { Z_INDEX, focusRingOutline, readableScaleInk } from '@dloizides/design-tokens';
5
6
  import { themeFocusRingColor, Button } from '@dloizides/ui-buttons';
6
7
  import { createPortal } from 'react-dom';
7
8
  import { MIN_TOUCH_TARGET_PX } from '@dloizides/a11y';
8
- import { Z_INDEX, focusRingOutline } from '@dloizides/design-tokens';
9
9
 
10
10
  // src/Field/Field.tsx
11
11
  function attachControlId(child, candidateId, describedById) {
@@ -140,6 +140,11 @@ function useFormGridMetrics() {
140
140
  function useIsGapOwned() {
141
141
  return React2.useContext(GapOwnedContext);
142
142
  }
143
+ var ERROR_LIGHT_STEPS = ["500", "600", "700"];
144
+ var ERROR_DARK_STEPS = ["300", "200", "400", "100", "500"];
145
+ function fieldErrorInk(theme) {
146
+ return readableScaleInk({ ...theme.semantic.error }, theme.colors.surface, ERROR_LIGHT_STEPS, ERROR_DARK_STEPS);
147
+ }
143
148
  var ERROR_FONT_SIZE = 12;
144
149
  var HINT_FONT_SIZE = 12;
145
150
  var OPTIONAL_MARK_FONT_WEIGHT = "400";
@@ -195,8 +200,8 @@ var Field = ({
195
200
  const { theme } = useUi();
196
201
  const gapOwned = useIsGapOwned();
197
202
  const spacingStyles = fieldSpacingStyleSet(resolveFieldSpacing(spacing, gapOwned));
198
- const { colors, semantic } = theme;
199
- const errorColor = semantic.error["500"];
203
+ const { colors } = theme;
204
+ const errorColor = fieldErrorInk(theme);
200
205
  const hasError = hasFieldError(error);
201
206
  const hasHint = isPresent(hint);
202
207
  const labelText = label ?? "";
@@ -242,6 +247,7 @@ var FORM_GRID_DEFAULT_MIN_WIDTH = 220;
242
247
  var FORM_GRID_DEFAULT_BASIS = 260;
243
248
  var styles2 = StyleSheet.create({
244
249
  grid: {
250
+ alignItems: "flex-start",
245
251
  flexDirection: "row",
246
252
  flexWrap: "wrap"
247
253
  }
@@ -257,6 +263,36 @@ var FormGrid = ({
257
263
  const metrics = React2.useMemo(() => ({ minWidth, basis }), [minWidth, basis]);
258
264
  return /* @__PURE__ */ jsx(FormGridProvider, { value: metrics, children: /* @__PURE__ */ jsx(GapOwnedProvider, { value: true, children: /* @__PURE__ */ jsx(View, { style: [styles2.grid, { gap }, style], testID, children }) }) });
259
265
  };
266
+ var LABEL_LINE_PLACEHOLDER = "\xA0";
267
+ var styles3 = StyleSheet.create({
268
+ labelLine: {
269
+ userSelect: "none"
270
+ }
271
+ });
272
+ var AlignToControl = ({
273
+ children,
274
+ labelVariant,
275
+ spacing,
276
+ style,
277
+ testID
278
+ }) => {
279
+ const spacingStyles = fieldSpacingStyleSet(resolveFieldSpacing(spacing, useIsGapOwned()));
280
+ const labelLineTestID = testID !== void 0 ? `${testID}-label-line` : void 0;
281
+ return /* @__PURE__ */ jsxs(View, { style: [spacingStyles.container, style], testID, children: [
282
+ /* @__PURE__ */ jsx(
283
+ Text,
284
+ {
285
+ accessibilityElementsHidden: true,
286
+ "aria-hidden": true,
287
+ importantForAccessibility: "no",
288
+ style: [spacingStyles.label, resolveLabelVariantStyle(labelVariant), styles3.labelLine],
289
+ testID: labelLineTestID,
290
+ children: LABEL_LINE_PLACEHOLDER
291
+ }
292
+ ),
293
+ children
294
+ ] });
295
+ };
260
296
  var GROW = 1;
261
297
  var NO_GROW = 0;
262
298
  var FormCell = ({
@@ -264,6 +300,7 @@ var FormCell = ({
264
300
  minWidth,
265
301
  basis,
266
302
  grow = true,
303
+ alignToControl = false,
267
304
  style,
268
305
  testID
269
306
  }) => {
@@ -275,11 +312,54 @@ var FormCell = ({
275
312
  flexBasis: resolvedBasis,
276
313
  flexGrow: grow ? GROW : NO_GROW
277
314
  };
278
- return /* @__PURE__ */ jsx(View, { style: [cellStyle, style], testID, children });
315
+ return /* @__PURE__ */ jsx(View, { style: [cellStyle, style], testID, children: alignToControl ? /* @__PURE__ */ jsx(AlignToControl, { children }) : children });
316
+ };
317
+
318
+ // src/InlineFieldRow/inlineFieldRowLayout.ts
319
+ var INLINE_FIELD_ROW_MIN_WIDTH = 1024;
320
+ function inlineFieldRowLayout(windowWidth, fieldMaxWidth, inlineMinWidth = INLINE_FIELD_ROW_MIN_WIDTH) {
321
+ const inline = windowWidth >= inlineMinWidth;
322
+ return { inline, fieldMaxWidth: inline ? fieldMaxWidth : void 0 };
323
+ }
324
+ var styles4 = StyleSheet.create({
325
+ action: {
326
+ flexShrink: 0
327
+ },
328
+ field: {
329
+ flexBasis: 0,
330
+ flexGrow: 1,
331
+ flexShrink: 1
332
+ },
333
+ row: {
334
+ alignItems: "flex-start",
335
+ flexDirection: "row"
336
+ }
337
+ });
338
+ var InlineFieldRow = ({
339
+ field,
340
+ action,
341
+ fieldMaxWidth,
342
+ inlineMinWidth,
343
+ gap = FORM_GRID_DEFAULT_GAP,
344
+ testID
345
+ }) => {
346
+ const { width } = useWindowDimensions();
347
+ const layout = inlineFieldRowLayout(width, fieldMaxWidth, inlineMinWidth);
348
+ const rowStyle = useMemo(() => [styles4.row, { gap }], [gap]);
349
+ const fieldStyle = useMemo(() => [styles4.field, { maxWidth: layout.fieldMaxWidth }], [layout.fieldMaxWidth]);
350
+ if (!layout.inline)
351
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
352
+ field,
353
+ action
354
+ ] });
355
+ return /* @__PURE__ */ jsxs(View, { style: rowStyle, testID, children: [
356
+ /* @__PURE__ */ jsx(View, { style: fieldStyle, children: field }),
357
+ /* @__PURE__ */ jsx(AlignToControl, { style: styles4.action, children: action })
358
+ ] });
279
359
  };
280
360
  var SECTION_GAP = 10;
281
361
  var DESCRIPTION_FONT_SIZE = 12;
282
- var styles3 = StyleSheet.create({
362
+ var styles5 = StyleSheet.create({
283
363
  section: {
284
364
  gap: SECTION_GAP
285
365
  },
@@ -314,7 +394,7 @@ var FormSection = ({
314
394
  // The section spaces its children with `gap`, so it owns their vertical rhythm exactly as a
315
395
  // `FormGrid` owns a row's — without this a `Field` placed directly in a section would stack its
316
396
  // 16px margin on the section's gap and recreate the F3 bug one level up.
317
- /* @__PURE__ */ jsx(GapOwnedProvider, { value: true, children: /* @__PURE__ */ jsxs(View, { role: "group", style: [styles3.section, style], testID, ...a11y, children: [
397
+ /* @__PURE__ */ jsx(GapOwnedProvider, { value: true, children: /* @__PURE__ */ jsxs(View, { role: "group", style: [styles5.section, style], testID, ...a11y, children: [
318
398
  hasTitle ? /* @__PURE__ */ jsx(
319
399
  Text,
320
400
  {
@@ -323,13 +403,13 @@ var FormSection = ({
323
403
  children: title
324
404
  }
325
405
  ) : null,
326
- hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles3.description, { color: colors.textSecondary }], children: description }) : null,
406
+ hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles5.description, { color: colors.textSecondary }], children: description }) : null,
327
407
  children
328
408
  ] }) })
329
409
  );
330
410
  };
331
411
  var ACTIONS_GAP = 12;
332
- var styles4 = StyleSheet.create({
412
+ var styles6 = StyleSheet.create({
333
413
  container: {
334
414
  flexDirection: "row",
335
415
  justifyContent: "flex-end",
@@ -349,7 +429,7 @@ var FormActions = ({
349
429
  saveTestID = "save-button",
350
430
  cancelTestID = "cancel-button",
351
431
  testID
352
- }) => /* @__PURE__ */ jsxs(View, { style: [styles4.container, containerStyle], testID, children: [
432
+ }) => /* @__PURE__ */ jsxs(View, { style: [styles6.container, containerStyle], testID, children: [
353
433
  typeof onCancel === "function" ? /* @__PURE__ */ jsx(
354
434
  Button,
355
435
  {
@@ -629,9 +709,9 @@ function withAlpha(color, alpha) {
629
709
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
630
710
  }
631
711
  function useThemedInput(options = {}) {
632
- const { hasError = false, onFocus, onBlur } = options;
712
+ const { hasError = false, onFocus, onBlur, editable = true } = options;
633
713
  const { theme } = useUi();
634
- const { colors, palette, semantic } = theme;
714
+ const { colors, palette } = theme;
635
715
  const [isFocused, setIsFocused] = useState(false);
636
716
  const [isHovered, setIsHovered] = useState(false);
637
717
  const handleFocus = useCallback(
@@ -651,13 +731,14 @@ function useThemedInput(options = {}) {
651
731
  const handleHoverIn = useCallback(() => setIsHovered(true), []);
652
732
  const handleHoverOut = useCallback(() => setIsHovered(false), []);
653
733
  const primary = palette.primary["500"];
654
- const errorColor = semantic.error["500"];
734
+ const errorColor = fieldErrorInk(theme);
655
735
  const style = useMemo(() => {
656
736
  const restBorder = hasError ? errorColor : colors.border;
657
737
  const focusBorder = hasError ? errorColor : primary;
658
738
  const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
659
- const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
660
- const base = { color: colors.text, borderColor, backgroundColor };
739
+ const editableFill = isFocused ? colors.surface : colors.surfaceElevated;
740
+ const backgroundColor = editable ? editableFill : colors.background;
741
+ const base = { color: editable ? colors.text : colors.textSecondary, borderColor, backgroundColor };
661
742
  if (!IS_WEB5) return base;
662
743
  const ringColor = hasError ? errorColor : themeFocusRingColor(theme);
663
744
  const ring = isFocused ? focusRingOutline(ringColor) : NO_RING;
@@ -677,6 +758,9 @@ function useThemedInput(options = {}) {
677
758
  colors.border,
678
759
  colors.surface,
679
760
  colors.surfaceElevated,
761
+ colors.background,
762
+ colors.textSecondary,
763
+ editable,
680
764
  primary,
681
765
  errorColor,
682
766
  theme
@@ -729,7 +813,7 @@ var ThemedTextInput = React2.forwardRef(function ThemedTextInput2({
729
813
  placeholderTextColor: themedPlaceholder,
730
814
  focusBind,
731
815
  hoverBind
732
- } = useThemedInput({ hasError, onFocus, onBlur });
816
+ } = useThemedInput({ hasError, onFocus, onBlur, editable: rest.editable });
733
817
  const webA11y = webFieldA11y({ describedById, hasError, required: requiredField });
734
818
  const boxStyle = unstyledBox ? null : controlStyles.input;
735
819
  const multilineStyle = rest.multiline === true && !unstyledBox ? boxStyles.multiline : null;
@@ -1165,7 +1249,7 @@ function chipIdentity(option, selected, config) {
1165
1249
  ariaChecked: isRadio ? selected : void 0
1166
1250
  };
1167
1251
  }
1168
- var styles5 = StyleSheet.create({
1252
+ var styles7 = StyleSheet.create({
1169
1253
  chipContainer: {
1170
1254
  flexDirection: "row",
1171
1255
  flexWrap: "wrap",
@@ -1251,7 +1335,7 @@ var ChipSelector = ({
1251
1335
  View,
1252
1336
  {
1253
1337
  accessibilityRole: isRadioGroup ? "radiogroup" : void 0,
1254
- style: styles5.chipContainer,
1338
+ style: styles7.chipContainer,
1255
1339
  ...webFieldA11y({ describedById, hasError }),
1256
1340
  ...radio.groupProps,
1257
1341
  children: options.map(renderChip)
@@ -1261,7 +1345,7 @@ var ChipSelector = ({
1261
1345
  );
1262
1346
  };
1263
1347
  var DESCRIPTION_FONT_SIZE2 = 12;
1264
- var styles6 = StyleSheet.create({
1348
+ var styles8 = StyleSheet.create({
1265
1349
  container: {
1266
1350
  flexDirection: "row",
1267
1351
  justifyContent: "space-between",
@@ -1322,15 +1406,15 @@ var FormSwitch = ({
1322
1406
  accessible: false,
1323
1407
  disabled,
1324
1408
  onPress: toggle,
1325
- style: [styles6.container, containerStyle],
1409
+ style: [styles8.container, containerStyle],
1326
1410
  tabIndex: -1,
1327
1411
  testID: `${testID}-row`,
1328
1412
  children: [
1329
- /* @__PURE__ */ jsxs(View, { style: styles6.textColumn, children: [
1330
- /* @__PURE__ */ jsx(Text, { style: [styles6.label, themeStyles.label], children: label }),
1331
- hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles6.description, themeStyles.description], children: description }) : null
1413
+ /* @__PURE__ */ jsxs(View, { style: styles8.textColumn, children: [
1414
+ /* @__PURE__ */ jsx(Text, { style: [styles8.label, themeStyles.label], children: label }),
1415
+ hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles8.description, themeStyles.description], children: description }) : null
1332
1416
  ] }),
1333
- /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles6.switchSlot, testID: `${testID}-hitbox`, children: /* @__PURE__ */ jsx(
1417
+ /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles8.switchSlot, testID: `${testID}-hitbox`, children: /* @__PURE__ */ jsx(
1334
1418
  Switch,
1335
1419
  {
1336
1420
  accessibilityHint: accessibilityHint ?? label,
@@ -1353,7 +1437,7 @@ var BOX_SIZE = 20;
1353
1437
  var BOX_RADIUS = 5;
1354
1438
  var CHECK_FONT_SIZE = 14;
1355
1439
  var DISABLED_OPACITY = 0.5;
1356
- var styles7 = StyleSheet.create({
1440
+ var styles9 = StyleSheet.create({
1357
1441
  container: {
1358
1442
  flexDirection: "row",
1359
1443
  alignItems: "flex-start",
@@ -1445,18 +1529,18 @@ var FormCheckbox = ({
1445
1529
  onPress: toggle,
1446
1530
  role: "checkbox",
1447
1531
  style: [
1448
- styles7.container,
1449
- hasHint ? null : styles7.centered,
1532
+ styles9.container,
1533
+ hasHint ? null : styles9.centered,
1450
1534
  containerStyle,
1451
1535
  disabled ? { opacity: DISABLED_OPACITY } : null
1452
1536
  ],
1453
1537
  testID,
1454
1538
  ...keyboardProps,
1455
1539
  children: [
1456
- /* @__PURE__ */ jsx(View, { style: [styles7.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsx(Text, { style: [styles7.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
1457
- /* @__PURE__ */ jsxs(View, { style: styles7.textColumn, children: [
1458
- /* @__PURE__ */ jsx(Text, { style: [styles7.label, themeStyles.label], children: label }),
1459
- hasHint ? /* @__PURE__ */ jsx(Text, { nativeID: hintId, style: [styles7.hint, themeStyles.hint], children: hintText }) : null
1540
+ /* @__PURE__ */ jsx(View, { style: [styles9.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsx(Text, { style: [styles9.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
1541
+ /* @__PURE__ */ jsxs(View, { style: styles9.textColumn, children: [
1542
+ /* @__PURE__ */ jsx(Text, { style: [styles9.label, themeStyles.label], children: label }),
1543
+ hasHint ? /* @__PURE__ */ jsx(Text, { nativeID: hintId, style: [styles9.hint, themeStyles.hint], children: hintText }) : null
1460
1544
  ] })
1461
1545
  ]
1462
1546
  }
@@ -1582,7 +1666,7 @@ function TypeaheadControl({
1582
1666
  testID
1583
1667
  }) {
1584
1668
  const { theme } = useUi();
1585
- const { colors, palette, semantic } = theme;
1669
+ const { colors, palette } = theme;
1586
1670
  const anchorRef = useRef(null);
1587
1671
  const [isOpen, setIsOpen] = useState(false);
1588
1672
  const exactPick = useMemo(() => options.some((o) => o.label === value), [options, value]);
@@ -1630,7 +1714,7 @@ function TypeaheadControl({
1630
1714
  onFocus: () => setIsOpen(true),
1631
1715
  onBlur: handleBlur,
1632
1716
  onSubmitEditing: handleSubmit,
1633
- style: [controlStyles.input, { borderColor: hasError ? semantic.error["500"] : colors.border, backgroundColor: colors.surface, color: colors.text }],
1717
+ style: [controlStyles.input, { borderColor: hasError ? fieldErrorInk(theme) : colors.border, backgroundColor: colors.surface, color: colors.text }],
1634
1718
  testID: `${testID}-input`
1635
1719
  }
1636
1720
  ),
@@ -1648,7 +1732,7 @@ function TypeaheadControl({
1648
1732
  usePortal: true
1649
1733
  }
1650
1734
  ) : null,
1651
- hasError ? /* @__PURE__ */ jsx(Text, { role: "alert", style: [controlStyles.error, { color: semantic.error["500"] }], testID: `${testID}-error`, children: error }) : null
1735
+ hasError ? /* @__PURE__ */ jsx(Text, { role: "alert", style: [controlStyles.error, { color: fieldErrorInk(theme) }], testID: `${testID}-error`, children: error }) : null
1652
1736
  ] });
1653
1737
  }
1654
1738
  var WEB_DATE_PROPS = typeof document !== "undefined" ? { type: "date" } : {};
@@ -1748,6 +1832,6 @@ function toggleGroupSelection(selected, groupKeys, order) {
1748
1832
  return order === void 0 ? merged : order.filter((key) => merged.includes(key));
1749
1833
  }
1750
1834
 
1751
- export { AnchoredMenu, ChipSelector, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, Field, FormActions, FormCell, FormCheckbox, FormField, FormGrid, FormSection, FormSwitch, SelectControl, ThemedTextInput, TypeaheadControl, controlStyles, groupAriaChecked, isUnmatched, resolveGroupSelection, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
1835
+ export { AlignToControl, AnchoredMenu, ChipSelector, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, Field, FormActions, FormCell, FormCheckbox, FormField, FormGrid, FormSection, FormSwitch, INLINE_FIELD_ROW_MIN_WIDTH, InlineFieldRow, SelectControl, ThemedTextInput, TypeaheadControl, controlStyles, groupAriaChecked, inlineFieldRowLayout, isUnmatched, resolveGroupSelection, suggestOptions, toggleGroupSelection, useRecentValues, useThemedInput };
1752
1836
  //# sourceMappingURL=index.mjs.map
1753
1837
  //# sourceMappingURL=index.mjs.map