@nxgt/material 3.68.0 → 3.71.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/CHANGELOG.md +84 -0
- package/README.md +13 -9
- package/dist/{alert-dialog-BhARh_qC.js → alert-dialog-y2pbowVh.js} +1 -1
- package/dist/{async-slice-CHyRU5V3.js → async-slice-CGfWBOaV.js} +1 -1
- package/dist/{button-Du7fJ9i_.js → button-D54Hrjo0.js} +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-BVB6UYl-.js → chip-B5MhXBfm.js} +2 -2
- package/dist/{code-editor-D-GBs2yu.js → code-editor-mULs4SL8.js} +5 -5
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-xrgq1Am0.js → code-language-items-BWDnqUqn.js} +2 -2
- package/dist/components/ui/collections/server-table/server-table-phone-row.css +6 -0
- package/dist/components/ui/display/code/code-moonlight.css +37 -0
- package/dist/{components-iNCh4A6M.js → components-B_taYVcd.js} +19740 -18682
- package/dist/components.js +28 -28
- package/dist/{dialog-DFY_nsqF.js → dialog-AjCYZ2Vz.js} +4 -4
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-B6Yh4XJu.js → dropdown-menu-LN60hu_3.js} +1 -1
- package/dist/editor.js +673 -673
- package/dist/{empty-state-DGtwE50c.js → empty-state-Bo8G8W8G.js} +2 -2
- package/dist/{field-blur-Br7ij-a1.js → field-blur-Cnd6FfCH.js} +1 -1
- package/dist/{field-blur-context-CucMAZLj.js → field-blur-context-bUFZj8iN.js} +1 -1
- package/dist/{hooks-CYQJkKGc.js → hooks-Dt4PkQiZ.js} +2 -2
- package/dist/hooks.js +3 -3
- package/dist/{hover-card-C2J0GwWf.js → hover-card-QtGnvsnc.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-LzO5K6Wu.js → icon-badge-DLbpCQIm.js} +3 -3
- package/dist/{icon-button-vLJ4YrEW.js → icon-button-C59DqL7L.js} +1 -1
- package/dist/lib/components/ui/actions/fab/fab-dock-inset.d.ts +29 -0
- package/dist/lib/components/ui/actions/fab/fab-dock.d.ts +6 -1
- package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +3 -2
- package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +3 -3
- package/dist/lib/components/ui/collections/index.d.ts +1 -0
- package/dist/lib/components/ui/collections/record-actions-menu/index.d.ts +2 -0
- package/dist/lib/components/ui/collections/record-actions-menu/record-actions-dialogs.d.ts +15 -0
- package/dist/lib/components/ui/collections/record-actions-menu/record-actions-menu.d.ts +72 -0
- package/dist/lib/components/ui/collections/record-actions-menu/record-actions-rules.d.ts +68 -0
- package/dist/lib/components/ui/collections/record-actions-menu/record-actions-words.d.ts +52 -0
- package/dist/lib/components/ui/collections/record-actions-menu/use-record-actions.d.ts +51 -0
- package/dist/lib/components/ui/collections/record-actions-menu/use-record-flight.d.ts +16 -0
- package/dist/lib/components/ui/collections/row-menu/row-menu-column.d.ts +4 -0
- package/dist/lib/components/ui/collections/row-menu/row-menu.d.ts +1 -1
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +7 -5
- package/dist/lib/components/ui/collections/server-table/partials/server-table-column-sorting.d.ts +7 -0
- package/dist/lib/components/ui/collections/server-table/types.d.ts +3 -1
- package/dist/lib/components/ui/display/markdown/markdown.d.ts +12 -1
- package/dist/lib/components/ui/display/markdown/markdown.utils.d.ts +2 -0
- package/dist/lib/components/ui/display/markdown/mermaid-csp-scope.d.ts +8 -0
- package/dist/lib/components/ui/display/markdown/mermaid-markup.d.ts +20 -0
- package/dist/lib/components/ui/display/markdown/partials/markdown-code.d.ts +5 -1
- package/dist/lib/components/ui/feedback/confirm-dialog/action-refusal.d.ts +24 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-alert.d.ts +17 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-description.d.ts +10 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +24 -2
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-refusal.d.ts +7 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +6 -0
- package/dist/lib/components/ui/feedback/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/stale-notice/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/stale-notice/stale-notice.d.ts +15 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/country-field/index.d.ts +2 -0
- package/dist/lib/components/ui/fields/country-field/iso-countries.d.ts +2 -0
- package/dist/lib/components/ui/fields/index.d.ts +1 -0
- package/dist/lib/components/ui/fields/phone-field/index.d.ts +1 -1
- package/dist/lib/components/ui/fields/phone-field/parse-phone.d.ts +2 -0
- package/dist/lib/components/ui/fields/phone-field/phone-field.d.ts +8 -1
- package/dist/lib/components/ui/fields/phone-field/phone-form-field.d.ts +8 -1
- package/dist/lib/components/ui/fields/phone-field/use-phone-value.d.ts +2 -1
- package/dist/lib/components/ui/fields/postal-address/utils.d.ts +15 -0
- package/dist/lib/components/ui/fields/quantity-field/index.d.ts +3 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-field.d.ts +8 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-form-field.d.ts +8 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-row.d.ts +13 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-unit.d.ts +12 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-value.d.ts +9 -0
- package/dist/lib/components/ui/fields/quantity-field/types.d.ts +45 -0
- package/dist/lib/components/ui/forms/index.d.ts +1 -0
- package/dist/lib/components/ui/forms/use-server-field-errors.d.ts +21 -0
- package/dist/lib/components/ui/layout/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/record-page/index.d.ts +2 -0
- package/dist/lib/components/ui/layout/record-page/record-details.d.ts +40 -0
- package/dist/lib/components/ui/layout/record-page/record-page.d.ts +27 -0
- package/dist/lib/components/ui/media/upload/index.d.ts +1 -0
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.d.ts +6 -1
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.errors.d.ts +8 -0
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.utils.d.ts +2 -7
- package/dist/lib/components/ui/media/upload/network-upload-field/use-start-upload.d.ts +7 -3
- package/dist/lib/components/ui/media/upload/network-upload-field/use-upload-item-handlers.d.ts +2 -1
- package/dist/lib/components/ui/media/upload/s3-upload-field/s3-upload-field.d.ts +2 -5
- package/dist/lib/components/ui/media/upload/s3-upload-field/s3-upload-field.utils.d.ts +26 -1
- package/dist/lib/components/ui/media/upload/upload-error.d.ts +33 -0
- package/dist/lib/components/ui/selection/autocomplete/autocomplete-enter.d.ts +18 -0
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-chip-input.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-text-input.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/types.d.ts +3 -1
- package/dist/lib/components/ui/widgets/chat/chat-conversation-item.d.ts +8 -2
- package/dist/lib/components/ui/widgets/chat/chat-markdown.d.ts +4 -0
- package/dist/lib/components/ui/widgets/chat/chat-message-list.d.ts +8 -1
- package/dist/lib/components/ui/widgets/chat/index.d.ts +2 -0
- package/dist/lib/components/ui/widgets/chat/partials/chat-conversation-item-text.d.ts +2 -1
- package/dist/lib/components/ui/widgets/chat/partials/chat-empty-thread.d.ts +2 -0
- package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal-parts.d.ts +24 -0
- package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal.d.ts +39 -0
- package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal.rules.d.ts +17 -0
- package/dist/lib/components/ui/widgets/chat/proposal/chat-tool-activity.d.ts +16 -0
- package/dist/lib/components/ui/widgets/education/questions/partials/question-content.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/question-text.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/question.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/types.d.ts +10 -0
- package/dist/lib/i18n/resources/en/en.d.ts +49 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +50 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +49 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +50 -1
- package/dist/lib/i18n/resources/resources.d.ts +98 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/print/output/to-pdf-element.d.ts +6 -0
- package/dist/lib.js +1 -1
- package/dist/main.js +42 -42
- package/dist/map.js +8 -8
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +135 -131
- package/dist/{public-BHthpdpe.js → public-BSaSCwy7.js} +9 -9
- package/dist/{rich-code-block-DoW7l2ND.js → rich-code-block-BFVj1GWz.js} +5 -5
- package/dist/{sortable-provider-FLNMfr0o.js → sortable-provider-REwyjiOZ.js} +2 -2
- package/dist/{spinner-DCgDw8eq.js → spinner-BFZxqdWC.js} +1 -1
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-BMDAwMLh.js → theme-toggle-BHi6miz-.js} +1 -1
- package/dist/{toolbar-items-CkPEhQq7.js → toolbar-items-BTtoKVlx.js} +3 -3
- package/dist/{translate-material-DkcuQBNr.js → translate-material-R-uLthBy.js} +1 -1
- package/dist/{translator-DXdVRdMm.js → translator-ClbXOGQO.js} +100 -2
- package/dist/{typography-BNS_IbFI.js → typography-v-PJ4Zco.js} +1 -1
- package/dist/{use-chat-composer-B-tGRLkI.js → use-chat-composer-Rhan4Xv0.js} +2463 -2405
- package/dist/{use-heading-spy-DaqibCCm.js → use-heading-spy-CJnBkCY0.js} +179 -87
- package/dist/{use-material-translation-Bxl8sv1n.js → use-material-translation-CCWUeiEn.js} +1 -1
- package/dist/{use-return-focus-Y_Kx0QlW.js → use-return-focus-Duk40DZI.js} +1 -1
- package/docs/components/buttons-and-actions.md +24 -0
- package/docs/components/data-display.md +33 -0
- package/docs/components/data-table-filter-and-lists.md +117 -5
- package/docs/components/feedback-and-status.md +58 -2
- package/docs/components/form-fields.md +97 -3
- package/docs/components/media-and-files.md +55 -9
- package/docs/components/navigation-and-layout.md +58 -0
- package/docs/components/rich-widgets.md +37 -3
- package/docs/components/selection-and-choice.md +10 -3
- package/docs/guides/cards-and-tiles.md +4 -2
- package/docs/guides/csp.md +44 -6
- package/docs/guides/forms.md +55 -7
- package/docs/guides/i18n.md +6 -5
- package/docs/guides/printing.md +17 -7
- package/docs/previews/data-table-filter-lists-recordactionsmenu.webp +0 -0
- package/docs/previews/feedback-status-stalenotice.webp +0 -0
- package/docs/previews/form-fields-quantityfield.webp +0 -0
- package/docs/previews/media-files-networkuploadfield.webp +0 -0
- package/docs/previews/media-files-s3uploadfield.webp +0 -0
- package/docs/previews/media-files-uploadfield.webp +0 -0
- package/docs/previews/navigation-layout-recordpage.webp +0 -0
- package/docs/previews/rich-widgets-chat-action-proposal.webp +0 -0
- package/docs/roadmap.md +14 -1
- package/docs/troubleshooting.md +58 -11
- package/lib/components/ui/collections/server-table/server-table-phone-row.css +6 -0
- package/lib/components/ui/display/code/code-moonlight.css +37 -0
- package/package.json +2 -2
|
@@ -83,8 +83,8 @@ function Conversation({ messages }: { messages: Message[] }) {
|
|
|
83
83
|
Parts: `ChatInbox` (`list`, `thread`, `listHeader`, `pane`), `ChatConversationItem`,
|
|
84
84
|
`ChatThread`, `ChatThreadHeader`, `ChatMessageList`, `ChatMessage`,
|
|
85
85
|
`ChatMessageSeparator`, `ChatReplyQuote`, `ChatReactionGroup`, `ChatComposer`,
|
|
86
|
-
`ChatComposerAttachments`, `ChatComposerAttachmentChip`, `ChatMarkdown`,
|
|
87
|
-
`ChatTypingIndicator`, `ChatWidget`, `chatGroupPosition`, `ChatLayoutContext`,
|
|
86
|
+
`ChatComposerAttachments`, `ChatComposerAttachmentChip`, `ChatMarkdown`, `Markdown` documents `math`, `mermaid` and the untrusted-input guarantees (data-display.md#markdown).
|
|
87
|
+
`ChatTypingIndicator`, `ChatActionProposal`, `ChatToolActivity`, `ChatWidget`, `chatGroupPosition`, `ChatLayoutContext`,
|
|
88
88
|
`useChatComposerAttachments`. Built on [TypingIndicator](feedback-and-status.md#typingindicator),
|
|
89
89
|
[DaySeparator](data-display.md#dayseparator) and
|
|
90
90
|
[ScrollToLatestButton](buttons-and-actions.md#scrolltolatestbutton).
|
|
@@ -94,6 +94,30 @@ Parts: `ChatInbox` (`list`, `thread`, `listHeader`, `pane`), `ChatConversationIt
|
|
|
94
94
|
[<img src="../previews/rich-widgets-chat-composer.webp" height="96" alt="ChatComposer">](#chat-kit)
|
|
95
95
|
[<img src="../previews/rich-widgets-chat-widget.webp" height="96" alt="ChatWidget">](#chat-kit)
|
|
96
96
|
[<img src="../previews/rich-widgets-chat-advanced.webp" height="96" alt="ChatComposer with mentions">](#chat-kit)
|
|
97
|
+
[<img src="../previews/rich-widgets-chat-action-proposal.webp" height="96" alt="ChatActionProposal">](#chat-kit)
|
|
98
|
+
|
|
99
|
+
An assistant's proposed action and its tool steps, inside a list:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import {
|
|
103
|
+
ChatActionProposal,
|
|
104
|
+
ChatMessageList,
|
|
105
|
+
ChatToolActivity,
|
|
106
|
+
} from '@nxgt/material/components';
|
|
107
|
+
|
|
108
|
+
<ChatMessageList>
|
|
109
|
+
<ChatToolActivity name="find_absences" state="ok" />
|
|
110
|
+
<ChatActionProposal
|
|
111
|
+
title="Mark 12 absences as justified"
|
|
112
|
+
summary="Class 5B, 3 to 7 November."
|
|
113
|
+
deadline="2026-11-02T16:00:00Z"
|
|
114
|
+
status={status} // 'pending' until answered
|
|
115
|
+
busy={sending}
|
|
116
|
+
onConfirm={confirm}
|
|
117
|
+
onReject={reject}
|
|
118
|
+
/>
|
|
119
|
+
</ChatMessageList>;
|
|
120
|
+
```
|
|
97
121
|
|
|
98
122
|
| Prop | Type | Default | Effect |
|
|
99
123
|
| --- | --- | --- | --- |
|
|
@@ -105,8 +129,16 @@ Parts: `ChatInbox` (`list`, `thread`, `listHeader`, `pane`), `ChatConversationIt
|
|
|
105
129
|
| `ChatMessage` `onReply` / `onReact` / `onDelete` / `menuItems` | callbacks / `ReactNode` | — | the toolbar shown on hover or focus (a tap on touch): react, reply, copy, and a "more" menu |
|
|
106
130
|
| `ChatMessage` `reactions` / `onReactionToggle` | `ChatReaction[]` | — | the reaction pills under the bubble |
|
|
107
131
|
| `ChatMessage` `isStreaming` / `copyable` | `boolean` | `false` / `true` | streaming caret; Copy in the toolbar |
|
|
132
|
+
| `ChatActionProposal` `title` / `summary` / `toolLabel` / `children` | `ReactNode` | — | a card for an action the assistant proposes: what, its effect, the tool behind it (a slot) and extra detail |
|
|
133
|
+
| `ChatActionProposal` `status` | `'pending' \| 'running' \| 'confirmed' \| 'failed' \| 'rejected' \| 'expired'` | `'pending'` | a toned badge with its icon and words (colour is never the only signal); only `pending` shows the buttons; leaving `pending` moves the focus to the status |
|
|
134
|
+
| `ChatActionProposal` `onConfirm` / `onReject` | `() => void` | — | the Confirm (filled) and Reject (tonal) buttons; `confirmLabel` / `rejectLabel` reword them; `actions` replaces both |
|
|
135
|
+
| `ChatActionProposal` `busy` / `disabled` | `boolean` | `false` | an answer is being sent (both disabled, Confirm spins) / a read-only viewer |
|
|
136
|
+
| `ChatActionProposal` `deadline` | `Date \| string \| number` | — | "Respond by …" in a `<time>`, in the active language; `expired` says "Expired on" |
|
|
137
|
+
| `ChatActionProposal` `failureCode` / `failureMessage` | `string` / `ReactNode` | — | a `failed` card's reason, in an `alert`, with the code |
|
|
138
|
+
| `ChatToolActivity` `name` / `state` / `detail` | `ReactNode` / `'running' \| 'ok' \| 'failed'` / `ReactNode` | — / `'running'` | one compact row for a tool step in a `ChatMessageList`: the name in mono, Running (spinner), Done or Failed |
|
|
108
139
|
| `ChatThread` / `ChatMessageList` `layout` | `'bubbles' \| 'flat'` | `'bubbles'` | a messenger's bubbles, or a team chat's rows (name and time over each run) |
|
|
109
140
|
| `ChatThread` / `ChatMessageList` `density` | `'comfortable' \| 'compact'` | `'comfortable'` | tighter bubbles, smaller avatars |
|
|
141
|
+
| `ChatMessageList` `viewportProps` | `ComponentProps` of the viewport | `{ tabIndex: 0 }` | attributes of the scrolling element; it is a tab stop so the keyboard can scroll the transcript (`tabIndex: -1` opts out) |
|
|
110
142
|
| `ChatThread` `surface` | `'plain' \| 'card'` | `'plain'` | fills its parent, or draws a rounded bordered card |
|
|
111
143
|
| `ChatThread` `composerProps` | `ChatComposerProps` | — | renders a `ChatComposer` under the list |
|
|
112
144
|
| `ChatMessageList` `isTyping` / `typingAvatar` | `boolean` / `ReactNode` | `false` | a typing bubble at the end of the list |
|
|
@@ -117,6 +149,7 @@ Parts: `ChatInbox` (`list`, `thread`, `listHeader`, `pane`), `ChatConversationIt
|
|
|
117
149
|
| `ChatComposer` `sendOnEnter` | `boolean` | `true` | Enter sends and Shift+Enter breaks the line; `false`: Ctrl/⌘+Enter sends. Never while an IME composes |
|
|
118
150
|
| `ChatComposer` `emojis` / `mentions` | `EmojiItem[]` / `MentionItem[]` | — | enables the emoji picker and the `@` list |
|
|
119
151
|
| `ChatThreadHeader` | `title`, `subtitle`, `avatarSrc`, `status`, `actions`, `onBack` | — | the title bar; inside a `ChatInbox` it carries the back button on a narrow screen |
|
|
152
|
+
| `ChatConversationItem` `title` / `titleText` | `ReactNode` / `string` | — | the clipped title carries its full text in a `title` attribute when it is a string; `titleText` gives it when `title` is a node |
|
|
120
153
|
| `ChatConversationItem` `status` / `isTyping` | `StatusTone` / `boolean` | — | presence dot; "Typing…" instead of the preview |
|
|
121
154
|
| `ChatInbox` `pane` / `defaultPane` | `'list' \| 'thread'` | `'list'` | which pane shows below the `@md` container width |
|
|
122
155
|
| `ChatWidget` `open` / `defaultOpen` / `onOpenChange` | `boolean` | `false` | the floating launcher's panel (full screen below `sm`); `unreadCount` on the launcher, `side`, `strategy` |
|
|
@@ -2457,9 +2490,10 @@ French bundle — see [i18n](../guides/i18n.md).
|
|
|
2457
2490
|
| `QuestionCard` `renderCode` | `(field: QuestionCodeField) => ReactNode` | — | the code field of a `code` question; `field` is `{ value, onChange, label, labelledBy, describedBy, language, maxLength, readOnly, disabled }`. Keeps Monaco out of the card's bundle |
|
|
2458
2491
|
| `Question` `description` | `string` | — | `open`, `code`: a persistent helper line under the answer field ("A person will mark this answer"), linked to it by `aria-describedby` and passed to `renderCode` as `field.describedBy`; always shown, unlike `hint` (a reveal button). Its id is `${promptId}-description` |
|
|
2459
2492
|
| `QuestionCard` `promptId` | `string` | generated | the id of the prompt text: point a wrapping section's `aria-labelledby` at it (and `aria-describedby` at `${promptId}-description`) |
|
|
2493
|
+
| `QuestionCard` `preview` | `boolean` | `false` | an author's preview, `<QuestionCard question={q} preview />`: forces `revealed` and `readOnly`, drops `onValueChange`, and shows the marks (the `question.correct` options marked, the expected text or number filled in) and the `explanation`, with no verdict banner. `verdict` and `showFeedback` are always ignored in a preview; `value` is ignored when the question has a `correct` — an `open` or `code` question has none, so pass its reference answer as `value`. Not for a learner's attempt |
|
|
2460
2494
|
| `QuestionCard` `readOnly` / `busy` | `boolean` | `false` | `readOnly`: an answer already submitted, shown without the revealed marks; `busy`: read-only and `aria-busy` while it is saved; every kind honours both |
|
|
2461
2495
|
| new strings | `material.education.kind-open`, `kind-code`, `code-placeholder` | — | English fallbacks and a French bundle |
|
|
2462
|
-
| `QuestionCard` `variant` / `density` / `showShortcuts` / `number` / `showFeedback` | — | `'card'`, `'comfortable'`, `false`, —, `true` | framed or plain, spacing, key badges, "Question 2", the verdict banner |
|
|
2496
|
+
| `QuestionCard` `variant` / `density` / `showShortcuts` / `number` / `showFeedback` | — | `'card'`, `'comfortable'`, `false`, —, `true` | framed or plain, spacing, key badges, "Question 2", the verdict banner (for a card with marks and no banner, use `preview`) |
|
|
2463
2497
|
|
|
2464
2498
|
An open answer and a code answer, with the code editor from its own subpath
|
|
2465
2499
|
(it is client-only, so load it lazily in an SSR app):
|
|
@@ -417,7 +417,7 @@ disabled with the field; Backspace in the empty input removes the last one.
|
|
|
417
417
|
| `getOptionValue`, `getOptionLabel` | `(option: T) => string` | `.value`, `.label` | for other option shapes |
|
|
418
418
|
| `getOptionLeading` | `(option: T) => ReactNode` | — | an avatar (or a flag) before the label, in the list and in the chip; `renderOption` replaces the whole row |
|
|
419
419
|
| `filter` | `'accent-insensitive' \| 'none' \| (option: T, query: string) => boolean` | `'accent-insensitive'`, `'none'` with `onSearch` | how the list is filtered as the user types: label or value, ignoring case and accents (`e` finds "École") in the provider's language; `'none'` shows `options` as given; a function gets the trimmed query, never an empty one |
|
|
420
|
-
| `freeSolo` | `boolean` | — | accept a typed value that is not an option |
|
|
420
|
+
| `freeSolo` | `boolean` | — | accept a typed value that is not an option; Enter adds it |
|
|
421
421
|
| `onSearch` | `(query: string, { signal }: { signal: AbortSignal }) => Promise<T[]>` | — | server search: called once typing pauses; its results become the list. Each search aborts the previous one, and a response that lands after a newer one is dropped |
|
|
422
422
|
| `onSearchError` | `(error: unknown) => void` | — | `onSearch` rejected (an abort is not a rejection) |
|
|
423
423
|
| `minQueryLength` | `number` | `1` | `onSearch` waits for this many characters; below it the list says "Type at least N characters to search." |
|
|
@@ -433,6 +433,12 @@ disabled with the field; Backspace in the empty input removes the last one.
|
|
|
433
433
|
| `disabled` | `boolean` | — | disables the input and each chip's remove button |
|
|
434
434
|
| `error` | `boolean` | — | error border on the chips box, error helper text, `aria-invalid` on the input |
|
|
435
435
|
|
|
436
|
+
Enter in a form: it acts on the list and does not submit the form while it has
|
|
437
|
+
something to do (WAI-ARIA combobox practice). It picks the highlighted option;
|
|
438
|
+
with `freeSolo` and no option highlighted it adds the typed text; with the list
|
|
439
|
+
open it is only default-prevented. On a closed list with nothing typed it
|
|
440
|
+
submits, as a plain input does.
|
|
441
|
+
|
|
436
442
|
An avatar in the rows and the chips:
|
|
437
443
|
`getOptionLeading={(person) => <Avatar src={person.photo} alt="" />}`; see
|
|
438
444
|
[the field look](../guides/field-look-2-x.md#autocomplete-and-the-chips-box).
|
|
@@ -471,8 +477,9 @@ text, as a `Select` shows it, and keeps the search:
|
|
|
471
477
|
gets the label back on leave: the value can only be replaced by another pick.
|
|
472
478
|
- The option's `getOptionLeading` (or `renderSelectedLeading`) avatar shows
|
|
473
479
|
before the text while it is picked.
|
|
474
|
-
- `freeSolo`
|
|
475
|
-
not added is dropped on leave, as
|
|
480
|
+
- `freeSolo` adds through its "Add" button, or with Enter when no option is
|
|
481
|
+
highlighted; text left in the input and not added is dropped on leave, as
|
|
482
|
+
any other typed text.
|
|
476
483
|
- Keep the selected option in `options` (as with `onSearch` and chips): a value
|
|
477
484
|
with no matching option has no label, so the input stays empty.
|
|
478
485
|
- The input is the combobox: `aria-expanded` follows the list,
|
|
@@ -146,7 +146,9 @@ sort control writes it), the cursor paging in the footer, `bulkActions` and the
|
|
|
146
146
|
selection kept across pages, the empty and error pages. An empty page has no
|
|
147
147
|
list bar over it, and the focus a bulk action's `clear()` hands back goes to the
|
|
148
148
|
list. The sort control names its columns as in a `DataTable`: set `meta.label`
|
|
149
|
-
on a column whose header is a render function (see [Sort](#sort)).
|
|
149
|
+
on a column whose header is a render function (see [Sort](#sort)). Without
|
|
150
|
+
`sorting`, no column is sortable (the server sorts, so pass `sorting` to let people
|
|
151
|
+
sort), and the phone list bar shows only for selection.
|
|
150
152
|
|
|
151
153
|
```tsx
|
|
152
154
|
import { ServerTable } from '@nxgt/material/components';
|
|
@@ -341,7 +343,7 @@ the `Guides/Recipes/Status board` story, and the Claude Code plugin's
|
|
|
341
343
|
|
|
342
344
|
## Troubleshooting
|
|
343
345
|
|
|
344
|
-
**The sort control is missing, or an option reads like the column id.** The control needs a column that can be sorted (an accessor, `enableSorting` not `false`); an option is the column's `meta.label`, else a string `header`. Set `meta.label`.
|
|
346
|
+
**The sort control is missing, or an option reads like the column id.** The control needs a column that can be sorted (an accessor, `enableSorting` not `false`); an option is the column's `meta.label`, else a string `header`. Set `meta.label`. On a `ServerTable` the control is also missing when `sorting` is not passed: no column is sortable without it. Pass `sorting` (the server sorts).
|
|
345
347
|
|
|
346
348
|
**The phone shows the table for a moment.** `{ phone, wide }` renders `wide` on
|
|
347
349
|
the server and for the first client render, so that hydration matches; a phone
|
package/docs/guides/csp.md
CHANGED
|
@@ -28,7 +28,7 @@ Content-Security-Policy:
|
|
|
28
28
|
| `style-src-elem 'self' 'nonce-X'` | Your stylesheet and the `<style>` elements that carry the nonce. No component needs `'unsafe-inline'` here, apart from the [known gaps](#known-gaps): see [the `<style>` elements the components inject](#style-elements-the-components-inject) |
|
|
29
29
|
| `style-src-attr 'unsafe-inline'` | The `style="…"` attributes in the server-rendered HTML and those set through `setAttribute('style', …)`. CSP blocks only these two; `el.style.x = …` and `style.setProperty` are not blocked. An attribute cannot carry a nonce, so this is the only way to allow it. It does not allow `<style>` elements or scripts |
|
|
30
30
|
| `font-src 'self'` | The library ships no web font (host Inter yourself: [Inter / the sans font](#inter--the-sans-font)), and KaTeX's fonts are files, never `data:` URIs: see [KaTeX fonts](#katex-fonts) for the one Vite setting the Tailwind route needs |
|
|
31
|
-
| `img-src 'self' data: blob:` | `data:` for the cropper's output, a captured `Camera` photo,
|
|
31
|
+
| `img-src 'self' data: blob:` | `data:` for the cropper's output, a captured `Camera` photo, and the PDF reader's annotation icons (SVG `data:` images; the icons the components draw are inline `<svg>` elements and need none); `blob:` for previews of a file the user picked and for a `Markdown` mermaid diagram (an SVG image from a `blob:` URL). Drop what you do not use |
|
|
32
32
|
| `worker-src 'self'` | The pdf.js worker of `PdfReader`: the build ships it as a file, `dist/pdf.worker.min.mjs`, referenced relative to the module that loads it, so `'self'` is enough with no setting and no `data:`. `configurePdfReader({ workerSrc })` overrides it ([PDF reader guide](pdf-reader.md#content-security-policy)). Monaco: see below |
|
|
33
33
|
| `connect-src 'self'` | `PdfReader` fetches the PDF from its `url` and its pdf.js assets from `assetsUrl`: add those origins when they are not yours |
|
|
34
34
|
|
|
@@ -38,7 +38,7 @@ Components outside this table need nothing more. Measured per component:
|
|
|
38
38
|
| --- | --- | --- |
|
|
39
39
|
| `PdfReader` | `worker-src 'self'` (the worker is a file of the package), `connect-src` for the PDF's and the assets' origins, and `script-src 'wasm-unsafe-eval'` for the JPEG 2000 and ICC WebAssembly decoders (without it: a JavaScript fallback for JPEG 2000, device colours for ICC profiles) | worker checked in the same Playwright run (Vite production build under `worker-src 'self'`), the rest read from pdf.js's code: [PDF reader guide](pdf-reader.md#content-security-policy) |
|
|
40
40
|
| `CodeEditor`, `CodeDiff` (Monaco) | `@monaco-editor/react` loads Monaco from `https://cdn.jsdelivr.net/npm/monaco-editor@…/min/vs` by default: that host in `style-src-elem` (Monaco's CSS) and `font-src` (its icon font), plus `worker-src blob:` | not yet verified in a browser. Safe default: allow `https://cdn.jsdelivr.net` in `script-src`, `style-src-elem` and `font-src`, and `worker-src blob:`; or stay on `'self'` by [serving Monaco yourself](#monaco-on-self) |
|
|
41
|
-
| `Markdown`
|
|
41
|
+
| `Markdown` (maths, mermaid, code) | nothing beyond the policy above, in a client render | checked in Chromium under that policy with `style-src-attr 'none'`, no `img-src data:` and no `'unsafe-eval'`: [Markdown under a strict CSP](#markdown-katex-mermaid-and-code-under-a-strict-csp) |
|
|
42
42
|
| `RichTextViewer` (`@nxgt/material/editor/viewer`) | none, but it emits `style` attributes: a text colour, a highlight, a text alignment or a table cell's background in the saved document becomes an inline `style` (only allow-listed properties with plain values survive), so a page that shows such a document needs `style-src-attr 'unsafe-inline'`. A document with none sends no style | measured by reading the renderers; a strict `style-src-attr 'none'` page shows the document without those colours |
|
|
43
43
|
| `Camera` | none: `getUserMedia` is not governed by CSP; the captured photo is a `data:` image | `media-src` for the live preview is not yet verified. Safe default: `media-src 'self' blob:` (a `srcObject` stream usually needs nothing) |
|
|
44
44
|
| `MediaPlayer` with `hlsStreaming()` | `connect-src` and `media-src blob:` for the stream; `worker-src blob:` only with `config: { enableWorker: true }` (the adapter defaults to no worker) | see the [streaming guide](media-streaming.md#content-security-policy) |
|
|
@@ -201,8 +201,8 @@ instead.
|
|
|
201
201
|
`style-src-elem 'self' 'nonce-X'` is enough for every component below: your
|
|
202
202
|
stylesheet carries the CSS the dependencies used to inject, and under a
|
|
203
203
|
`NonceProvider` the few `<style>` elements left carry the nonce. No route
|
|
204
|
-
needs `'unsafe-inline'` in `style-src-elem` for them;
|
|
205
|
-
font fallback are the [known gaps](#known-gaps).
|
|
204
|
+
needs `'unsafe-inline'` in `style-src-elem` for them; the mindmap diagrams of
|
|
205
|
+
mermaid and pdf.js's font fallback are the [known gaps](#known-gaps).
|
|
206
206
|
|
|
207
207
|
| Component | What it needs | How |
|
|
208
208
|
| --- | --- | --- |
|
|
@@ -261,12 +261,50 @@ when the page carries it nowhere. `setCspNonce` does nothing on a server (no
|
|
|
261
261
|
`document`), so a request's nonce never lands in a module global there: see
|
|
262
262
|
[SSR](#ssr).
|
|
263
263
|
|
|
264
|
+
### Markdown (KaTeX, mermaid and code) under a strict CSP
|
|
265
|
+
|
|
266
|
+
The full `Markdown` (and `ChatMarkdown`) needs no relaxed directive: no
|
|
267
|
+
`'unsafe-inline'`, no `'unsafe-eval'`, no `style-src-attr`, no `img-src data:`.
|
|
268
|
+
Only `img-src blob:` (already in the policy above) for the diagrams. The
|
|
269
|
+
`Data display/Markdown > StrictCsp` story plays maths, two flowcharts (one with
|
|
270
|
+
an `<img>` in a label, removed before the render, since mermaid would load it in the page) and a code block in a document served
|
|
271
|
+
`script-src 'self' 'nonce-X'` (no `'unsafe-eval'`),
|
|
272
|
+
`style-src-elem 'self' 'nonce-X'`, `style-src-attr 'none'`,
|
|
273
|
+
`img-src 'self' blob:` and `font-src 'self'`, and expects not one violation.
|
|
274
|
+
What the app does: wrap the app in [`NonceProvider`](#nonceprovider) (or let
|
|
275
|
+
the page carry the nonce in its meta tag).
|
|
276
|
+
|
|
277
|
+
- **Code blocks**: the scheme's colours are classes of the stylesheet
|
|
278
|
+
(`data-scheme`), and `maxHeight` is a custom property written through the
|
|
279
|
+
CSSOM in a layout effect (a server-rendered block is full height until
|
|
280
|
+
hydration).
|
|
281
|
+
- **KaTeX**: on a client render React writes its styles through the CSSOM.
|
|
282
|
+
Its fonts are files ([KaTeX fonts](#katex-fonts)).
|
|
283
|
+
- **Mermaid**: the diagram stays an image, now from a `blob:` URL (revoked when
|
|
284
|
+
the diagram goes), so the SVG's own `<style>` and `style` attributes live in
|
|
285
|
+
the image and the page needs neither `data:` nor `style-src-attr` for them.
|
|
286
|
+
While a diagram renders, mermaid builds it in a temporary element of the
|
|
287
|
+
page and sanitises it with DOMPurify, which a strict policy reports. For
|
|
288
|
+
that render only, and only for the elements of that diagram, a short-lived
|
|
289
|
+
scope gives mermaid's `<style>` the nonce, writes its `style` attributes
|
|
290
|
+
through the CSSOM and parks the `style` attributes of the string DOMPurify
|
|
291
|
+
parses (patches of `Document.prototype.createElement`,
|
|
292
|
+
`Element.prototype.setAttribute` and `DOMParser.prototype.parseFromString`;
|
|
293
|
+
each is a pass-through once the last render ends and is restored only if
|
|
294
|
+
no other script patched it since). Unrelated calls, such as an editor
|
|
295
|
+
parsing HTML, are left alone.
|
|
296
|
+
- **Left**: a page whose HTML the server renders with math carries KaTeX's
|
|
297
|
+
`style="…"` attributes in the markup, which `style-src-attr 'none'` blocks
|
|
298
|
+
(the formula loses its spacing until `style-src-attr 'unsafe-inline'`, or a
|
|
299
|
+
client-only render of the `Markdown`). Mermaid's mindmap and architecture
|
|
300
|
+
diagrams add a `<style>` to `<head>` ([known gaps](#known-gaps)).
|
|
301
|
+
|
|
264
302
|
### Known gaps
|
|
265
303
|
|
|
266
304
|
Not fixable by configuration yet, and still reported by a strict policy:
|
|
267
305
|
|
|
268
|
-
- the `<style>` element
|
|
269
|
-
|
|
306
|
+
- the `<style>` element cytoscape adds to `<head>` for mermaid's mindmap and
|
|
307
|
+
architecture diagrams (flowcharts, sequences and the like add none);
|
|
270
308
|
- the font fallback of pdf.js, when a PDF font is not embedded;
|
|
271
309
|
- this repository's Storybook, or an app that imports react-use's barrel
|
|
272
310
|
itself on a dev server: react-use served as one module, whose `useCss`
|
package/docs/guides/forms.md
CHANGED
|
@@ -115,7 +115,7 @@ export function PlanForm() {
|
|
|
115
115
|
| The field | Touched when |
|
|
116
116
|
| --- | --- |
|
|
117
117
|
| one input (`TextFormField`, `EmailFormField`, `TextareaFormField`, `SwitchFormField`, `CheckboxFormField`, …) | its input loses focus, as a native `blur` |
|
|
118
|
-
| several controls (`OpeningHoursFormField`, inline by default: focus leaves the week editor and its phone sheet; `ContactsFormField`, inline by default: focus leaves the rows and the phone sheet; `AttributesFormField`, inline by default: focus leaves the rows, their open fields and the phone sheet; `PasswordFormField` and its eye button, `NumberFormField` and its steppers, `PhoneFormField`, `OtpFormField`, `SliderFormField` with two thumbs, `DurationFormField`, `RatingFormField`, `RadioGroupFormField`, `CheckboxGroupFormField`, `ChipGroupFormField`, `ChoiceCardGroupFormField`, `SortableListFormField`, `TransferListFormField`, `InlineEditFormField`, `LocalizedTextFormField`, `AttributeValuesFormFieldSet`, …) | focus leaves all of them; moving between them is not leaving |
|
|
118
|
+
| several controls (`OpeningHoursFormField`, inline by default: focus leaves the week editor and its phone sheet; `ContactsFormField`, inline by default: focus leaves the rows and the phone sheet; `AttributesFormField`, inline by default: focus leaves the rows, their open fields and the phone sheet; `PasswordFormField` and its eye button, `NumberFormField` and its steppers, `QuantityFormField`, `PhoneFormField`, `OtpFormField`, `SliderFormField` with two thumbs, `DurationFormField`, `RatingFormField`, `RadioGroupFormField`, `CheckboxGroupFormField`, `ChipGroupFormField`, `ChoiceCardGroupFormField`, `SortableListFormField`, `TransferListFormField`, `InlineEditFormField`, `LocalizedTextFormField`, `AttributeValuesFormFieldSet`, …) | focus leaves all of them; moving between them is not leaving |
|
|
119
119
|
| a picker (`SelectFormField`, `ComboboxFormField`, `AutocompleteFormField`, `TreeSelectFormField`, `CountryFormField`, `TimezoneFormField`, `ColorFormField`, `DateFormField`, `DateRangeFormField`, `TimeFormField`, …) | focus leaves the trigger **and** the popover it opened. Opening the popover, or picking in it, is not leaving; closing it hands focus back to the trigger, and the next Tab or click elsewhere is. A typed `DateFormField`, `DateRangeFormField` or `TimeFormField` is one trigger: moving between its segments (both ends of a range) and its buttons is not leaving |
|
|
120
120
|
| a dialog editor (`PostalAddressFormField`, `ContactsFormField` with `editing="dialog"`, `OpeningHoursFormField` with `editing="dialog"`, `AttributesFormField` with `editing="dialog"`, and `LocationFormField` from `@nxgt/material/map`) | focus leaves its buttons and the dialog they open. Closing the dialog hands focus back to the button that opened it |
|
|
121
121
|
| an upload (`UploadFormField`, `NetworkUploadFormField`, `S3UploadFormField`, `ImageFormField`, `ImageS3FormField`, `FileListFormField`, `FileListS3FormField`) | focus leaves the drop zone and its buttons. Choosing a file in the browser's own dialog is not leaving |
|
|
@@ -485,8 +485,9 @@ or fails validation.
|
|
|
485
485
|
| `PasswordField` · `PasswordFormField` | `string` | `''` |
|
|
486
486
|
| `NumberField` · `NumberFormField` | `number \| null`. `null` when cleared; clamped to `min` / `max` on blur | `null` |
|
|
487
487
|
| `PercentField` · `PercentFormField` | `number \| null`, `min` 0, `max` 100, `step` 1 by default | `null` |
|
|
488
|
+
| `QuantityField` · `QuantityFormField` | `{ value: number \| null, unit: string }`. An error on the field, `.value` or `.unit` shows as one message under the pair | `{ value: null, unit: '' }` |
|
|
488
489
|
| `CurrencyField` · `CurrencyFormField` | `number \| null`, the amount only, not rounded. `currency` (default `'EUR'`) and `fractionDigits` (the currency's own decimals by default) are display | `null` |
|
|
489
|
-
| `PhoneField` · `PhoneFormField` | E.164 `string` (`'+33612345678'`), or `null`
|
|
490
|
+
| `PhoneField` · `PhoneFormField` | E.164 `string` (`'+33612345678'`), or `null` when empty. `PhoneFormField` reports a non-empty number that is not yet possible as the field error `material.phone-field.incomplete`; `PhoneField` signals it with `onValidityChange`. `defaultCountry` is `'FR'` | `null` |
|
|
490
491
|
| `UrlField` · `UrlFormField` | `string` (as typed) | `''` |
|
|
491
492
|
| `SearchField` · `SearchFormField` | `string`, after `debounceMs` (default `0`) | `''` |
|
|
492
493
|
| `ColorField` · `ColorFormField` | `string` (hex, `'#1e293b'`) | `''` |
|
|
@@ -535,7 +536,7 @@ or fails validation.
|
|
|
535
536
|
| Field · wrapper | Form value | Empty default |
|
|
536
537
|
| --- | --- | --- |
|
|
537
538
|
| `NetworkUploadField` · `NetworkUploadFormField` | `mode="single"`: the `string` your `onUpload` resolved (a URL or a key), or `null` after removal. `mode="multiple"`: `string[]` | `undefined` / `[]` |
|
|
538
|
-
| `S3UploadField` · `S3UploadFormField` | as above; the value is `publicUrl` from `getPresignedUrl` | `undefined` / `[]` |
|
|
539
|
+
| `S3UploadField` · `S3UploadFormField` | as above; the value is `publicUrl` (PUT) or `value` (POST) from `getPresignedUrl` | `undefined` / `[]` |
|
|
539
540
|
| `ImageField`, `ImageS3Field` · `ImageFormField`, `ImageS3FormField` | `string \| null` (the uploaded URL) | `undefined` |
|
|
540
541
|
| `FileListField`, `FileListS3Field` · `FileListFormField`, `FileListS3FormField` | `string[]` | `[]` |
|
|
541
542
|
| `FilesFormField` | `mode="single"`: `string \| null`. `mode="multiple"`: `string[]` (typed or picked URLs) | `null` / `[]` |
|
|
@@ -785,8 +786,9 @@ form.setFocus('email'); // or without an error
|
|
|
785
786
|
|
|
786
787
|
## A form that is quick to fill
|
|
787
788
|
|
|
788
|
-
`useFastForm` and `
|
|
789
|
-
|
|
789
|
+
`useFastForm`, `useFocusOnRefusal` and `useServerFieldErrors`
|
|
790
|
+
(`@nxgt/material/components`) are the three habits of a record form, in one
|
|
791
|
+
place; the first two are below, the third under [Server errors](#server-errors). `useFastForm(props, first, active?)`
|
|
790
792
|
is `useForm` with `mode: 'onTouched'` (an error shows once the user leaves the
|
|
791
793
|
field, then follows each change; a `mode` in `props` wins) that also focuses
|
|
792
794
|
the `first` field when the form opens, so typing starts at once. It waits a
|
|
@@ -820,8 +822,24 @@ Storybook: `Guides/Forms/useFastForm`. If the focus does not move, see
|
|
|
820
822
|
|
|
821
823
|
## Server errors
|
|
822
824
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
+
`useServerFieldErrors(form, fieldErrors)` (`@nxgt/material/components`) puts an
|
|
826
|
+
action's `{ path: message }` field errors on the form, each under its field
|
|
827
|
+
with `type: 'server'`, and focuses the first. It runs when the `fieldErrors`
|
|
828
|
+
object changes, so pass the answer's own object (one per answer) and
|
|
829
|
+
`undefined` while a request runs or when the answer is not a refusal; entries
|
|
830
|
+
with an empty message are skipped, and the keys must be names of the form's
|
|
831
|
+
fields.
|
|
832
|
+
|
|
833
|
+
```tsx
|
|
834
|
+
import { useFastForm, useServerFieldErrors } from '@nxgt/material/components';
|
|
835
|
+
|
|
836
|
+
const form = useFastForm<Values>({ defaultValues }, 'email');
|
|
837
|
+
useServerFieldErrors(form, actionData?.fieldErrors); // { email: 'Already used' }
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
It pairs with `useFastForm` and `useFocusOnRefusal` above. To write the same
|
|
841
|
+
mapping by hand, or to put anything that belongs to no field under `root`,
|
|
842
|
+
shown in an `Alert`, use `setError`:
|
|
825
843
|
|
|
826
844
|
```tsx
|
|
827
845
|
import { useForm } from 'react-hook-form';
|
|
@@ -869,6 +887,36 @@ export function SignInForm() {
|
|
|
869
887
|
- `Alert` `variant` is one of `primary`, `secondary`, `error`, `success`,
|
|
870
888
|
`info`, `warning`, `foreground`.
|
|
871
889
|
|
|
890
|
+
### A save refused because another write came first
|
|
891
|
+
|
|
892
|
+
When the API refuses a versioned save (`stale-version`), keep the form as it is
|
|
893
|
+
and show a `StaleNotice` above it. It takes the focus and scrolls into view on
|
|
894
|
+
mount, and its one action reads the record again. There is no overwrite.
|
|
895
|
+
|
|
896
|
+
```tsx
|
|
897
|
+
import type { FormEventHandler } from 'react';
|
|
898
|
+
import type { UseFormReturn } from 'react-hook-form';
|
|
899
|
+
import { Button, StaleNotice, TextFormField } from '@nxgt/material/components';
|
|
900
|
+
|
|
901
|
+
type Props = {
|
|
902
|
+
form: UseFormReturn<{ title: string }>;
|
|
903
|
+
stale: boolean; // the save was refused with `stale-version`
|
|
904
|
+
reload: () => void; // read the record again
|
|
905
|
+
reloading: boolean;
|
|
906
|
+
onSubmit: FormEventHandler<HTMLFormElement>;
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
function EditNote({ form, stale, reload, reloading, onSubmit }: Props) {
|
|
910
|
+
return (
|
|
911
|
+
<form onSubmit={onSubmit} className="grid gap-4">
|
|
912
|
+
{stale && <StaleNotice onReload={reload} busy={reloading} />}
|
|
913
|
+
<TextFormField control={form.control} name="title" label="Title" />
|
|
914
|
+
<Button type="submit">Save</Button>
|
|
915
|
+
</form>
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
```
|
|
919
|
+
|
|
872
920
|
## Dialogs and steps
|
|
873
921
|
|
|
874
922
|
### A form in a dialog
|
package/docs/guides/i18n.md
CHANGED
|
@@ -236,7 +236,7 @@ the camera's button, toggle and image (`material.camera.capture`, `captured`,
|
|
|
236
236
|
barcode accessible names, the calendar's navigation tooltips
|
|
237
237
|
(`material.calendar.*`), the data table's selection checkboxes, the filter's
|
|
238
238
|
presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
|
|
239
|
-
`ScrollToTop`, the network upload field's `Retry
|
|
239
|
+
`ScrollToTop`, the network upload field's `Retry` and `Remove {name}`, the map's
|
|
240
240
|
`You are here` and zoom buttons, and the icon-only close button of
|
|
241
241
|
`DialogClose`, `DrawerClose`, `SheetClose` and `SheetContent`'s
|
|
242
242
|
`withCloseButton`. A range `Slider`'s readout inputs read
|
|
@@ -320,9 +320,9 @@ those words as an optional third argument); the `aria-label` of `Breadcrumb`
|
|
|
320
320
|
and `Pagination` and the breadcrumb ellipsis's "More"
|
|
321
321
|
(`material.breadcrumb.*`, `material.pagination.label`), and `DocHeader`'s
|
|
322
322
|
breadcrumb (`material.doc.breadcrumb`); `MessageCard`'s "Unread"
|
|
323
|
-
(`material.cards.unread`); the network upload field's "Replace {name}", "Upload
|
|
323
|
+
(`material.cards.unread`); the network upload field's "Replace {name}", "Remove {name}", "Upload
|
|
324
324
|
failed", its type, size and count errors (`material.upload.replace-named`,
|
|
325
|
-
`failed`, `invalid-type`, `too-large`, `max-files`; an error keeps the language
|
|
325
|
+
`remove-named`, `failed`, `invalid-type`, `too-large`, `max-files`; an error keeps the language
|
|
326
326
|
it was raised in) and the drop zone's "Max {size}" and "Up to {count} files"
|
|
327
327
|
(`material.upload.dropzone.max-size` / `max-files`); the editor's
|
|
328
328
|
placeholder, which follows a language change without rebuilding the editor,
|
|
@@ -485,8 +485,9 @@ and description and against the English ones, ignoring accents: in French,
|
|
|
485
485
|
|
|
486
486
|
Some strings are still English in every language, among them the filter's
|
|
487
487
|
validation messages, the camera's error messages, the editor's callout type
|
|
488
|
-
names and default block content.
|
|
489
|
-
the provider's language
|
|
488
|
+
names and default block content. The filter chips' dates, and file sizes outside the upload field (`2 KB`),
|
|
489
|
+
are not formatted for the provider's language; the upload field writes its
|
|
490
|
+
size in it (`10,4 ko`).
|
|
490
491
|
|
|
491
492
|
The emoji picker's category tabs are icon-only; their name, read by screen
|
|
492
493
|
readers and shown as a tooltip, comes from `material.emoji.groups.<group>`.
|
package/docs/guides/printing.md
CHANGED
|
@@ -1013,7 +1013,7 @@ it:
|
|
|
1013
1013
|
| `heading` | `heading(text, level?)` | A title, level `1`, `2` or `3` |
|
|
1014
1014
|
| `text` | `text(content, { variant?, style? })` | A paragraph: a string, or runs `{ text, bold?, italic?, color?, href? }`; `variant` is `'body'`, `'muted'`, `'caption'` or `'label'` |
|
|
1015
1015
|
| `stack` / `row` | `stack(children, { gap?, style?, keepTogether? })`, `row(children, { gap?, align?, justify?, style? })` | Children one under the other, or side by side |
|
|
1016
|
-
| `section` | `section(title, children, { keepTogether? })` | A titled block, as `PrintSection`; `keepTogether` as on a `stack
|
|
1016
|
+
| `section` | `section(title, children, { keepTogether? })` | A titled block, as `PrintSection`; `keepTogether` as on a `stack`; a block taller than a page breaks normally, see [Keeping a block on one page](#keeping-a-block-on-one-page) |
|
|
1017
1017
|
| `table` | `table({ columns, rows, footer?, labelTotals?, striped?, repeatHeader?, keepRowsWhole?, emptyText?, hideWhenEmpty? })`, or `dataTable(columns, rows, options?)` from [column definitions](#tables-from-column-definitions-datatable-and-keyvalueof) | A table of `PrintValue`s, as `PrintDataTable`; a row or a cell may carry a `style`, see [Row and cell styles](#row-and-cell-styles-in-a-table); with `labelTotals`, an empty first cell of a `footer` row reads "Total" in the document's language |
|
|
1018
1018
|
| `keyValue` | `keyValue(items, { columns?, keepEmpty? })`, or `keyValueOf(fields, data, options?)` from column definitions | Label and value pairs, as `PrintKeyValue` |
|
|
1019
1019
|
| `totals` | `totals(rows)` | Label and value rows; `emphasis` makes one bold |
|
|
@@ -1140,12 +1140,22 @@ section(group.title, rows, { keepTogether: true });
|
|
|
1140
1140
|
stack([heading('Result'), table(result)], { keepTogether: true });
|
|
1141
1141
|
```
|
|
1142
1142
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1143
|
+
A block taller than a page breaks normally. react-pdf cannot split a block
|
|
1144
|
+
that opted out of wrapping (it overflows and is cut off at the edge), and
|
|
1145
|
+
cannot measure ahead, so for a document as data `renderPdfBuffer`,
|
|
1146
|
+
`renderPdfBlob`, `renderPdfStream`, `downloadPdf`, `openPdf` and `pdfResponse`
|
|
1147
|
+
first render each `keepTogether` block alone, on a page like the one it sits
|
|
1148
|
+
on, and drop the option of those that need more than one page. A block that
|
|
1149
|
+
fits keeps it, so its output does not change; a document with no
|
|
1150
|
+
`keepTogether` pays for no extra pass. The measure is made at the page's
|
|
1151
|
+
width: a block inside a `row` is measured with that row, but a parent's padding or narrower style is not applied.
|
|
1152
|
+
|
|
1153
|
+
This holds for a document as data only. `<PrintSection keepTogether>` in a
|
|
1154
|
+
`PrintDocument` element, and a document you render yourself with
|
|
1155
|
+
`renderPdfElement`, are not measured: use `keepTogether` there on a block
|
|
1156
|
+
shorter than a page, or call `resolveKeepTogether(doc)` from
|
|
1157
|
+
`@nxgt/material-core/pdf/render` first and render its result. For a long
|
|
1158
|
+
table, `keepRowsWhole` and `repeatHeader` keep the rows apart from each other.
|
|
1149
1159
|
|
|
1150
1160
|
### Row and cell styles in a table
|
|
1151
1161
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/roadmap.md
CHANGED
|
@@ -6,6 +6,8 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
6
6
|
|
|
7
7
|
## Now
|
|
8
8
|
|
|
9
|
+
- **Calculator** — `@nxgt/material/calculator` adds `Calculator`, in six variants (Standard, Scientific, point of sale, Financial, Unit conversion, Programmer) chosen by `variant` or by its own switcher, and `CalculatorField` / `CalculatorFormField`, a number field whose button opens it in a popover, or a bottom sheet on a phone. Every variant shows the whole expression above the result, honours operator precedence and computes in exact decimals (0.1 + 0.2 = 0.3) without `eval`, so a strict CSP holds. A history tape (persisted, with an opt-out), memory keys, the physical keyboard, copy and paste, locale formatting and screen-reader announcements come with each; haptic and sound feedback are opt-in. Point of sale charges through `onCharge(total)`; currency rates come from the app. React only for now; the Vue port is tracked in [#2100](https://github.com/softistx/nxgt-material/issues/2100). Tracked in [#2101](https://github.com/softistx/nxgt-material/issues/2101).
|
|
10
|
+
|
|
9
11
|
- **Barcodes and label sheets** — `@nxgt/material/barcode` draws Code 128
|
|
10
12
|
and Data Matrix as accessible SVG (themed, dark-aware, black on white in
|
|
11
13
|
print) and lays labels out on a sheet to print: label size, columns, rows,
|
|
@@ -15,6 +17,10 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
15
17
|
- **Toast texts follow app translations** — the toast presets read the
|
|
16
18
|
app's `I18nProvider` `resources`, so an override under `material.*`
|
|
17
19
|
changes their wording ([#1855](https://github.com/softistx/nxgt-material/issues/1855)).
|
|
20
|
+
- **A record page layout** — `RecordPage` and `RecordDetails` lay out one
|
|
21
|
+
record: a header card, the content, and its facts as a side panel from `xl`
|
|
22
|
+
that stacks after or before the content, with a created / updated line
|
|
23
|
+
([#1689](https://github.com/softistx/nxgt-material/issues/1689)).
|
|
18
24
|
- **A decision bar for phone pages** — `StickyActionBar` holds a long page's
|
|
19
25
|
main decision at the bottom of a phone, above the keyboard and clear of the
|
|
20
26
|
safe areas, and inline from `md`
|
|
@@ -157,6 +163,8 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
157
163
|
|
|
158
164
|
## Next
|
|
159
165
|
|
|
166
|
+
- **Layouts: adaptive navigation, headers and page layouts** — `ActivityLayout` takes `navigation` (a bottom bar, a rail with a FAB, standard and modal drawers, a collapsible sidebar, a top navigation with dropdowns, two-level navigation or a floating toolbar, chosen for phone, tablet and desktop) and `header` (small, center-aligned, medium and large collapsing on scroll, a search bar with full-screen search on a phone, sticky tabs, hide on scroll); each piece is also exported. Page layouts grow with it: an inspector pane and URL state for `ListDetailsLayout`, phone modes for `PaneLayout`, spans and masonry for `ResponsiveGrid`, tabs for `Kanban` on a phone, and new settings, dashboard (rearranging tiles), editor and landing layouts, each with a screen recipe. Everything is opt-in; the shell's default is unchanged. Tracked in [#2103](https://github.com/softistx/nxgt-material/issues/2103).
|
|
167
|
+
|
|
160
168
|
- **FilterBar "Add a filter" stays on its row** — the button no longer wraps
|
|
161
169
|
alone onto a second row on a desktop screen ([#1752](https://github.com/softistx/nxgt-material/issues/1752)).
|
|
162
170
|
|
|
@@ -241,11 +249,13 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
241
249
|
slot; the bar's `views` slot is planned with it. They are meant to replace
|
|
242
250
|
the legacy `FilterPresets`. Tracked in [#1876](https://github.com/softistx/nxgt-material/issues/1876).
|
|
243
251
|
|
|
244
|
-
- **RecordActionsMenu** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
|
|
245
252
|
- **Cursor trail cap** — `ServerTable` and `PageFooter` keep cursor URLs short: the trail is encoded once and capped by `maxTrail` (20 by default) with an exact page count, and `totalCount` becomes optional for APIs that do not count.
|
|
246
253
|
|
|
247
254
|
## Later
|
|
248
255
|
|
|
256
|
+
- **One `onUpload` options object (next major)** — the upload fields'
|
|
257
|
+
`onUpload(file, onProgress?, signal?)` becomes `onUpload(file, { onProgress,
|
|
258
|
+
signal })`, like the editor's upload context.
|
|
249
259
|
- **A strict theme `motif` (4.0)** — a theme background's `motif` with an
|
|
250
260
|
unknown key, a `layout` or `density` outside its list or a bad `icons` is
|
|
251
261
|
dropped in 3.22 (`@nxgt/material/themes`'s `defineTheme` warns for each one in
|
|
@@ -361,6 +371,9 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
361
371
|
|
|
362
372
|
## Shipped
|
|
363
373
|
|
|
374
|
+
- **RecordActionsMenu (next minor)** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
|
|
375
|
+
- **Assistant approval card (next minor)** — `ChatActionProposal` shows an action an assistant proposes, with its status, deadline and Confirm / Reject, and `ChatToolActivity` is a compact row for a tool step in a chat thread ([#2134](https://github.com/softistx/nxgt-material/issues/2134)).
|
|
376
|
+
|
|
364
377
|
- **Link navigation (3.66.0)** — `TabsNav` and `SegmentedNav` render route tabs and segmented navigation as a `nav` of links with `aria-current="page"`, looking like Tabs and the segmented ToggleGroup, plus `useTabParam`, `skipTabChange` and `tabIds` to keep the current tab in the URL. Tracked in [#2029](https://github.com/softistx/nxgt-material/issues/2029).
|
|
365
378
|
|
|
366
379
|
- **Smaller, easier-to-read components (3.58.0)** — the library's own source
|