@crewlethq/ui 0.45.0 → 0.46.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.
Files changed (123) hide show
  1. package/README.md +22 -0
  2. package/dist/AgentHandoffPanel/index.js +6 -3
  3. package/dist/AppShell/index.js +3 -3
  4. package/dist/ArrangeList/index.js +5 -3
  5. package/dist/ArtifactBar/index.js +5 -5
  6. package/dist/AvatarPicker/index.js +6 -6
  7. package/dist/Canvas/index.js +1 -1
  8. package/dist/CanvasBoard/index.js +1 -1
  9. package/dist/CanvasToolbar/index.js +1 -1
  10. package/dist/CatalogTile/index.js +1 -1
  11. package/dist/{CatalogTile-VPSLCKJE.css → CatalogTile-26XN7L2A.css} +13 -7
  12. package/dist/CharacterCount/CharacterCount.d.ts +63 -0
  13. package/dist/CharacterCount/index.d.ts +3 -0
  14. package/dist/CharacterCount/index.js +4 -0
  15. package/dist/CharacterCount-S735DICR.css +29 -0
  16. package/dist/Checkbox/index.js +5 -2
  17. package/dist/Combobox/index.js +5 -3
  18. package/dist/CommandPalette/index.js +6 -4
  19. package/dist/CommentThread/index.js +6 -3
  20. package/dist/Composer/index.js +5 -2
  21. package/dist/ConfirmField/index.js +5 -2
  22. package/dist/DataTable/index.js +8 -6
  23. package/dist/DataView/index.js +9 -7
  24. package/dist/DateTimePicker/index.js +1 -1
  25. package/dist/DocTabsEditor/index.js +5 -4
  26. package/dist/EditingStatus/index.js +2 -1
  27. package/dist/{Editor-AMO2B54T.js → Editor-6FXHMW64.js} +6 -4
  28. package/dist/FollowBanner/index.js +2 -2
  29. package/dist/HuePicker/index.js +5 -5
  30. package/dist/InlineEditor/InlineEditor.d.ts +86 -0
  31. package/dist/InlineEditor/index.d.ts +3 -0
  32. package/dist/InlineEditor/index.js +9 -0
  33. package/dist/InlineEditor-W55DDKML.css +72 -0
  34. package/dist/Input/FormField.d.ts +18 -1
  35. package/dist/Input/Textarea.d.ts +16 -0
  36. package/dist/Input/index.d.ts +2 -2
  37. package/dist/Input/index.js +4 -1
  38. package/dist/{Input-OQZBCRX5.css → Input-VDSV7VWJ.css} +19 -0
  39. package/dist/LinkInterstitial/index.js +3 -3
  40. package/dist/List/List.d.ts +16 -1
  41. package/dist/List/index.js +1 -1
  42. package/dist/{List-YHLIZFK4.css → List-XY445TKY.css} +31 -0
  43. package/dist/ListInput/ListInput.d.ts +87 -2
  44. package/dist/ListInput/index.d.ts +2 -2
  45. package/dist/ListInput/index.js +5 -2
  46. package/dist/{ListInput-O7LJKOIG.css → ListInput-DP2CSJ22.css} +118 -2
  47. package/dist/MarkdownEditor/index.js +5 -5
  48. package/dist/Menu/index.js +2 -2
  49. package/dist/MergeConflicts/index.js +5 -5
  50. package/dist/Modal/index.js +2 -2
  51. package/dist/PresenterView/index.js +3 -3
  52. package/dist/PrincipalPicker/index.js +6 -4
  53. package/dist/RelatedRow/index.js +2 -2
  54. package/dist/RichEditor/index.js +1 -1
  55. package/dist/SegmentedControl/index.js +4 -4
  56. package/dist/Select/index.js +1 -1
  57. package/dist/ShareDialog/index.js +3 -3
  58. package/dist/SheetColumnEditor/index.js +10 -8
  59. package/dist/SheetGrid/index.js +4 -4
  60. package/dist/SlideCompare/index.js +1 -1
  61. package/dist/SlideFilmstrip/index.js +3 -3
  62. package/dist/Switcher/index.js +5 -4
  63. package/dist/Tabs/index.js +3 -3
  64. package/dist/Text/Text.d.ts +8 -0
  65. package/dist/Text/index.js +1 -1
  66. package/dist/{Text-S3Q4O3IG.css → Text-XOCOU34C.css} +9 -0
  67. package/dist/ThemeSwitcher/index.js +5 -5
  68. package/dist/TileGrid/index.js +1 -1
  69. package/dist/{TileGrid-I6LNYD2H.css → TileGrid-UYMVVVJK.css} +30 -0
  70. package/dist/TimeWindowPicker/index.js +5 -3
  71. package/dist/TreeCanvas/index.js +1 -1
  72. package/dist/VersionCompare/index.js +5 -5
  73. package/dist/{chunk-5AHDGYKN.js → chunk-2KQR6JKE.js} +2 -2
  74. package/dist/{chunk-AGNZIT3Z.js → chunk-2LPNWAN6.js} +1 -1
  75. package/dist/{chunk-MVGCZ655.js → chunk-2UVMIM2J.js} +1 -1
  76. package/dist/{chunk-NF6SWHQI.js → chunk-32MTQ6DN.js} +2 -2
  77. package/dist/{chunk-QOP7QYUW.js → chunk-34RMTXDZ.js} +1 -1
  78. package/dist/{chunk-Q7HLZH32.js → chunk-3QSKDZR5.js} +1 -1
  79. package/dist/{chunk-BVFMLSE3.js → chunk-5EBDOE3C.js} +1 -1
  80. package/dist/{chunk-BF5WV7NM.js → chunk-6FVGSWLS.js} +1 -1
  81. package/dist/chunk-7P336RA5.js +147 -0
  82. package/dist/{chunk-KKUJSMLF.js → chunk-DY4DQ7EB.js} +1 -1
  83. package/dist/{chunk-4DIKLY53.js → chunk-HBSR5DL6.js} +1 -1
  84. package/dist/{chunk-KA75QPFM.js → chunk-ILFEWRLG.js} +1 -1
  85. package/dist/{chunk-Y4GXCKOC.js → chunk-KOJKX3U6.js} +3 -3
  86. package/dist/{chunk-FG243QHY.js → chunk-KZDMAQNJ.js} +1 -1
  87. package/dist/{chunk-TC7DJTXI.js → chunk-MULOZYHL.js} +2 -0
  88. package/dist/{chunk-ZUTUI27P.js → chunk-N4GBDK4L.js} +1 -1
  89. package/dist/{chunk-7CXGSS7D.js → chunk-O6H5XXVK.js} +1 -1
  90. package/dist/{chunk-GI3S6RMM.js → chunk-PWZT6CZY.js} +3 -3
  91. package/dist/{chunk-NNTO3TNC.js → chunk-Q6CQMS6R.js} +1 -1
  92. package/dist/{chunk-IKBK4A5O.js → chunk-QP2P6GDQ.js} +1 -1
  93. package/dist/{chunk-W53CWWX3.js → chunk-RCKRJXFV.js} +2 -2
  94. package/dist/{chunk-U44WGRFW.js → chunk-RFPCHMSX.js} +1 -1
  95. package/dist/{chunk-X6IJ7DAX.js → chunk-RL75QTNT.js} +2 -2
  96. package/dist/{chunk-N3SHQ3VU.js → chunk-RRPQMMCZ.js} +1 -1
  97. package/dist/{chunk-EY7LACIR.js → chunk-RWET7W22.js} +1 -1
  98. package/dist/{chunk-JF3TJUBU.js → chunk-RWWLVDRE.js} +1 -1
  99. package/dist/{chunk-LSRQJDGR.js → chunk-SEPSXOGJ.js} +1 -1
  100. package/dist/{chunk-MCOC3KQA.js → chunk-SQ4JFHO7.js} +1 -1
  101. package/dist/chunk-SQZEVWVC.js +436 -0
  102. package/dist/{chunk-DL5ERIFF.js → chunk-SS4XCUUJ.js} +2 -2
  103. package/dist/{chunk-GPIRKHGS.js → chunk-T7DXSFUX.js} +1 -1
  104. package/dist/{chunk-NZWX27J7.js → chunk-TDQ262TB.js} +19 -3
  105. package/dist/{chunk-5GNA4RKP.js → chunk-TGSIVB6H.js} +1 -1
  106. package/dist/{chunk-UO5VJHWP.js → chunk-U4HOKU6P.js} +2 -2
  107. package/dist/chunk-XCUUI7E7.js +62 -0
  108. package/dist/{chunk-ENHLVF6Q.js → chunk-XS2FJVMT.js} +1 -1
  109. package/dist/{chunk-FV6H7H5I.js → chunk-YBJVIY5E.js} +4 -4
  110. package/dist/{chunk-6MAQ2SVP.js → chunk-YR3ZURBM.js} +3 -3
  111. package/dist/{chunk-ZI5G5RS7.js → chunk-Z734SV4E.js} +55 -8
  112. package/dist/{chunk-OWIGEE2Y.js → chunk-ZZCCNVF3.js} +3 -3
  113. package/dist/generated/index.d.ts +2 -0
  114. package/dist/generated/index.js +78 -76
  115. package/dist/index.js +78 -76
  116. package/dist/styles.css +333 -15
  117. package/package.json +3 -3
  118. package/dist/chunk-NMYGJJJK.js +0 -263
  119. package/dist/{chunk-6IPEPSB5.js → chunk-245IXG5P.js} +0 -0
  120. package/dist/{chunk-OED6LUWE.js → chunk-4A4QG6UC.js} +1 -1
  121. package/dist/{chunk-FYINAYAJ.js → chunk-6AKG2VLR.js} +1 -1
  122. package/dist/{chunk-E6HXFFWO.js → chunk-ISDEXIXH.js} +1 -1
  123. package/dist/{chunk-PCI7ZJ6Z.js → chunk-TMM32U7D.js} +1 -1
@@ -0,0 +1,86 @@
1
+ import { type FormHTMLAttributes, type ReactNode } from 'react';
2
+ import { type EditingStatusLabels } from '../EditingStatus/index.js';
3
+ /** Every string the editor's bar draws. */
4
+ export interface InlineEditorLabels {
5
+ save: string;
6
+ cancel: string;
7
+ /** Why Save does nothing while nothing has changed. */
8
+ unchanged: string;
9
+ /** Why Save does nothing while a field refuses its value. */
10
+ invalid: string;
11
+ /** Why Cancel does nothing while the save is in flight. */
12
+ saving: string;
13
+ /** The words after the shortcut's keycap: "to save". */
14
+ shortcut: string;
15
+ status?: Partial<EditingStatusLabels> | undefined;
16
+ }
17
+ export declare const INLINE_EDITOR_LABELS: InlineEditorLabels;
18
+ export interface InlineEditorProps extends Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'children' | 'aria-label'> {
19
+ /** The form's name, for a reader moving by landmark: "Edit the mission". */
20
+ label: string;
21
+ /** The fields. Their values, and what a save sends, are the caller's. */
22
+ children: ReactNode;
23
+ /** Save was asked for: the button, or Mod+Enter from inside the form. Not called while Save is unavailable. */
24
+ onSave: () => void;
25
+ /** Cancel was asked for: the button, or Escape from inside the form. Not called while saving. */
26
+ onCancel: () => void;
27
+ /** Anything differs from what was opened. Save waits for it, and the bar says so. */
28
+ dirty: boolean;
29
+ /** A save is in flight: Save spins, and neither Save nor Cancel can be pressed again. */
30
+ saving?: boolean | undefined;
31
+ /** A field refuses its value. Save waits for it. */
32
+ invalid?: boolean | undefined;
33
+ /** Why the last save failed, said as an alert above the bar. */
34
+ error?: ReactNode;
35
+ /** A note above the bar that is not an error: a `ConflictNote` once the record moved on. */
36
+ notice?: ReactNode;
37
+ /**
38
+ * Puts the focus in the first field when the editor opens, at the end of
39
+ * what it holds. ON by default, because an editor is opened by a press of
40
+ * Edit, and the reader's next keystroke is meant for the field, not for the
41
+ * button that has gone. Named as `ListInput`'s is.
42
+ */
43
+ focusOnMount?: boolean | undefined;
44
+ /** Draws the Mod+Enter keycap beside Save. ON by default. */
45
+ shortcutHint?: boolean | undefined;
46
+ /** Controls at the start of the bar, after the status: a "Reset to the default" link. */
47
+ secondaryActions?: ReactNode;
48
+ labels?: Partial<InlineEditorLabels> | undefined;
49
+ }
50
+ /**
51
+ * One record edited where it is read: its fields, then a bar with the editing
52
+ * status at its start and Cancel and Save at its end.
53
+ *
54
+ * WHY IT EXISTS. A page that shows a mission, a list of policies or a goal and
55
+ * lets the reader change it has the same five decisions to make every time,
56
+ * and each one made differently is a page that behaves unlike its neighbour:
57
+ * which keys save and cancel, whether a Save with nothing to save can be
58
+ * pressed, where the failure is said, where the focus goes when the form
59
+ * opens, and what the reader is told while the save is in flight. This is
60
+ * those five, once.
61
+ *
62
+ * IT OWNS NO STATE. The draft, the save call, what happens on a conflict and
63
+ * where the focus goes once the editor closes are the caller's: the record is
64
+ * the caller's, and so is the button that opened the editor, which is the one
65
+ * place the focus belongs when it closes.
66
+ *
67
+ * THE KEYS. Mod+Enter saves from any field and Escape cancels, each only
68
+ * once nothing inside has claimed the key first (an open menu's Escape closes
69
+ * the menu, not the editor) and never in the middle of an input method's
70
+ * composition. Enter alone belongs to the field: a newline in a paragraph,
71
+ * the next item in a list, and NOTHING in a single-line field. A browser
72
+ * submits a form on Enter in a one-line input, and here that would save a
73
+ * record mid-edit from the key a writer presses to finish a word, so the
74
+ * editor refuses that implicit submission: saving is always the one gesture.
75
+ *
76
+ * CANCEL DISCARDS AT ONCE. Escape and Cancel call `onCancel` whatever the
77
+ * draft holds, because whether a draft is worth a question is the caller's
78
+ * call. A caller holding a draft worth keeping asks first in `onCancel` while
79
+ * `dirty` ("Discard your changes to the mission?"), and closes only on yes.
80
+ *
81
+ * SAVE IS NEVER A DEAD BUTTON. While nothing has changed, while a field
82
+ * refuses its value and while a save is in flight it stays reachable and says
83
+ * why it does nothing, rather than being natively disabled, which takes the
84
+ * focus away from the reader who just pressed it.
85
+ */
86
+ export declare function InlineEditor({ label, children, onSave, onCancel, dirty, saving, invalid, error, notice, focusOnMount, shortcutHint, secondaryActions, labels: overrides, className, onKeyDown, ...rest }: InlineEditorProps): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import './InlineEditor.css';
2
+ export { INLINE_EDITOR_LABELS, InlineEditor } from './InlineEditor.js';
3
+ export type { InlineEditorLabels, InlineEditorProps } from './InlineEditor.js';
@@ -0,0 +1,9 @@
1
+ export { INLINE_EDITOR_LABELS, InlineEditor } from '../chunk-7P336RA5.js';
2
+ import '../chunk-MULOZYHL.js';
3
+ import '../chunk-SMBKXWML.js';
4
+ import '../chunk-OMV5W53A.js';
5
+ import '../chunk-OG6CL3EB.js';
6
+ import '../chunk-H5XTJ7GJ.js';
7
+ import '../chunk-NUZHC6MM.js';
8
+ import '../chunk-BZFWDIV6.js';
9
+ import '../chunk-ODEQTPFM.js';
@@ -0,0 +1,72 @@
1
+ /*
2
+ * InlineEditor: one record's fields, then a bar. It draws no surface of its
3
+ * own, so it reads as the card or the section it is opened in rather than as
4
+ * a form dropped on top of it.
5
+ */
6
+
7
+ .crewlet-inline-editor {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: var(--spacing-3);
11
+ min-width: 0;
12
+ margin: 0;
13
+ }
14
+
15
+ .crewlet-inline-editor__fields {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: var(--spacing-4);
19
+ min-width: 0;
20
+ }
21
+
22
+ .crewlet-inline-editor__error {
23
+ font-size: var(--font-size-sm);
24
+ line-height: var(--font-line-height-normal);
25
+ color: var(--color-feedback-danger-ink);
26
+ }
27
+
28
+ /*
29
+ * THE BAR: the status at its start, the two controls at its end, on one line
30
+ * where there is room. On a narrow card it wraps whole, the controls keeping
31
+ * their order at the end, so Save is never the control that falls off.
32
+ */
33
+ .crewlet-inline-editor__bar {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ gap: var(--spacing-2) var(--spacing-3);
39
+ padding-top: var(--spacing-3);
40
+ border-top-width: 1px;
41
+ border-top-style: solid;
42
+ border-top-color: var(--color-border-default);
43
+ }
44
+
45
+ .crewlet-inline-editor__start,
46
+ .crewlet-inline-editor__end {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ gap: var(--spacing-2);
50
+ min-width: 0;
51
+ }
52
+
53
+ .crewlet-inline-editor__end {
54
+ margin-inline-start: auto;
55
+ }
56
+
57
+ .crewlet-inline-editor__hint {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: var(--spacing-1);
61
+ font-size: var(--font-size-xs);
62
+ color: var(--color-text-tertiary);
63
+ white-space: nowrap;
64
+ }
65
+
66
+ /* A touch screen has no Mod+Enter to press: the hint goes where no keyboard
67
+ is the main way in. */
68
+ @media (hover: none) and (pointer: coarse) {
69
+ .crewlet-inline-editor__hint {
70
+ display: none;
71
+ }
72
+ }
@@ -1,4 +1,13 @@
1
1
  import { type HTMLAttributes, type ReactNode } from 'react';
2
+ import { type CharacterCountLabels } from '../CharacterCount/index.js';
3
+ /** A field's length limit, and how much of it the value uses. */
4
+ export interface FormFieldLimit {
5
+ /** How many characters the value holds. `countCharacters` counts them as a reader would. */
6
+ length: number;
7
+ /** The most it may hold. */
8
+ max: number;
9
+ labels?: Partial<CharacterCountLabels> | undefined;
10
+ }
2
11
  /** What a field's control needs to know about the row it sits in. */
3
12
  export interface FormFieldRender {
4
13
  /** The id the label points at. Put it on the control. */
@@ -28,6 +37,14 @@ export interface FormFieldProps extends Omit<HTMLAttributes<HTMLElement>, 'child
28
37
  helperOnFocus?: boolean | undefined;
29
38
  /** Why the last value was refused. */
30
39
  error?: ReactNode;
40
+ /**
41
+ * A length limit: the count is drawn at the end of the help line ("120 /
42
+ * 500"), joined into the description after the help and before the error,
43
+ * and a value past it marks the field invalid. A SOFT limit, never a
44
+ * `maxLength`: what was typed or pasted stays, and the count says by how
45
+ * much it is over. See `CharacterCount`.
46
+ */
47
+ limit?: FormFieldLimit | undefined;
31
48
  /** A control rendered beside the label, such as an Add button. */
32
49
  labelAction?: ReactNode;
33
50
  /**
@@ -65,4 +82,4 @@ export interface FormFieldProps extends Omit<HTMLAttributes<HTMLElement>, 'child
65
82
  * instant, the only line that said what right looks like disappeared. Both
66
83
  * lines are joined into `aria-describedby` in reading order, help first.
67
84
  */
68
- export declare const FormField: ({ label, required, optional, requiredNews, helper, helperOnFocus, error, labelAction, as, describedBy, htmlFor, className, children, ...rest }: FormFieldProps) => import("react").JSX.Element;
85
+ export declare const FormField: ({ label, required, optional, requiredNews, helper, helperOnFocus, error, limit, labelAction, as, describedBy, htmlFor, className, children, ...rest }: FormFieldProps) => import("react").JSX.Element;
@@ -12,5 +12,21 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
12
12
  * hinting that Enter adds the value. It does not take the pointer.
13
13
  */
14
14
  trailing?: ReactNode;
15
+ /**
16
+ * A value of ONE LINE that wraps so it can be read whole: a mission, a
17
+ * policy, a title with a long tail. A one-line `Input` scrolls a long value
18
+ * sideways, so a 160-character mission shows only its last words while it
19
+ * is being edited; this field wraps it instead, and still holds one line.
20
+ * Enter inserts no line break (the key stays the surrounding form's or
21
+ * list's to act on), and a line break pasted or dropped in is folded into
22
+ * a space, and said so (`foldedMessage`), since the value is then not
23
+ * quite what was pasted. `aria-multiline` is false, so it is announced as
24
+ * the one-line field it is. Pair it with `autoResize` and `rows={1}`.
25
+ */
26
+ singleLine?: boolean;
27
+ /** Said through the page's `Announcer` when a `singleLine` field folds line breaks it was given. */
28
+ foldedMessage?: string;
15
29
  }
30
+ /** A value with its line breaks, and the space around each, folded into one space. */
31
+ export declare function foldLineBreaks(text: string): string;
16
32
  export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
@@ -1,12 +1,12 @@
1
1
  import './Input.css';
2
2
  export { Input, InputAffix } from './Input.js';
3
3
  export type { InputAffixProps, InputAppearance, InputProps, InputSize, InputWidth } from './Input.js';
4
- export { Textarea } from './Textarea.js';
4
+ export { Textarea, foldLineBreaks } from './Textarea.js';
5
5
  export type { TextareaProps } from './Textarea.js';
6
6
  export { Label } from './Label.js';
7
7
  export type { LabelProps } from './Label.js';
8
8
  export { FormField } from './FormField.js';
9
- export type { FormFieldProps, FormFieldRender } from './FormField.js';
9
+ export type { FormFieldLimit, FormFieldProps, FormFieldRender } from './FormField.js';
10
10
  export { FormRow } from './FormRow.js';
11
11
  export type { FormRowProps } from './FormRow.js';
12
12
  export { FormSection } from './FormSection.js';
@@ -1,5 +1,8 @@
1
- export { FormField, FormRow, FormSection, Input, InputAffix, Label, ReadOnlyField, Textarea } from '../chunk-ZI5G5RS7.js';
1
+ export { FormField, FormRow, FormSection, Input, InputAffix, Label, ReadOnlyField, Textarea, foldLineBreaks } from '../chunk-Z734SV4E.js';
2
+ import '../chunk-XCUUI7E7.js';
2
3
  import '../chunk-PQADV6P6.js';
4
+ import '../chunk-RSAQOKH3.js';
5
+ import '../chunk-H5XTJ7GJ.js';
3
6
  import '../chunk-LACA4JGC.js';
4
7
  import '../chunk-NUZHC6MM.js';
5
8
  import '../chunk-BZFWDIV6.js';
@@ -561,6 +561,25 @@
561
561
  color: var(--color-text-tertiary);
562
562
  }
563
563
 
564
+ /*
565
+ * THE HELP LINE WITH A COUNT AT ITS END (`limit`). The help wraps in the room
566
+ * the count leaves, and the count keeps its width and its line: it is the
567
+ * figure a writer glances at, so it does not move as the sentence beside it
568
+ * wraps.
569
+ */
570
+ .crewlet-form-field__meta {
571
+ display: flex;
572
+ align-items: flex-start;
573
+ justify-content: space-between;
574
+ gap: var(--spacing-3);
575
+ }
576
+
577
+ .crewlet-form-field__meta > .crewlet-form-field__helper,
578
+ .crewlet-form-field__meta-spacer {
579
+ flex: 1;
580
+ min-width: 0;
581
+ }
582
+
564
583
  /* SHOWN ONLY WHILE THE FIELD HAS FOCUS (`helperOnFocus`): a dense form reads as
565
584
  labels and values at rest, and the sentence appears under the field the
566
585
  reader is in. Still described, so a screen reader hears it on entry. */
@@ -1,11 +1,11 @@
1
- export { LinkInterstitial, openExternal } from '../chunk-KKUJSMLF.js';
1
+ export { LinkInterstitial, openExternal } from '../chunk-DY4DQ7EB.js';
2
2
  import '../chunk-5CLW2CXT.js';
3
- import '../chunk-FYINAYAJ.js';
3
+ import '../chunk-6AKG2VLR.js';
4
4
  import '../chunk-SZZRIHGT.js';
5
5
  import '../chunk-PQADV6P6.js';
6
6
  import '../chunk-OMV5W53A.js';
7
- import '../chunk-H5XTJ7GJ.js';
8
7
  import '../chunk-OG6CL3EB.js';
8
+ import '../chunk-H5XTJ7GJ.js';
9
9
  import '../chunk-LACA4JGC.js';
10
10
  import '../chunk-NUZHC6MM.js';
11
11
  import '../chunk-BZFWDIV6.js';
@@ -32,6 +32,21 @@ export interface ListProps extends HTMLAttributes<HTMLElement> {
32
32
  * row measures itself against the same track list.
33
33
  */
34
34
  template?: string | undefined;
35
+ /**
36
+ * How much room a row takes. `comfortable` (the default) is the list as it
37
+ * always was. `compact` sets the rows a small step apart in the body's size
38
+ * at its normal leading, for a card that has to hold a long list on one
39
+ * screen: eight standing policies of two lines each, on a page that also
40
+ * shows the mission and the vision above them.
41
+ */
42
+ density?: 'comfortable' | 'compact' | undefined;
43
+ /**
44
+ * Holds each row's words to a reading measure while the row itself, and the
45
+ * rule under it, still spans the list: `normal` is prose's (68ch), `wide`
46
+ * is a list of statements read one at a time (80ch), a company's policies.
47
+ * Unset, the words take the row's width, as a feed's do.
48
+ */
49
+ measure?: 'normal' | 'wide' | undefined;
35
50
  }
36
51
  /**
37
52
  * The non-tabular list: an activity feed, a set of search results, a
@@ -43,7 +58,7 @@ export interface ListProps extends HTMLAttributes<HTMLElement> {
43
58
  * means nothing. What a feed does want is alignment, which `template` gives it
44
59
  * without the promise.
45
60
  */
46
- export declare function List({ variant, template, className, style, children, ...rest }: ListProps): import("react").JSX.Element;
61
+ export declare function List({ variant, template, density, measure, className, style, children, ...rest }: ListProps): import("react").JSX.Element;
47
62
  export declare namespace List {
48
63
  var Item: typeof ListItem;
49
64
  }
@@ -1,3 +1,3 @@
1
- export { List, ListItem } from '../chunk-NZWX27J7.js';
1
+ export { List, ListItem } from '../chunk-TDQ262TB.js';
2
2
  import '../chunk-BZFWDIV6.js';
3
3
  import '../chunk-ODEQTPFM.js';
@@ -166,6 +166,37 @@
166
166
  color: var(--color-text-tertiary);
167
167
  }
168
168
 
169
+ /*
170
+ * COMPACT: a row a small step tall, in the body's size at its normal leading,
171
+ * so a list of statements that each run to two lines still stands on one
172
+ * screen. Only the rhythm changes; the number column, the rule between rows
173
+ * and the inset a card gives them stay where they were.
174
+ */
175
+ .crewlet-list--compact .crewlet-list__row {
176
+ min-height: 0;
177
+ padding-block: var(--spacing-1);
178
+ }
179
+
180
+ .crewlet-list--compact.crewlet-list--numbered-divided .crewlet-list__row {
181
+ padding-block: var(--spacing-2);
182
+ font-size: var(--font-size-sm);
183
+ line-height: var(--font-line-height-normal);
184
+ text-wrap: pretty;
185
+ }
186
+
187
+ /*
188
+ * THE WORDS AT A READING MEASURE (`measure`), while the row and its rule
189
+ * still span the list. In a card the width of the page a policy otherwise
190
+ * runs to some 150 characters a line. A maximum, never a width.
191
+ */
192
+ .crewlet-list--measure-normal .crewlet-list__content {
193
+ max-inline-size: var(--size-measure);
194
+ }
195
+
196
+ .crewlet-list--measure-wide .crewlet-list__content {
197
+ max-inline-size: var(--size-measure-wide);
198
+ }
199
+
169
200
  /* ---- States ------------------------------------------------------------ */
170
201
 
171
202
  /*
@@ -1,4 +1,26 @@
1
1
  import { type ReactNode } from 'react';
2
+ /**
3
+ * The list a form would save: `value` with the new-item box's text appended,
4
+ * trimmed, when there is any.
5
+ *
6
+ * WHY A FORM NEEDS IT. Text typed into the new-item box is not an item until
7
+ * Enter adds it, and a writer who types a policy and presses the form's Save
8
+ * expects it saved, not silently dropped. An owner that holds the box's text
9
+ * (`draft` and `onDraftChange`) counts it as unsaved and saves it with this,
10
+ * so the rule is written once rather than by each form.
11
+ */
12
+ export declare function withDraft(value: readonly string[], draft: string | undefined): string[];
13
+ /** What a caller can ask the list to do from outside it. */
14
+ export interface ListInputHandle {
15
+ /**
16
+ * Puts the focus in the item at `index` (in `value`'s order), with the caret
17
+ * at its end: the first item a failed save refused, or the one a conflict
18
+ * kept. Does nothing for an index the list does not hold.
19
+ */
20
+ focusItem: (index: number) => void;
21
+ /** Puts the focus in the new-item box, when the list is not full. */
22
+ focusNew: () => void;
23
+ }
2
24
  export interface ListInputProps {
3
25
  /** The list's name, such as "Responsibilities". It is the group's legend. */
4
26
  label: string;
@@ -6,7 +28,26 @@ export interface ListInputProps {
6
28
  itemName: string;
7
29
  value: readonly string[];
8
30
  onChange: (next: string[]) => void;
9
- /** Items are paragraphs: Shift+Enter makes a newline. */
31
+ /**
32
+ * The text in the new-item box, held by the owner. Left out, the list holds
33
+ * it itself. Hold it wherever a form saves the list: text typed and not yet
34
+ * added is part of what the writer means to save, so the owner counts it
35
+ * as a change and saves it (`withDraft`) rather than dropping it.
36
+ */
37
+ draft?: string | undefined;
38
+ /** Every change to the new-item box's text, an add clearing it included. */
39
+ onDraftChange?: ((next: string) => void) | undefined;
40
+ /**
41
+ * The legend is kept for assistive technology and drawn for nobody, for a
42
+ * list whose name is already the heading right above it (a card titled
43
+ * "Standing policies"). The add control stays where it is.
44
+ */
45
+ hideLabel?: boolean | undefined;
46
+ /**
47
+ * Items are paragraphs: Shift+Enter makes a newline. Without it each item
48
+ * is ONE line, which still wraps so a long one can be read while it is
49
+ * edited, and takes no line break however it arrives.
50
+ */
10
51
  multiline?: boolean | undefined;
11
52
  /** Where the values come from, or what shape they take. */
12
53
  helper?: ReactNode | undefined;
@@ -29,6 +70,45 @@ export interface ListInputProps {
29
70
  * focus to its own first control. Named as `TagsInput`'s is.
30
71
  */
31
72
  focusOnMount?: boolean | undefined;
73
+ /**
74
+ * Each row carries a grip it can be DRAGGED by, as well as Move up and Move
75
+ * down. Off by default. The grip is a pointer's affordance only, hidden from
76
+ * a screen reader and out of the tab order, because the keyboard already
77
+ * has the whole of reordering (Alt+Up and Alt+Down from the item, and the
78
+ * two buttons) and a third control per row that does the same thing is a
79
+ * row a keyboard has to Tab through for nothing.
80
+ */
81
+ reorderByDrag?: boolean | undefined;
82
+ /**
83
+ * The most items the list holds. Once it is full the new-item box gives way
84
+ * to a sentence saying so (`fullMessage`), the add control does nothing,
85
+ * and every item stays editable, movable and removable.
86
+ */
87
+ maxItems?: number | undefined;
88
+ /**
89
+ * The most characters one item holds, counted on its TRIMMED text as a
90
+ * server judging the saved list does, and as code points (see
91
+ * `countCharacters`). A SOFT limit, as `FormField`'s
92
+ * `limit` is: an item past it keeps what was typed, draws its count in the
93
+ * danger ink and is marked invalid; the new-item box refuses to add one,
94
+ * and shows its count from nine tenths of the way.
95
+ */
96
+ maxLength?: number | undefined;
97
+ /**
98
+ * Removing an item leaves a row where it was, saying so, with Undo (which
99
+ * puts it back where it was, with the focus in it) and Dismiss. The focus
100
+ * goes to Undo. The row goes on the list's next change or on Dismiss, so an
101
+ * Undo never restores an item into a list that has moved on.
102
+ */
103
+ undoRemove?: boolean | undefined;
104
+ /**
105
+ * A refusal of one item, said under it and linked to it, which marks it
106
+ * invalid: a server's word about policy 3, or a rule only the caller knows.
107
+ * Called for every item on every render, with its index and its value.
108
+ */
109
+ itemError?: ((index: number, value: string) => ReactNode) | undefined;
110
+ /** What the list says in place of the new-item box once it is full. */
111
+ fullMessage?: ((max: number) => string) | undefined;
32
112
  /** The name of the control beside the legend that appends what the box holds. */
33
113
  addLabel?: ((itemName: string) => string) | undefined;
34
114
  itemLabel?: ((name: string, position: string) => string) | undefined;
@@ -39,6 +119,11 @@ export interface ListInputProps {
39
119
  addedMessage?: ((itemName: string, position: string) => string) | undefined;
40
120
  removedMessage?: ((itemName: string, position: string) => string) | undefined;
41
121
  movedMessage?: ((itemName: string, position: number, count: number) => string) | undefined;
122
+ /** The row a removed item leaves (`undoRemove`): "Policy 3 removed." */
123
+ removedNotice?: ((name: string, position: number) => string) | undefined;
124
+ undoLabel?: string | undefined;
125
+ dismissLabel?: string | undefined;
126
+ restoredMessage?: ((itemName: string, position: string) => string) | undefined;
42
127
  }
43
128
  /**
44
129
  * An ordered list of strings, edited in place.
@@ -75,4 +160,4 @@ export interface ListInputProps {
75
160
  *
76
161
  * Ported from the engine dashboard's `ui/ListField.tsx`.
77
162
  */
78
- export declare function ListInput({ label, itemName, value, onChange, multiline, helper, helperOnFocus, error, placeholder, disabled, optional, enterHint, focusOnMount, addLabel, itemLabel, newItemLabel, moveUpLabel, moveDownLabel, removeLabel, addedMessage, removedMessage, movedMessage, }: ListInputProps): import("react").JSX.Element;
163
+ export declare const ListInput: import("react").ForwardRefExoticComponent<ListInputProps & import("react").RefAttributes<ListInputHandle>>;
@@ -1,3 +1,3 @@
1
1
  import './ListInput.css';
2
- export { ListInput } from './ListInput.js';
3
- export type { ListInputProps } from './ListInput.js';
2
+ export { ListInput, withDraft } from './ListInput.js';
3
+ export type { ListInputHandle, ListInputProps } from './ListInput.js';
@@ -1,9 +1,12 @@
1
- export { ListInput } from '../chunk-NMYGJJJK.js';
2
- import '../chunk-ZI5G5RS7.js';
1
+ export { ListInput, withDraft } from '../chunk-SQZEVWVC.js';
2
+ import '../chunk-XJ2QEBMM.js';
3
+ import '../chunk-Z734SV4E.js';
4
+ import '../chunk-XCUUI7E7.js';
3
5
  import '../chunk-SMBKXWML.js';
4
6
  import '../chunk-PQADV6P6.js';
5
7
  import '../chunk-RSAQOKH3.js';
6
8
  import '../chunk-OMV5W53A.js';
9
+ import '../chunk-OG6CL3EB.js';
7
10
  import '../chunk-H5XTJ7GJ.js';
8
11
  import '../chunk-LACA4JGC.js';
9
12
  import '../chunk-NUZHC6MM.js';
@@ -13,6 +13,7 @@
13
13
  .crewlet-list-input__add {
14
14
  display: flex;
15
15
  align-items: flex-start;
16
+ gap: var(--spacing-2);
16
17
  }
17
18
 
18
19
  .crewlet-list-input__add > * {
@@ -69,10 +70,75 @@
69
70
  border-block-end-style: none;
70
71
  }
71
72
 
72
- .crewlet-list-input__item > .crewlet-input,
73
- .crewlet-list-input__item > .crewlet-textarea {
73
+ /*
74
+ * THE FIELD TAKES THE ROW'S WIDTH, and holds the control with whatever is
75
+ * said under it (its count, its refusal) as one column, so a note under an
76
+ * item sits under the item's words and not under its number or its buttons.
77
+ */
78
+ .crewlet-list-input__field {
79
+ display: flex;
80
+ flex: 1;
81
+ flex-direction: column;
82
+ min-width: 0;
83
+ }
84
+
85
+ .crewlet-list-input__field > .crewlet-input,
86
+ .crewlet-list-input__field > .crewlet-textarea,
87
+ .crewlet-list-input__field > .crewlet-textarea-shell {
88
+ width: 100%;
89
+ min-width: 0;
90
+ }
91
+
92
+ /* What is said under one item: its refusal at the start, its count at the end. */
93
+ .crewlet-list-input__note {
94
+ display: flex;
95
+ align-items: flex-start;
96
+ justify-content: space-between;
97
+ gap: var(--spacing-3);
98
+ padding-bottom: var(--spacing-1);
99
+ }
100
+
101
+ .crewlet-list-input__error {
102
+ min-width: 0;
103
+ font-size: var(--font-size-xs);
104
+ line-height: var(--font-line-height-normal);
105
+ color: var(--color-feedback-danger-ink);
106
+ }
107
+
108
+ /*
109
+ * THE ROW A REMOVED ITEM LEAVES (`undoRemove`): the item's own place in the
110
+ * order, at a row's height and on the row's rule, saying what happened in the
111
+ * quiet ink with Undo beside it, so the list does not close up over the place
112
+ * an Undo would put it back.
113
+ */
114
+ .crewlet-list-input__removed {
115
+ display: flex;
116
+ align-items: center;
117
+ gap: var(--spacing-2);
118
+ padding: var(--spacing-1) var(--spacing-3);
119
+ border-block-end-width: 1px;
120
+ border-block-end-style: solid;
121
+ border-block-end-color: var(--color-border-default);
122
+ background: var(--color-surface-subtle);
123
+ }
124
+
125
+ .crewlet-list-input__removed:last-child {
126
+ border-block-end-style: none;
127
+ }
128
+
129
+ .crewlet-list-input__removed-text {
74
130
  flex: 1;
75
131
  min-width: 0;
132
+ font-size: var(--font-size-compact);
133
+ color: var(--color-text-secondary);
134
+ }
135
+
136
+ /* Where the new-item box was, once the list holds all it may. */
137
+ .crewlet-list-input__full {
138
+ margin: 0;
139
+ font-size: var(--font-size-xs);
140
+ line-height: var(--font-line-height-normal);
141
+ color: var(--color-text-tertiary);
76
142
  }
77
143
 
78
144
  /*
@@ -140,8 +206,58 @@
140
206
  opacity: 1;
141
207
  }
142
208
 
209
+ /*
210
+ * THE GRIP (`reorderByDrag`) waits with the move pair: the pointer's way to
211
+ * take hold of a row, shown under the pointer and while the row holds the
212
+ * focus, in the quiet ink so the row's number stays the first mark on it.
213
+ */
214
+ .crewlet-list-input__handle {
215
+ display: inline-flex;
216
+ flex: none;
217
+ align-items: center;
218
+ justify-content: center;
219
+ margin-inline-start: calc(var(--spacing-1) * -1);
220
+ color: var(--color-text-tertiary);
221
+ cursor: grab;
222
+ opacity: 0;
223
+ transition: opacity var(--motion-duration-fast) var(--motion-easing-default);
224
+ }
225
+
226
+ .crewlet-list-input__handle:active {
227
+ cursor: grabbing;
228
+ }
229
+
230
+ .crewlet-list-input__item:hover .crewlet-list-input__handle,
231
+ .crewlet-list-input__item:focus-within .crewlet-list-input__handle {
232
+ opacity: 1;
233
+ }
234
+
235
+ /* The row being dragged keeps its place as an outline, so the list does not
236
+ close up under the pointer and the reader can see where it came from. */
237
+ .crewlet-list-input__item[data-reorder='dragging'] {
238
+ outline-width: 1px;
239
+ outline-style: dashed;
240
+ outline-color: var(--color-border-strong);
241
+ outline-offset: -1px;
242
+ }
243
+
244
+ .crewlet-list-input__item[data-reorder='dragging'] > * {
245
+ opacity: 0.4;
246
+ }
247
+
143
248
  @media (prefers-reduced-motion: reduce) {
144
249
  .crewlet-list-input__move {
145
250
  transition: none;
146
251
  }
252
+
253
+ .crewlet-list-input__handle {
254
+ transition: none;
255
+ }
256
+ }
257
+
258
+ /* With a hidden legend the add control stands at the end of the box's row. */
259
+ .crewlet-list-input__add-control {
260
+ display: inline-flex;
261
+ flex: none;
262
+ align-self: center;
147
263
  }