@hjmds/design-contracts 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-session.d.ts +16 -0
- package/dist/action-session.d.ts.map +1 -1
- package/dist/action-session.js +18 -0
- package/dist/action-session.js.map +1 -1
- package/dist/behaviors.d.ts +7 -6
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +4 -3
- package/dist/behaviors.js.map +1 -1
- package/dist/bottom-navigation.d.ts +4 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +11 -0
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/carousel.d.ts +3 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +42 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-recipes.d.ts +42 -0
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +14 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/field-group.d.ts +14 -0
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +9 -0
- package/dist/field-group.js.map +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +10 -3
- package/dist/form.js.map +1 -1
- package/dist/screen-patterns.d.ts +4 -0
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +12 -3
- package/dist/screen-patterns.js.map +1 -1
- package/dist/toast.d.ts +10 -1
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +43 -2
- package/dist/toast.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/action-session.md +2 -0
- package/docs/asset.md +7 -0
- package/docs/bottom-navigation.md +11 -0
- package/docs/brand-boundary.md +3 -3
- package/docs/collection-rail.md +4 -0
- package/docs/compound-controls.md +8 -3
- package/docs/consumer-policy.md +39 -120
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +3 -3
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +1 -1
- package/docs/gravity-letters.md +2 -0
- package/docs/grid-reveal.md +2 -0
- package/docs/layout.md +2 -2
- package/docs/load-more.md +5 -0
- package/docs/migration-native-legacy-removal.md +30 -17
- package/docs/optional-adapters.md +4 -0
- package/docs/screen-patterns.md +23 -1
- package/docs/toast.md +41 -2
- package/docs/usage/README.md +248 -245
- package/docs/usage/SELECTION.md +43 -0
- package/docs/usage/STANDARD.md +22 -10
- package/docs/usage/components/accordion.md +5 -3
- package/docs/usage/components/agreement.md +6 -9
- package/docs/usage/components/alert-dialog.md +12 -6
- package/docs/usage/components/asset.md +10 -4
- package/docs/usage/components/auth-provider-button.md +2 -2
- package/docs/usage/components/avatar.md +8 -6
- package/docs/usage/components/badge.md +4 -4
- package/docs/usage/components/bottom-cta.md +4 -4
- package/docs/usage/components/bottom-info.md +2 -2
- package/docs/usage/components/bottom-navigation.md +16 -5
- package/docs/usage/components/button.md +2 -2
- package/docs/usage/components/card.md +1 -1
- package/docs/usage/components/carousel.md +12 -4
- package/docs/usage/components/chat-message.md +6 -2
- package/docs/usage/components/chat-screen.md +5 -5
- package/docs/usage/components/checkbox-group.md +1 -1
- package/docs/usage/components/checkbox.md +2 -2
- package/docs/usage/components/chip.md +2 -2
- package/docs/usage/components/code-block.md +3 -4
- package/docs/usage/components/collapsible.md +6 -4
- package/docs/usage/components/combobox.md +2 -2
- package/docs/usage/components/command-palette.md +1 -1
- package/docs/usage/components/comment-thread-screen.md +7 -4
- package/docs/usage/components/container.md +1 -1
- package/docs/usage/components/content-transition.md +2 -2
- package/docs/usage/components/context-menu.md +2 -2
- package/docs/usage/components/counter-badge.md +3 -3
- package/docs/usage/components/data-table.md +1 -1
- package/docs/usage/components/date-picker.md +4 -4
- package/docs/usage/components/description-list.md +3 -3
- package/docs/usage/components/design-system-provider.md +2 -2
- package/docs/usage/components/dialog.md +14 -3
- package/docs/usage/components/divider.md +2 -2
- package/docs/usage/components/editor-screen.md +5 -3
- package/docs/usage/components/effect-surface.md +1 -1
- package/docs/usage/components/empty-state.md +6 -6
- package/docs/usage/components/field.md +5 -3
- package/docs/usage/components/file-picker.md +6 -3
- package/docs/usage/components/form.md +1 -1
- package/docs/usage/components/heading.md +3 -3
- package/docs/usage/components/icon-button.md +3 -3
- package/docs/usage/components/icon.md +1 -1
- package/docs/usage/components/image.md +5 -4
- package/docs/usage/components/keyboard-dock.md +1 -1
- package/docs/usage/components/layout.md +1 -1
- package/docs/usage/components/link.md +2 -2
- package/docs/usage/components/list-detail-screen.md +6 -6
- package/docs/usage/components/list-row.md +13 -4
- package/docs/usage/components/list.md +5 -4
- package/docs/usage/components/masonry.md +2 -1
- package/docs/usage/components/media-selection-screen.md +11 -5
- package/docs/usage/components/mentions.md +2 -2
- package/docs/usage/components/menu.md +3 -2
- package/docs/usage/components/menubar.md +2 -2
- package/docs/usage/components/message-composer.md +3 -3
- package/docs/usage/components/moderation-screen.md +3 -2
- package/docs/usage/components/notice.md +2 -2
- package/docs/usage/components/notification-inbox-screen.md +1 -1
- package/docs/usage/components/notification-item.md +2 -2
- package/docs/usage/components/number-field.md +3 -3
- package/docs/usage/components/otp-field.md +1 -1
- package/docs/usage/components/overview-screen.md +1 -1
- package/docs/usage/components/permission-screen.md +1 -1
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/popover.md +2 -2
- package/docs/usage/components/profile-screen.md +1 -1
- package/docs/usage/components/progress.md +2 -2
- package/docs/usage/components/qr-code.md +2 -2
- package/docs/usage/components/radio-group.md +1 -2
- package/docs/usage/components/radio.md +1 -2
- package/docs/usage/components/result.md +1 -1
- package/docs/usage/components/saved-items-screen.md +4 -2
- package/docs/usage/components/screen-layout.md +4 -3
- package/docs/usage/components/search-field.md +1 -1
- package/docs/usage/components/search-screen.md +4 -4
- package/docs/usage/components/section.md +3 -3
- package/docs/usage/components/segmented-control.md +4 -4
- package/docs/usage/components/select.md +2 -3
- package/docs/usage/components/settings-screen.md +11 -49
- package/docs/usage/components/sheet.md +10 -5
- package/docs/usage/components/sidebar.md +1 -1
- package/docs/usage/components/skeleton.md +1 -1
- package/docs/usage/components/skip-nav.md +1 -1
- package/docs/usage/components/slider.md +2 -2
- package/docs/usage/components/spinner.md +2 -2
- package/docs/usage/components/splitter.md +1 -1
- package/docs/usage/components/statistic.md +5 -4
- package/docs/usage/components/steps.md +2 -2
- package/docs/usage/components/surface.md +2 -3
- package/docs/usage/components/switch.md +1 -1
- package/docs/usage/components/tabs.md +4 -4
- package/docs/usage/components/tag.md +2 -2
- package/docs/usage/components/tags-input.md +4 -4
- package/docs/usage/components/text-area.md +2 -3
- package/docs/usage/components/text.md +85 -4
- package/docs/usage/components/timeline.md +2 -2
- package/docs/usage/components/toast.md +17 -7
- package/docs/usage/components/toggle-group.md +3 -3
- package/docs/usage/components/tooltip.md +1 -1
- package/docs/usage/components/top-bar.md +5 -4
- package/docs/usage/components/top.md +3 -3
- package/docs/usage/components/transfer-list.md +5 -4
- package/docs/usage/components/upload-item.md +3 -3
- package/docs/usage/components/virtual-list.md +1 -0
- package/docs/usage/compositions/action-feedback.md +42 -6
- package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
- package/docs/usage/compositions/action-recovery-save.md +19 -23
- package/docs/usage/compositions/action-recovery-undo.md +6 -1
- package/docs/usage/compositions/activity-status.md +11 -5
- package/docs/usage/compositions/collection-detail.md +2 -0
- package/docs/usage/compositions/command-records.md +15 -5
- package/docs/usage/compositions/common-message.md +4 -2
- package/docs/usage/compositions/common-notification.md +5 -3
- package/docs/usage/compositions/compound-controls.md +13 -5
- package/docs/usage/compositions/content-transition-comparison.md +2 -2
- package/docs/usage/compositions/date-entry.md +10 -5
- package/docs/usage/compositions/date-time-selection.md +3 -3
- package/docs/usage/compositions/design-profile-comparison.md +7 -5
- package/docs/usage/compositions/disclosure.md +1 -1
- package/docs/usage/compositions/document-resource.md +10 -3
- package/docs/usage/compositions/expo-interactions.md +3 -2
- package/docs/usage/compositions/family-drawer.md +9 -4
- package/docs/usage/compositions/field-group.md +7 -1
- package/docs/usage/compositions/floating-action-button.md +18 -18
- package/docs/usage/compositions/illustrated-outcome.md +3 -2
- package/docs/usage/compositions/input-sheet.md +6 -2
- package/docs/usage/compositions/interaction-adapters.md +1 -1
- package/docs/usage/compositions/interaction-flow-apply.md +6 -3
- package/docs/usage/compositions/interaction-flow-draft.md +5 -2
- package/docs/usage/compositions/interaction-flow-search.md +5 -2
- package/docs/usage/compositions/live-list.md +7 -5
- package/docs/usage/compositions/navigation-bar-collection.md +8 -7
- package/docs/usage/compositions/optional-adapters.md +1 -1
- package/docs/usage/compositions/origin-dialog.md +5 -5
- package/docs/usage/compositions/paper-surface.md +1 -1
- package/docs/usage/compositions/photo-source.md +1 -1
- package/docs/usage/compositions/purpose-input-comment.md +5 -3
- package/docs/usage/compositions/purpose-input-message.md +5 -3
- package/docs/usage/compositions/reference-first.md +63 -42
- package/docs/usage/compositions/reference-review.md +60 -50
- package/docs/usage/compositions/reference-settings.md +60 -50
- package/docs/usage/compositions/selection-scope.md +48 -35
- package/docs/usage/compositions/stea-order-progress.md +1 -0
- package/docs/usage/compositions/stea-otp-verify.md +5 -3
- package/docs/usage/compositions/stea-pixel-empty.md +3 -3
- package/docs/usage/compositions/time-selection.md +6 -5
- package/docs/usage/compositions/upload-recovery.md +10 -1
- package/docs/usage/compositions/video-dialog.md +10 -3
- package/docs/usage/compositions/web-additions.md +5 -5
- package/docs/usage/compositions/web-navigation.md +1 -1
- package/docs/usage/screens/common-chat.md +21 -8
- package/docs/usage/screens/common-comments.md +13 -1
- package/docs/usage/screens/common-inbox.md +19 -6
- package/docs/usage/screens/common-login.md +19 -7
- package/docs/usage/screens/common-profile.md +22 -9
- package/docs/usage/screens/common-saved.md +16 -2
- package/docs/usage/screens/common-search.md +24 -4
- package/docs/usage/screens/common-settings.md +22 -50
- package/docs/usage/screens/common-shell.md +14 -2
- package/docs/usage/screens/dashboard.md +52 -109
- package/docs/usage/screens/discovery-gallery.md +57 -148
- package/docs/usage/screens/flow-collection.md +38 -9
- package/docs/usage/screens/flow-editor.md +25 -7
- package/docs/usage/screens/flow-media.md +23 -7
- package/docs/usage/screens/flow-moderation.md +15 -2
- package/docs/usage/screens/flow-onboarding.md +3 -3
- package/docs/usage/screens/flow-permission.md +17 -3
- package/docs/usage/screens/landing.md +37 -20
- package/docs/usage/screens/mockup-studio.md +22 -7
- package/docs/usage/screens/notification-settings.md +18 -4
- package/docs/usage/screens/product-bento.md +18 -9
- package/docs/usage/screens/reference-comparison.md +29 -10
- package/docs/usage/tokens/color.md +6 -3
- package/docs/usage/tokens/elevation-opacity.md +4 -2
- package/docs/usage/tokens/font-roles.md +2 -2
- package/docs/usage/tokens/layers.md +3 -3
- package/docs/usage/tokens/motion.md +2 -2
- package/docs/usage/tokens/radius.md +4 -2
- package/docs/usage/tokens/spacing.md +3 -3
- package/docs/usage/tokens/theme-studio.md +2 -2
- package/docs/usage/tokens/typography-studio.md +14 -8
- package/docs/usage/tokens/typography.md +15 -5
- package/package.json +1 -1
package/dist/field-group.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export type FieldGroupLayout = "stack" | "inline";
|
|
2
|
+
/** Date and duration parts share grouping, spacing and wrapping rather than rebuilding a fieldset. */
|
|
3
|
+
export declare const fieldGroupRecipe: {
|
|
4
|
+
readonly defaults: {
|
|
5
|
+
readonly layout: "stack";
|
|
6
|
+
};
|
|
7
|
+
readonly gap: "md";
|
|
8
|
+
readonly headingGap: "sm";
|
|
9
|
+
readonly supportGap: "xs";
|
|
10
|
+
readonly inline: {
|
|
11
|
+
readonly minCharacters: 10;
|
|
12
|
+
readonly minWidth: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
1
15
|
/** Related-input grouping contract. Values and submit/validation timing remain product-owned. */
|
|
2
16
|
export type FieldGroupMember = Readonly<{
|
|
3
17
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-group.d.ts","sourceRoot":"","sources":["../src/field-group.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"field-group.d.ts","sourceRoot":"","sources":["../src/field-group.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAClD,sGAAsG;AACtG,eAAO,MAAM,gBAAgB;;;;;;;;;;;CAMnB,CAAC;AAEX,iGAAiG;AACjG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8FAA8F;IAC9F,KAAK,CAAC,EAAE,QAAQ,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACnE,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,SAAS,QAAQ,CAAC;QAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;CACzG,CAAC,CAAC;AAMH,6FAA6F;AAC7F,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,wBAAwB,EAAE;iBAItE,SAAS,wBAAwB,EAAE;IAQhD,yFAAyF;;UAEnF,IAAI,SAAS,OAAO,EAAE,MAAM,MAAM,YAAY,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI;EAOxG;AAUD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,oBAAoB,GAAG,QAAQ,CAAC;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,SAAS,wBAAwB,EAAE,CAAC;CAC7C,CAAC,CA2CD"}
|
package/dist/field-group.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { spacing } from "./foundations.js";
|
|
2
|
+
/** Date and duration parts share grouping, spacing and wrapping rather than rebuilding a fieldset. */
|
|
3
|
+
export const fieldGroupRecipe = {
|
|
4
|
+
defaults: { layout: "stack" },
|
|
5
|
+
gap: "md",
|
|
6
|
+
headingGap: "sm",
|
|
7
|
+
supportGap: "xs",
|
|
8
|
+
inline: { minCharacters: 10, minWidth: spacing.xxxl * 3 },
|
|
9
|
+
};
|
|
1
10
|
function requireText(value, name) {
|
|
2
11
|
if (typeof value !== "string" || !value.trim())
|
|
3
12
|
throw new TypeError(`Field group ${name} must be nonempty text`);
|
package/dist/field-group.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-group.js","sourceRoot":"","sources":["../src/field-group.ts"],"names":[],"mappings":"AA4BA,SAAS,WAAW,CAAC,KAAc,EAAE,IAAY;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,eAAe,IAAI,wBAAwB,CAAC,CAAC;AACnH,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,2BAA2B,CAAC,MAA2C;IACrF,IAAI,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO;QACL,MAAM,CAAC,IAAyC;YAC9C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YACrD,8EAA8E;YAC9E,6EAA6E;YAC7E,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1E,KAAK,MAAM,EAAE,IAAI,OAAO;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACpE,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QAClF,CAAC;QACD,yFAAyF;QACzF,OAAO,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC9B,KAAK,CAAyB,EAAU,EAAE,QAAiC;YACzE,0EAA0E;YAC1E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/B,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAChG,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,IAAY;IAChD,IAAI,KAAK,KAAK,SAAS;QAAE,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;AACrH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAgC;IAOhE,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACvC,YAAY,CAAC,UAAU,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACpD,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;IAClG,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACtC,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAClC,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACxC,YAAY,CAAC,KAAK,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QACrD,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACzC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;QACnF,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACnC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC3C,0EAA0E;QAC1E,8EAA8E;QAC9E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAChH,MAAM,IAAI,SAAS,CAAC,6DAA6D,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;QAChC,QAAQ;QACR,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YAClD,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;YACxG,MAAM,OAAO,GAAkD,EAAE,CAAC;YAClE,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;YAC9H,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YACpH,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAClG,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YAChG,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,KAAK;gBACnF,+EAA+E;gBAC/E,QAAQ,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS;gBAC/G,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;KACJ,CAAC,CAAC;AACL,CAAC","sourcesContent":["/** Related-input grouping contract. Values and submit/validation timing remain product-owned. */\nexport type FieldGroupMember = Readonly<{\n id: string;\n label: string;\n description?: string;\n error?: string;\n disabled?: boolean;\n}>;\n\nexport type FieldGroupDescriptor = Readonly<{\n label: string;\n description?: string;\n disabled?: boolean;\n /** An empty fieldIds list is a group-only error, not a reason to mark every field invalid. */\n error?: Readonly<{ message: string; fieldIds: readonly string[] }>;\n fields: readonly FieldGroupMember[];\n}>;\n\nexport type ResolvedFieldGroupMember = Readonly<{\n id: string;\n label: string;\n groupLabel: string;\n disabled: boolean;\n invalid: boolean;\n /** Renderers associate these messages without replacing a field's own help or error. */\n support: readonly Readonly<{ scope: \"group\" | \"field\"; kind: \"description\" | \"error\"; text: string }>[];\n}>;\n\nfunction requireText(value: unknown, name: string): asserts value is string {\n if (typeof value !== \"string\" || !value.trim()) throw new TypeError(`Field group ${name} must be nonempty text`);\n}\n\n/** Late native events and retained callbacks must use the current committed group policy. */\nexport function createFieldGroupEditSession(fields: readonly ResolvedFieldGroupMember[]) {\n let enabled = new Set(fields.filter(field => !field.disabled).map(field => field.id));\n const members = new Map(fields.map(field => [field.id, {}]));\n return {\n update(next: readonly ResolvedFieldGroupMember[]) {\n const current = new Set(next.map(field => field.id));\n // A reused id is a new mounted field after removal. Checking only enabled ids\n // would revive queued events from the old field and overwrite the new draft.\n for (const id of members.keys()) if (!current.has(id)) members.delete(id);\n for (const id of current) if (!members.has(id)) members.set(id, {});\n enabled = new Set(next.filter(field => !field.disabled).map(field => field.id));\n },\n /** Effect cleanup blocks events without treating Strict Mode replay as field removal. */\n suspend() { enabled.clear(); },\n guard<Args extends unknown[]>(id: string, callback: (...args: Args) => void): (...args: Args) => void {\n // A new host binds during render, before its committed policy enables it.\n if (!members.has(id)) members.set(id, {});\n const member = members.get(id);\n return (...args) => { if (members.get(id) === member && enabled.has(id)) callback(...args); };\n },\n };\n}\n\nfunction optionalText(value: unknown, name: string): void {\n if (value !== undefined) requireText(value, name);\n}\n\nfunction optionalBoolean(value: unknown): void {\n if (value !== undefined && typeof value !== \"boolean\") throw new TypeError(\"Field group disabled must be boolean\");\n}\n\nexport function resolveFieldGroup(descriptor: FieldGroupDescriptor): Readonly<{\n label: string;\n description: string | undefined;\n error: string | undefined;\n disabled: boolean;\n fields: readonly ResolvedFieldGroupMember[];\n}> {\n requireText(descriptor.label, \"label\");\n optionalText(descriptor.description, \"description\");\n optionalBoolean(descriptor.disabled);\n if (!Array.isArray(descriptor.fields)) throw new TypeError(\"Field group fields must be an array\");\n const ids = new Set<string>();\n for (const field of descriptor.fields) {\n requireText(field.id, \"field id\");\n requireText(field.label, \"field label\");\n optionalText(field.description, \"field description\");\n optionalText(field.error, \"field error\");\n optionalBoolean(field.disabled);\n if (ids.has(field.id)) throw new TypeError(\"Field group field ids must be unique\");\n ids.add(field.id);\n }\n if (descriptor.error !== undefined) {\n requireText(descriptor.error.message, \"error\");\n const affected = descriptor.error.fieldIds;\n // A typo or removed field must not silently lose its validation feedback.\n // Products update the visible error with their dynamic field list atomically.\n if (!Array.isArray(affected) || new Set(affected).size !== affected.length || affected.some(id => !ids.has(id))) {\n throw new TypeError(\"Field group error must reference distinct current field ids\");\n }\n }\n const disabled = descriptor.disabled ?? false;\n return Object.freeze({\n label: descriptor.label,\n description: descriptor.description,\n error: descriptor.error?.message,\n disabled,\n fields: Object.freeze(descriptor.fields.map(field => {\n const groupError = descriptor.error?.fieldIds.includes(field.id) ? descriptor.error.message : undefined;\n const support: ResolvedFieldGroupMember[\"support\"][number][] = [];\n if (descriptor.description !== undefined) support.push({ scope: \"group\", kind: \"description\", text: descriptor.description });\n if (field.description !== undefined) support.push({ scope: \"field\", kind: \"description\", text: field.description });\n if (field.error !== undefined) support.push({ scope: \"field\", kind: \"error\", text: field.error });\n if (groupError !== undefined) support.push({ scope: \"group\", kind: \"error\", text: groupError });\n return Object.freeze({ id: field.id, label: field.label, groupLabel: descriptor.label,\n // Group unlock restores independently disabled members; it never enables them.\n disabled: disabled || (field.disabled ?? false), invalid: field.error !== undefined || groupError !== undefined,\n support: Object.freeze(support.map(message => Object.freeze(message))) });\n })),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"field-group.js","sourceRoot":"","sources":["../src/field-group.ts"],"names":[],"mappings":"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","sourcesContent":["import { spacing } from \"./foundations.js\";\n\nexport type FieldGroupLayout = \"stack\" | \"inline\";\n/** Date and duration parts share grouping, spacing and wrapping rather than rebuilding a fieldset. */\nexport const fieldGroupRecipe = {\n defaults: { layout: \"stack\" },\n gap: \"md\",\n headingGap: \"sm\",\n supportGap: \"xs\",\n inline: { minCharacters: 10, minWidth: spacing.xxxl * 3 },\n} as const;\n\n/** Related-input grouping contract. Values and submit/validation timing remain product-owned. */\nexport type FieldGroupMember = Readonly<{\n id: string;\n label: string;\n description?: string;\n error?: string;\n disabled?: boolean;\n}>;\n\nexport type FieldGroupDescriptor = Readonly<{\n label: string;\n description?: string;\n disabled?: boolean;\n /** An empty fieldIds list is a group-only error, not a reason to mark every field invalid. */\n error?: Readonly<{ message: string; fieldIds: readonly string[] }>;\n fields: readonly FieldGroupMember[];\n}>;\n\nexport type ResolvedFieldGroupMember = Readonly<{\n id: string;\n label: string;\n groupLabel: string;\n disabled: boolean;\n invalid: boolean;\n /** Renderers associate these messages without replacing a field's own help or error. */\n support: readonly Readonly<{ scope: \"group\" | \"field\"; kind: \"description\" | \"error\"; text: string }>[];\n}>;\n\nfunction requireText(value: unknown, name: string): asserts value is string {\n if (typeof value !== \"string\" || !value.trim()) throw new TypeError(`Field group ${name} must be nonempty text`);\n}\n\n/** Late native events and retained callbacks must use the current committed group policy. */\nexport function createFieldGroupEditSession(fields: readonly ResolvedFieldGroupMember[]) {\n let enabled = new Set(fields.filter(field => !field.disabled).map(field => field.id));\n const members = new Map(fields.map(field => [field.id, {}]));\n return {\n update(next: readonly ResolvedFieldGroupMember[]) {\n const current = new Set(next.map(field => field.id));\n // A reused id is a new mounted field after removal. Checking only enabled ids\n // would revive queued events from the old field and overwrite the new draft.\n for (const id of members.keys()) if (!current.has(id)) members.delete(id);\n for (const id of current) if (!members.has(id)) members.set(id, {});\n enabled = new Set(next.filter(field => !field.disabled).map(field => field.id));\n },\n /** Effect cleanup blocks events without treating Strict Mode replay as field removal. */\n suspend() { enabled.clear(); },\n guard<Args extends unknown[]>(id: string, callback: (...args: Args) => void): (...args: Args) => void {\n // A new host binds during render, before its committed policy enables it.\n if (!members.has(id)) members.set(id, {});\n const member = members.get(id);\n return (...args) => { if (members.get(id) === member && enabled.has(id)) callback(...args); };\n },\n };\n}\n\nfunction optionalText(value: unknown, name: string): void {\n if (value !== undefined) requireText(value, name);\n}\n\nfunction optionalBoolean(value: unknown): void {\n if (value !== undefined && typeof value !== \"boolean\") throw new TypeError(\"Field group disabled must be boolean\");\n}\n\nexport function resolveFieldGroup(descriptor: FieldGroupDescriptor): Readonly<{\n label: string;\n description: string | undefined;\n error: string | undefined;\n disabled: boolean;\n fields: readonly ResolvedFieldGroupMember[];\n}> {\n requireText(descriptor.label, \"label\");\n optionalText(descriptor.description, \"description\");\n optionalBoolean(descriptor.disabled);\n if (!Array.isArray(descriptor.fields)) throw new TypeError(\"Field group fields must be an array\");\n const ids = new Set<string>();\n for (const field of descriptor.fields) {\n requireText(field.id, \"field id\");\n requireText(field.label, \"field label\");\n optionalText(field.description, \"field description\");\n optionalText(field.error, \"field error\");\n optionalBoolean(field.disabled);\n if (ids.has(field.id)) throw new TypeError(\"Field group field ids must be unique\");\n ids.add(field.id);\n }\n if (descriptor.error !== undefined) {\n requireText(descriptor.error.message, \"error\");\n const affected = descriptor.error.fieldIds;\n // A typo or removed field must not silently lose its validation feedback.\n // Products update the visible error with their dynamic field list atomically.\n if (!Array.isArray(affected) || new Set(affected).size !== affected.length || affected.some(id => !ids.has(id))) {\n throw new TypeError(\"Field group error must reference distinct current field ids\");\n }\n }\n const disabled = descriptor.disabled ?? false;\n return Object.freeze({\n label: descriptor.label,\n description: descriptor.description,\n error: descriptor.error?.message,\n disabled,\n fields: Object.freeze(descriptor.fields.map(field => {\n const groupError = descriptor.error?.fieldIds.includes(field.id) ? descriptor.error.message : undefined;\n const support: ResolvedFieldGroupMember[\"support\"][number][] = [];\n if (descriptor.description !== undefined) support.push({ scope: \"group\", kind: \"description\", text: descriptor.description });\n if (field.description !== undefined) support.push({ scope: \"field\", kind: \"description\", text: field.description });\n if (field.error !== undefined) support.push({ scope: \"field\", kind: \"error\", text: field.error });\n if (groupError !== undefined) support.push({ scope: \"group\", kind: \"error\", text: groupError });\n return Object.freeze({ id: field.id, label: field.label, groupLabel: descriptor.label,\n // Group unlock restores independently disabled members; it never enables them.\n disabled: disabled || (field.disabled ?? false), invalid: field.error !== undefined || groupError !== undefined,\n support: Object.freeze(support.map(message => Object.freeze(message))) });\n })),\n });\n}\n"]}
|
package/dist/form.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAMH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9E,MAAM,MAAM,eAAe,GACvB,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5B,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC,GACjC,QAAQ,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GACzB,QAAQ,CAAC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAChD,QAAQ,CAAC;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,GAC9D,QAAQ,CAAC;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAAC;AAEzC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjF,MAAM,MAAM,wBAAwB,CAAC,MAAM,IAAI,QAAQ,CAAC;IACtD,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACpC,oGAAoG;IACpG,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClD,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,QAAQ,CAAC;IAC/C,WAAW,IAAI,eAAe,CAAC;IAC/B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnD,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC;IACjB,OAAO,IAAI,OAAO,CAAC;CACpB,CAAC,CAAC;AAQH,wBAAgB,gCAAgC,CAAC,MAAM,EACrD,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,IAAI,CAKN;AAcD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAC5C,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,iBAAiB,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAMH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE9E,MAAM,MAAM,eAAe,GACvB,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5B,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC,GACjC,QAAQ,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GACzB,QAAQ,CAAC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,GAClC,QAAQ,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAChD,QAAQ,CAAC;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,GAC9D,QAAQ,CAAC;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAAC;AAEzC,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjF,MAAM,MAAM,wBAAwB,CAAC,MAAM,IAAI,QAAQ,CAAC;IACtD,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACpC,oGAAoG;IACpG,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAClD,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,QAAQ,CAAC;IAC/C,WAAW,IAAI,eAAe,CAAC;IAC/B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnD,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC;IACjB,OAAO,IAAI,OAAO,CAAC;CACpB,CAAC,CAAC;AAQH,wBAAgB,gCAAgC,CAAC,MAAM,EACrD,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,IAAI,CAKN;AAcD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAC5C,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACxC,iBAAiB,CAAC,MAAM,CAAC,CA0E3B;AAMD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAW1E;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,GACvD,MAAM,GAAG,IAAI,CAQf;AAMD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,CAAC;AAEpD,eAAO,MAAM,YAAY;;CAE8B,CAAC;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAgBrB,CAAC;AAMH,eAAO,MAAM,oBAAoB;IAC/B,+FAA+F;;CAElB,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;QAT3B,+FAA+F;;;;;;;;;;;;;;;;;;CAsCvF,CAAC"}
|
package/dist/form.js
CHANGED
|
@@ -51,7 +51,6 @@ export function createFormSubmitSession(options) {
|
|
|
51
51
|
if (phase.status === "submitting") {
|
|
52
52
|
return Promise.resolve({ outcome: "blocked", reason: "already-submitting" });
|
|
53
53
|
}
|
|
54
|
-
transition({ status: "submitting" });
|
|
55
54
|
return new Promise((resolve) => {
|
|
56
55
|
let settled = false;
|
|
57
56
|
const settleOnce = (outcome) => {
|
|
@@ -62,20 +61,28 @@ export function createFormSubmitSession(options) {
|
|
|
62
61
|
resolve(outcome);
|
|
63
62
|
};
|
|
64
63
|
interruptCurrentAttempt = settleOnce;
|
|
64
|
+
// Subscribers may dispose synchronously. Install cancellation before notifying.
|
|
65
|
+
transition({ status: "submitting" });
|
|
66
|
+
if (settled)
|
|
67
|
+
return;
|
|
65
68
|
(async () => {
|
|
66
69
|
try {
|
|
67
70
|
await options.onSubmit(values);
|
|
68
71
|
if (settled)
|
|
69
72
|
return;
|
|
70
|
-
|
|
73
|
+
phase = { status: "succeeded" };
|
|
71
74
|
settleOnce({ outcome: "succeeded" });
|
|
75
|
+
// A terminal-state subscriber can start the next attempt; do not clear
|
|
76
|
+
// that new attempt's cancellation handle after notifying it.
|
|
77
|
+
notify();
|
|
72
78
|
}
|
|
73
79
|
catch (error) {
|
|
74
80
|
if (settled)
|
|
75
81
|
return;
|
|
76
82
|
const message = resolveFailureMessage(options, error);
|
|
77
|
-
|
|
83
|
+
phase = { status: "failed", message };
|
|
78
84
|
settleOnce({ outcome: "failed", message });
|
|
85
|
+
notify();
|
|
79
86
|
}
|
|
80
87
|
})();
|
|
81
88
|
});
|
package/dist/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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","sourcesContent":["import { spacing } from \"./foundations.js\";\n\n/**\n * Form owns two things only: the vertical rhythm between already-framed\n * Fields, and the repeatable submit session (idle → submitting →\n * succeeded/failed). Field values, registration, dependencies, validateTrigger\n * policy and Form.List-style repeating groups stay product-owned (React Hook\n * Form or equivalent) — see docs/form.md for the full boundary.\n */\n\n// ---------------------------------------------------------------------------\n// Submit session\n// ---------------------------------------------------------------------------\n\nexport type FormSubmitStatus = \"idle\" | \"submitting\" | \"succeeded\" | \"failed\";\n\nexport type FormSubmitPhase =\n | Readonly<{ status: \"idle\" }>\n | Readonly<{ status: \"submitting\" }>\n | Readonly<{ status: \"succeeded\" }>\n | Readonly<{ status: \"failed\"; message: string }>;\n\nexport type FormSubmitOutcome =\n | Readonly<{ outcome: \"succeeded\" }>\n | Readonly<{ outcome: \"failed\"; message: string }>\n | Readonly<{ outcome: \"blocked\"; reason: \"already-submitting\" }>\n | Readonly<{ outcome: \"interrupted\" }>;\n\n/** Rejects to report a submission failure; resolving never implies success. */\nexport type FormSubmitHandler<Values> = (values: Values) => void | Promise<void>;\n\nexport type FormSubmitSessionOptions<Values> = Readonly<{\n onSubmit: FormSubmitHandler<Values>;\n /** Localized fallback shown when `resolveErrorMessage` is absent, throws, or returns empty copy. */\n fallbackErrorMessage: string;\n resolveErrorMessage?: (error: unknown) => string;\n}>;\n\n/**\n * A single submit session is reused across many attempts — unlike\n * AlertDialog's one-shot confirm, a form typically stays mounted and gets\n * resubmitted (edit → retry, or a settings screen saved again later). Each\n * attempt still settles its own returned Promise exactly once, and\n * `submitting` blocks every concurrent attempt the same way AlertDialog's\n * `busy` blocks a second confirm.\n */\nexport type FormSubmitSession<Values> = Readonly<{\n getSnapshot(): FormSubmitPhase;\n subscribe(listener: () => void): () => void;\n submit(values: Values): Promise<FormSubmitOutcome>;\n /** Moves a resting `succeeded`/`failed` phase back to `idle`. No-op while submitting. */\n reset(): boolean;\n dispose(): boolean;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Form ${field} must not be empty`);\n }\n}\n\nexport function validateFormSubmitSessionOptions<Values>(\n options: FormSubmitSessionOptions<Values>,\n): void {\n if (typeof options.onSubmit !== \"function\") {\n throw new TypeError(\"Form onSubmit must be a function\");\n }\n assertCopy(options.fallbackErrorMessage, \"fallbackErrorMessage\");\n}\n\nfunction resolveFailureMessage<Values>(\n options: FormSubmitSessionOptions<Values>,\n error: unknown,\n): string {\n try {\n const resolved = options.resolveErrorMessage?.(error)?.trim();\n return resolved || options.fallbackErrorMessage;\n } catch {\n return options.fallbackErrorMessage;\n }\n}\n\n/**\n * Owns submit re-entrancy and exactly-once settlement. Does not own field\n * values (the caller passes `values` at call time), field-level validation,\n * or when submission is allowed to start (the product decides that from its\n * own dirty/valid state before calling `submit`).\n */\nexport function createFormSubmitSession<Values>(\n options: FormSubmitSessionOptions<Values>,\n): FormSubmitSession<Values> {\n validateFormSubmitSessionOptions(options);\n\n let phase: FormSubmitPhase = { status: \"idle\" };\n let disposed = false;\n let interruptCurrentAttempt: ((outcome: FormSubmitOutcome) => void) | null = null;\n const listeners = new Set<() => void>();\n\n const notify = () => {\n for (const listener of listeners) listener();\n };\n const transition = (next: FormSubmitPhase) => {\n phase = next;\n notify();\n };\n\n return {\n getSnapshot: () => phase,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n submit(values) {\n if (disposed) throw new Error(\"Cannot use a disposed Form submit session\");\n if (phase.status === \"submitting\") {\n return Promise.resolve({ outcome: \"blocked\", reason: \"already-submitting\" });\n }\n\n transition({ status: \"submitting\" });\n\n return new Promise<FormSubmitOutcome>((resolve) => {\n let settled = false;\n const settleOnce = (outcome: FormSubmitOutcome) => {\n if (settled) return;\n settled = true;\n interruptCurrentAttempt = null;\n resolve(outcome);\n };\n interruptCurrentAttempt = settleOnce;\n\n (async () => {\n try {\n await options.onSubmit(values);\n if (settled) return;\n transition({ status: \"succeeded\" });\n settleOnce({ outcome: \"succeeded\" });\n } catch (error) {\n if (settled) return;\n const message = resolveFailureMessage(options, error);\n transition({ status: \"failed\", message });\n settleOnce({ outcome: \"failed\", message });\n }\n })();\n });\n },\n reset() {\n if (disposed) return false;\n if (phase.status === \"idle\" || phase.status === \"submitting\") return false;\n transition({ status: \"idle\" });\n return true;\n },\n dispose() {\n if (disposed) return false;\n disposed = true;\n interruptCurrentAttempt?.({ outcome: \"interrupted\" });\n interruptCurrentAttempt = null;\n return true;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Submit-failure focus\n// ---------------------------------------------------------------------------\n\n/**\n * Form does not register fields or run validation, so it cannot find the\n * first invalid field on its own. The product supplies its own field render\n * order and the ids it currently considers invalid (e.g. React Hook Form's\n * `formState.errors` keys); Form only fixes which one wins the race — first\n * in render order — so every product answers this the same way.\n */\nexport function validateFormFieldOrder(fieldOrder: readonly string[]): void {\n const seen = new Set<string>();\n fieldOrder.forEach((fieldId, index) => {\n if (fieldId.trim().length === 0) {\n throw new TypeError(`Form field id at index ${index} must not be empty`);\n }\n if (seen.has(fieldId)) {\n throw new TypeError(`Duplicate Form field id: ${fieldId}`);\n }\n seen.add(fieldId);\n });\n}\n\n/**\n * Web renderers call `.focus()` on the resolved field's control; React Native\n * renderers move accessibility focus to it (e.g. `AccessibilityInfo.sendAccessibilityEvent`\n * or a focus-trap ref). Returns `null` when there are no fields yet or none\n * are invalid, in which case a failed submit has no field to focus and the\n * form-level error slot below owns the announcement instead.\n */\nexport function resolveFirstInvalidFieldFocusTarget(\n fieldOrder: readonly string[],\n invalidFieldIds: ReadonlySet<string> | readonly string[],\n): string | null {\n validateFormFieldOrder(fieldOrder);\n const invalid =\n invalidFieldIds instanceof Set ? invalidFieldIds : new Set(invalidFieldIds);\n for (const fieldId of fieldOrder) {\n if (invalid.has(fieldId)) return fieldId;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Visual recipe — inter-field rhythm only\n// ---------------------------------------------------------------------------\n\nexport type FormDensity = \"compact\" | \"comfortable\";\n\nexport const formDefaults = {\n density: \"comfortable\",\n} as const satisfies Readonly<{ density: FormDensity }>;\n\n/**\n * Anatomy is fixed, not a configurable prop: fields, then the form-level\n * error, then actions. `field` frame styling (background, border, its own\n * label/hint/error gap) belongs to `fieldRecipe`; this recipe only sets the\n * gap *between* one field and the next. `formError` renders as the existing\n * Notice `tone=\"danger\"` contract — Form fixes only its slot position, it\n * does not redeclare Notice's colors. `actions` has no dedicated layout here;\n * composing Button/Stack in that slot is the product's job, same reasoning\n * the roadmap already applies to Stack.\n */\nexport const formRecipe = {\n slots: [\"root\", \"field\", \"formError\", \"actions\"] as const,\n defaults: formDefaults,\n density: {\n compact: { fieldGap: spacing.sm },\n comfortable: { fieldGap: spacing.lg },\n },\n formError: {\n position: \"beforeActions\",\n gap: spacing.sm,\n },\n} as const satisfies Readonly<{\n slots: readonly [\"root\", \"field\", \"formError\", \"actions\"];\n defaults: Readonly<{ density: FormDensity }>;\n density: Record<FormDensity, Readonly<{ fieldGap: number }>>;\n formError: Readonly<{ position: \"beforeActions\"; gap: number }>;\n}>;\n\n// ---------------------------------------------------------------------------\n// Behavior — source for the shared behaviorRegistry wiring\n// ---------------------------------------------------------------------------\n\nexport const formBehaviorDefaults = {\n /** Reuses Toast's own `normal | high` vocabulary: a blocked submission is `high`/assertive. */\n errorAnnouncementPriority: \"high\",\n} as const satisfies Readonly<{ errorAnnouncementPriority: \"normal\" | \"high\" }>;\n\n/**\n * Kept beside the Form contract as the source value wired by\n * `behaviorRegistry.form`; this lets the registry reuse the contract without\n * redeclaring Form's inputs, events, states, or scenarios.\n */\nexport const formBehaviorSpec = {\n /*\n 값은 제품이 소유하므로 controlled prop이 없다. 대신 renderer가 반드시 배선해야 하는\n 것을 `inputs`/`events`로 선언한다 — 공유 불변식이 \"controlled + inputs + events가\n 하나도 없는 behavior는 renderer 테스트가 구동할 대상이 없다\"고 요구하고, 그 지적이\n 옳다. 같은 형태(컨트롤러가 상태를 소유하고 renderer가 구동)인 `loadMore`가 이미\n `controlled: []` + `inputs` + `events`로 선언한 선례를 따른다.\n */\n controlled: [],\n inputs: [\"status\", \"formError\", \"fieldOrder\"],\n events: [\"onSubmit\"],\n defaults: formBehaviorDefaults,\n stateAxes: {\n availability: [\"enabled\", \"busy\"],\n content: [\"idle\", \"loading\", \"error\"],\n },\n web: { roles: [\"alert\"], keyboard: [\"Tab\", \"Enter\"], focus: \"native\" },\n native: { roles: [\"alert\"], states: [\"busy\"], actions: [\"submit\"] },\n scenarios: [\n \"submitting-blocks-every-concurrent-submit-attempt\",\n \"each-submit-attempt-settles-its-result-exactly-once\",\n \"failed-submit-returns-to-a-resting-state-that-still-allows-retry\",\n \"succeeded-submit-remains-resting-and-allows-resubmission\",\n \"dispose-during-submit-settles-the-pending-attempt-as-interrupted\",\n \"disposed-session-rejects-further-submit-attempts\",\n \"form-level-error-never-carries-a-per-field-message\",\n \"first-invalid-field-in-render-order-receives-focus-on-submit-failure\",\n \"form-level-error-is-announced-when-no-field-target-exists\",\n ],\n} as const;\n"]}
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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","sourcesContent":["import { spacing } from \"./foundations.js\";\n\n/**\n * Form owns two things only: the vertical rhythm between already-framed\n * Fields, and the repeatable submit session (idle → submitting →\n * succeeded/failed). Field values, registration, dependencies, validateTrigger\n * policy and Form.List-style repeating groups stay product-owned (React Hook\n * Form or equivalent) — see docs/form.md for the full boundary.\n */\n\n// ---------------------------------------------------------------------------\n// Submit session\n// ---------------------------------------------------------------------------\n\nexport type FormSubmitStatus = \"idle\" | \"submitting\" | \"succeeded\" | \"failed\";\n\nexport type FormSubmitPhase =\n | Readonly<{ status: \"idle\" }>\n | Readonly<{ status: \"submitting\" }>\n | Readonly<{ status: \"succeeded\" }>\n | Readonly<{ status: \"failed\"; message: string }>;\n\nexport type FormSubmitOutcome =\n | Readonly<{ outcome: \"succeeded\" }>\n | Readonly<{ outcome: \"failed\"; message: string }>\n | Readonly<{ outcome: \"blocked\"; reason: \"already-submitting\" }>\n | Readonly<{ outcome: \"interrupted\" }>;\n\n/** Rejects to report a submission failure; resolving never implies success. */\nexport type FormSubmitHandler<Values> = (values: Values) => void | Promise<void>;\n\nexport type FormSubmitSessionOptions<Values> = Readonly<{\n onSubmit: FormSubmitHandler<Values>;\n /** Localized fallback shown when `resolveErrorMessage` is absent, throws, or returns empty copy. */\n fallbackErrorMessage: string;\n resolveErrorMessage?: (error: unknown) => string;\n}>;\n\n/**\n * A single submit session is reused across many attempts — unlike\n * AlertDialog's one-shot confirm, a form typically stays mounted and gets\n * resubmitted (edit → retry, or a settings screen saved again later). Each\n * attempt still settles its own returned Promise exactly once, and\n * `submitting` blocks every concurrent attempt the same way AlertDialog's\n * `busy` blocks a second confirm.\n */\nexport type FormSubmitSession<Values> = Readonly<{\n getSnapshot(): FormSubmitPhase;\n subscribe(listener: () => void): () => void;\n submit(values: Values): Promise<FormSubmitOutcome>;\n /** Moves a resting `succeeded`/`failed` phase back to `idle`. No-op while submitting. */\n reset(): boolean;\n dispose(): boolean;\n}>;\n\nfunction assertCopy(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Form ${field} must not be empty`);\n }\n}\n\nexport function validateFormSubmitSessionOptions<Values>(\n options: FormSubmitSessionOptions<Values>,\n): void {\n if (typeof options.onSubmit !== \"function\") {\n throw new TypeError(\"Form onSubmit must be a function\");\n }\n assertCopy(options.fallbackErrorMessage, \"fallbackErrorMessage\");\n}\n\nfunction resolveFailureMessage<Values>(\n options: FormSubmitSessionOptions<Values>,\n error: unknown,\n): string {\n try {\n const resolved = options.resolveErrorMessage?.(error)?.trim();\n return resolved || options.fallbackErrorMessage;\n } catch {\n return options.fallbackErrorMessage;\n }\n}\n\n/**\n * Owns submit re-entrancy and exactly-once settlement. Does not own field\n * values (the caller passes `values` at call time), field-level validation,\n * or when submission is allowed to start (the product decides that from its\n * own dirty/valid state before calling `submit`).\n */\nexport function createFormSubmitSession<Values>(\n options: FormSubmitSessionOptions<Values>,\n): FormSubmitSession<Values> {\n validateFormSubmitSessionOptions(options);\n\n let phase: FormSubmitPhase = { status: \"idle\" };\n let disposed = false;\n let interruptCurrentAttempt: ((outcome: FormSubmitOutcome) => void) | null = null;\n const listeners = new Set<() => void>();\n\n const notify = () => {\n for (const listener of listeners) listener();\n };\n const transition = (next: FormSubmitPhase) => {\n phase = next;\n notify();\n };\n\n return {\n getSnapshot: () => phase,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n submit(values) {\n if (disposed) throw new Error(\"Cannot use a disposed Form submit session\");\n if (phase.status === \"submitting\") {\n return Promise.resolve({ outcome: \"blocked\", reason: \"already-submitting\" });\n }\n\n return new Promise<FormSubmitOutcome>((resolve) => {\n let settled = false;\n const settleOnce = (outcome: FormSubmitOutcome) => {\n if (settled) return;\n settled = true;\n interruptCurrentAttempt = null;\n resolve(outcome);\n };\n interruptCurrentAttempt = settleOnce;\n // Subscribers may dispose synchronously. Install cancellation before notifying.\n transition({ status: \"submitting\" });\n if (settled) return;\n\n (async () => {\n try {\n await options.onSubmit(values);\n if (settled) return;\n phase = { status: \"succeeded\" };\n settleOnce({ outcome: \"succeeded\" });\n // A terminal-state subscriber can start the next attempt; do not clear\n // that new attempt's cancellation handle after notifying it.\n notify();\n } catch (error) {\n if (settled) return;\n const message = resolveFailureMessage(options, error);\n phase = { status: \"failed\", message };\n settleOnce({ outcome: \"failed\", message });\n notify();\n }\n })();\n });\n },\n reset() {\n if (disposed) return false;\n if (phase.status === \"idle\" || phase.status === \"submitting\") return false;\n transition({ status: \"idle\" });\n return true;\n },\n dispose() {\n if (disposed) return false;\n disposed = true;\n interruptCurrentAttempt?.({ outcome: \"interrupted\" });\n interruptCurrentAttempt = null;\n return true;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Submit-failure focus\n// ---------------------------------------------------------------------------\n\n/**\n * Form does not register fields or run validation, so it cannot find the\n * first invalid field on its own. The product supplies its own field render\n * order and the ids it currently considers invalid (e.g. React Hook Form's\n * `formState.errors` keys); Form only fixes which one wins the race — first\n * in render order — so every product answers this the same way.\n */\nexport function validateFormFieldOrder(fieldOrder: readonly string[]): void {\n const seen = new Set<string>();\n fieldOrder.forEach((fieldId, index) => {\n if (fieldId.trim().length === 0) {\n throw new TypeError(`Form field id at index ${index} must not be empty`);\n }\n if (seen.has(fieldId)) {\n throw new TypeError(`Duplicate Form field id: ${fieldId}`);\n }\n seen.add(fieldId);\n });\n}\n\n/**\n * Web renderers call `.focus()` on the resolved field's control; React Native\n * renderers move accessibility focus to it (e.g. `AccessibilityInfo.sendAccessibilityEvent`\n * or a focus-trap ref). Returns `null` when there are no fields yet or none\n * are invalid, in which case a failed submit has no field to focus and the\n * form-level error slot below owns the announcement instead.\n */\nexport function resolveFirstInvalidFieldFocusTarget(\n fieldOrder: readonly string[],\n invalidFieldIds: ReadonlySet<string> | readonly string[],\n): string | null {\n validateFormFieldOrder(fieldOrder);\n const invalid =\n invalidFieldIds instanceof Set ? invalidFieldIds : new Set(invalidFieldIds);\n for (const fieldId of fieldOrder) {\n if (invalid.has(fieldId)) return fieldId;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Visual recipe — inter-field rhythm only\n// ---------------------------------------------------------------------------\n\nexport type FormDensity = \"compact\" | \"comfortable\";\n\nexport const formDefaults = {\n density: \"comfortable\",\n} as const satisfies Readonly<{ density: FormDensity }>;\n\n/**\n * Anatomy is fixed, not a configurable prop: fields, then the form-level\n * error, then actions. `field` frame styling (background, border, its own\n * label/hint/error gap) belongs to `fieldRecipe`; this recipe only sets the\n * gap *between* one field and the next. `formError` renders as the existing\n * Notice `tone=\"danger\"` contract — Form fixes only its slot position, it\n * does not redeclare Notice's colors. `actions` has no dedicated layout here;\n * composing Button/Stack in that slot is the product's job, same reasoning\n * the roadmap already applies to Stack.\n */\nexport const formRecipe = {\n slots: [\"root\", \"field\", \"formError\", \"actions\"] as const,\n defaults: formDefaults,\n density: {\n compact: { fieldGap: spacing.sm },\n comfortable: { fieldGap: spacing.lg },\n },\n formError: {\n position: \"beforeActions\",\n gap: spacing.sm,\n },\n} as const satisfies Readonly<{\n slots: readonly [\"root\", \"field\", \"formError\", \"actions\"];\n defaults: Readonly<{ density: FormDensity }>;\n density: Record<FormDensity, Readonly<{ fieldGap: number }>>;\n formError: Readonly<{ position: \"beforeActions\"; gap: number }>;\n}>;\n\n// ---------------------------------------------------------------------------\n// Behavior — source for the shared behaviorRegistry wiring\n// ---------------------------------------------------------------------------\n\nexport const formBehaviorDefaults = {\n /** Reuses Toast's own `normal | high` vocabulary: a blocked submission is `high`/assertive. */\n errorAnnouncementPriority: \"high\",\n} as const satisfies Readonly<{ errorAnnouncementPriority: \"normal\" | \"high\" }>;\n\n/**\n * Kept beside the Form contract as the source value wired by\n * `behaviorRegistry.form`; this lets the registry reuse the contract without\n * redeclaring Form's inputs, events, states, or scenarios.\n */\nexport const formBehaviorSpec = {\n /*\n 값은 제품이 소유하므로 controlled prop이 없다. 대신 renderer가 반드시 배선해야 하는\n 것을 `inputs`/`events`로 선언한다 — 공유 불변식이 \"controlled + inputs + events가\n 하나도 없는 behavior는 renderer 테스트가 구동할 대상이 없다\"고 요구하고, 그 지적이\n 옳다. 같은 형태(컨트롤러가 상태를 소유하고 renderer가 구동)인 `loadMore`가 이미\n `controlled: []` + `inputs` + `events`로 선언한 선례를 따른다.\n */\n controlled: [],\n inputs: [\"status\", \"formError\", \"fieldOrder\"],\n events: [\"onSubmit\"],\n defaults: formBehaviorDefaults,\n stateAxes: {\n availability: [\"enabled\", \"busy\"],\n content: [\"idle\", \"loading\", \"error\"],\n },\n web: { roles: [\"alert\"], keyboard: [\"Tab\", \"Enter\"], focus: \"native\" },\n native: { roles: [\"alert\"], states: [\"busy\"], actions: [\"submit\"] },\n scenarios: [\n \"submitting-blocks-every-concurrent-submit-attempt\",\n \"each-submit-attempt-settles-its-result-exactly-once\",\n \"failed-submit-returns-to-a-resting-state-that-still-allows-retry\",\n \"succeeded-submit-remains-resting-and-allows-resubmission\",\n \"dispose-during-submit-settles-the-pending-attempt-as-interrupted\",\n \"disposed-session-rejects-further-submit-attempts\",\n \"form-level-error-never-carries-a-per-field-message\",\n \"first-invalid-field-in-render-order-receives-focus-on-submit-failure\",\n \"form-level-error-is-announced-when-no-field-target-exists\",\n ],\n} as const;\n"]}
|
|
@@ -150,6 +150,8 @@ export declare function resolveSearchEmptyCause(count: number | null, appliedFil
|
|
|
150
150
|
export declare function resolveFocusAfterRemoval(removedIndex: number, remaining: number): number;
|
|
151
151
|
/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */
|
|
152
152
|
export declare const searchScreenRecipe: {
|
|
153
|
+
/** Debounced typing delay; explicit submit remains a separate host action. */
|
|
154
|
+
readonly debounceMs: 300;
|
|
153
155
|
/** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */
|
|
154
156
|
readonly recentVisible: 5;
|
|
155
157
|
/** Suggestions under the "search for ‘q’" row. */
|
|
@@ -163,6 +165,8 @@ export declare const searchScreenRecipe: {
|
|
|
163
165
|
/** Between chips in applied filters and suggested queries. */
|
|
164
166
|
readonly chipGap: 8;
|
|
165
167
|
};
|
|
168
|
+
/** Keep finite negative delays at zero; reject values whose timer behavior varies by host. */
|
|
169
|
+
export declare function resolveSearchDebounceMs(value?: number): number;
|
|
166
170
|
/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */
|
|
167
171
|
export declare function timestampRevealOffset(dx: number, dy: number): number;
|
|
168
172
|
//# sourceMappingURL=screen-patterns.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"AAEA,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GAC3B,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1G,wBAAgB,yBAAyB,CAAC,KAAK,GAAE,kBAAsC;;;;;EAatF;AAGD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;CAmBtB,CAAC;AAEX,0GAA0G;AAC1G,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,UAAU,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC,CAAC;AACH,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,IAAI,GAAE,SAAS,GAAG,MAAkB,GAAG,OAAO,CAEnL;AACD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAe,EAAE,eAAmB,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAGtI;AAED,gHAAgH;AAChH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0IAA0I;IAC1I,qBAAqB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3C,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AACrF,wBAAgB,uBAAuB,CAAC,MAAM,EAAC,sBAAsB,8CAEpE;AACD,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM;;;EAG9D;AAED,qGAAqG;AACrG,wBAAgB,qBAAqB,CAAC,KAAK,EAAC,SAAS;IAAC,EAAE,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,GAAC,IAAI,CAAA;CAAC,EAAE,QAGtF;AAED,0FAA0F;AAC1F,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,CAAC,qEAAqE;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAClL,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,SAAS,2BAA2B,EAAE,GAAG,IAAI,CAMpG;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAC,MAAM,CAAC;IAAC,OAAO,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzG,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAClG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5I,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAC,SAAS,CAAC,EAAE,EAAC,WAAW,EAAC,SAAS,eAAe,EAAE,EAAC,YAAY,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS,EAAC,cAAc,EAAC,MAAM,GAAC,IAAI,GAAC,SAAS;;;YAJ3I,MAAM;eAAS,MAAM;iBAAW,SAAS,MAAM,EAAE;;;;EAa7F;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC9D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAKlG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAC5D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,kBAAkB,EAAE,MAAM,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"screen-patterns.d.ts","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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"}
|
package/dist/screen-patterns.js
CHANGED
|
@@ -117,11 +117,12 @@ export function resolveSearchCommit(value) {
|
|
|
117
117
|
return next ? next : null;
|
|
118
118
|
}
|
|
119
119
|
export function resolveSearchEmptyCause(count, appliedFilterCount) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (count < 0 || !Number.isSafeInteger(count) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {
|
|
120
|
+
// Loading/nonempty results must not bypass the same count contract used by empty results.
|
|
121
|
+
if ((count !== null && (count < 0 || !Number.isSafeInteger(count))) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {
|
|
123
122
|
throw new RangeError("Search counts must be nonnegative integers");
|
|
124
123
|
}
|
|
124
|
+
if (count === null || count > 0)
|
|
125
|
+
return "none";
|
|
125
126
|
return appliedFilterCount > 0 ? "filters" : "query";
|
|
126
127
|
}
|
|
127
128
|
/**
|
|
@@ -137,6 +138,8 @@ export function resolveFocusAfterRemoval(removedIndex, remaining) {
|
|
|
137
138
|
}
|
|
138
139
|
/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */
|
|
139
140
|
export const searchScreenRecipe = {
|
|
141
|
+
/** Debounced typing delay; explicit submit remains a separate host action. */
|
|
142
|
+
debounceMs: 300,
|
|
140
143
|
/** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */
|
|
141
144
|
recentVisible: 5,
|
|
142
145
|
/** Suggestions under the "search for ‘q’" row. */
|
|
@@ -150,6 +153,12 @@ export const searchScreenRecipe = {
|
|
|
150
153
|
/** Between chips in applied filters and suggested queries. */
|
|
151
154
|
chipGap: spacing.xs,
|
|
152
155
|
};
|
|
156
|
+
/** Keep finite negative delays at zero; reject values whose timer behavior varies by host. */
|
|
157
|
+
export function resolveSearchDebounceMs(value = searchScreenRecipe.debounceMs) {
|
|
158
|
+
if (!Number.isFinite(value))
|
|
159
|
+
throw new RangeError("Search debounceMs must be finite");
|
|
160
|
+
return Math.max(0, value);
|
|
161
|
+
}
|
|
153
162
|
/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */
|
|
154
163
|
export function timestampRevealOffset(dx, dy) {
|
|
155
164
|
// A 12px dead zone and 2:1 direction bias avoid turning taps/list scrolling into a time gesture.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-patterns.js","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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","sourcesContent":["import { layout, spacing } from \"./foundations.js\";\n\n/** Initial load replaces the body; refresh/save errors belong beside retained content. */\nexport type ScreenContentState =\n | Readonly<{ kind: \"ready\" }>\n | Readonly<{ kind: \"loading\" | \"empty\" | \"error\" | \"restricted\"; title: string; description?: string }>;\n\nexport function resolveScreenContentState(state: ScreenContentState = { kind: \"ready\" }) {\n if (![\"ready\", \"loading\", \"empty\", \"error\", \"restricted\"].includes(state.kind)) {\n throw new TypeError(\"Unknown screen content state\");\n }\n if (state.kind !== \"ready\" && !state.title.trim()) {\n throw new TypeError(\"Screen state requires a localized title\");\n }\n return {\n kind: state.kind,\n replacesContent: state.kind !== \"ready\",\n busy: state.kind === \"loading\",\n announcement: state.kind === \"error\" ? \"assertive\" : \"polite\",\n } as const;\n}\n\n// Reuse reading-width and spacing tokens; do not derive geometry from a product's device or artwork.\nexport const screenPatternRecipe = {\n maxWidth: layout.readingMaxWidth,\n padding: spacing.md,\n sectionGap: spacing.xl,\n itemGap: spacing.sm,\n stateGap: spacing.md,\n // Compact screens must keep back/title/save on one row; scale this floor for large text instead of reserving a desktop-width title.\n headerMinWidth: layout.readingMaxWidth / 6,\n // Reserve the opposite edge for authorship and cap the composer so history remains visible.\n messageMaxWidth: \"84%\",\n // Social thumbnails stay compact; selection order and removal replace a second toolbar.\n attachmentSize: 80,\n attachmentRemoveSize: 24,\n reactionHoldMs: 450,\n reactionMoveTolerance: 10,\n reactionMenuWidth: 320,\n replySwipeDistance: 60,\n composerMinLines: 1,\n composerMaxLines: 5,\n} as const;\n\n/** Controlled composer: HJM never clears drafts or interprets a fulfilled callback as server delivery. */\nexport type MessageComposerDescriptor = Readonly<{\n value: string;\n label: string;\n sendLabel: string;\n /** Short visual copy may differ from the complete accessible input name. */\n placeholder?: string;\n description?: string;\n error?: string;\n invalid?: boolean;\n /** Newline remains the compatible default; desktop chat may explicitly opt into send. */\n submitMode?: \"newline\" | \"send\";\n disabled?: boolean;\n pending?: boolean;\n attachmentCount?: number;\n}>;\n/** Safari may report the final IME Enter as keyCode 229 after isComposing becomes false. */\nexport function shouldSubmitMessageKey(event: Readonly<{ key: string; shiftKey?: boolean; isComposing?: boolean; keyCode?: number }>, mode: \"newline\" | \"send\" = \"newline\"): boolean {\n return mode === \"send\" && event.key === \"Enter\" && !event.shiftKey && !event.isComposing && event.keyCode !== 229;\n}\nexport function canSubmitMessage({ value, disabled = false, pending = false, attachmentCount = 0 }: MessageComposerDescriptor): boolean {\n if (!Number.isSafeInteger(attachmentCount) || attachmentCount < 0) throw new RangeError(\"Attachment count must be a nonnegative integer\");\n return !disabled && !pending && (value.trim().length > 0 || attachmentCount > 0);\n}\n\n/** Bubble alignment expresses authorship, not delivery; delivery text always comes from the product receipt. */\nexport type ChatMessageDescriptor = Readonly<{\n direction: \"incoming\" | \"outgoing\";\n author: string;\n timestamp: string;\n deliveryLabel?: string;\n /** Inward drag reveals time (incoming right, outgoing left) while delivery state remains visible; default preserves existing captions. */\n timestampPresentation?: \"always\" | \"swipe\";\n /** Connected authorship tip; opt-in keeps existing card-like conversations unchanged. */\n bubbleTail?: boolean;\n}>;\n\n/** A denied permission goes to settings; rendering never requests OS access automatically. */\nexport type PermissionScreenStatus = \"prompt\" | \"denied\" | \"granted\" | \"unavailable\";\nexport function resolvePermissionAction(status:PermissionScreenStatus) {\n switch(status){case \"prompt\":return \"request\";case \"denied\":return \"settings\";case \"granted\":return \"continue\";case \"unavailable\":return null;default:throw new TypeError(\"Unknown permission state\");}\n}\nexport function resolveOnboardingStep(total:number,index:number){\n if(!Number.isInteger(total)||total<1||!Number.isInteger(index)||index<0||index>=total)throw new RangeError(\"Onboarding requires a valid step index\");\n return {first:index===0,last:index===total-1};\n}\n\n/** Replies are one level deep; reject orphaned/duplicate records instead of silently losing them. */\nexport function validateCommentThread(items:readonly {id:string;parentId:string|null}[]){\n const roots=new Set(items.filter(item=>item.parentId===null).map(item=>item.id));const ids=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id))throw new TypeError(\"Comment ids must be unique and nonempty\");ids.add(item.id);if(item.parentId!==null&&(!roots.has(item.parentId)||item.parentId===item.id))throw new TypeError(\"Replies require an existing root comment\");}\n}\n\n/** Source bytes, permission requests, ordering and upload limits remain product-owned. */\nexport type MessageAttachmentDescriptor = Readonly<{ id: string; removeLabel: string; /** Local preparation can lock removal without locking text entry. */ disabled?: boolean }>;\nexport function validateMessageAttachments(attachments: readonly MessageAttachmentDescriptor[]): void {\n const ids = new Set<string>();\n for (const item of attachments) {\n if (!item.id.trim() || ids.has(item.id) || !item.removeLabel.trim()) throw new TypeError(\"Attachments require unique ids and localized removal labels\");\n ids.add(item.id);\n }\n}\n\n/** Require horizontal intent so a timeline scroll cannot accidentally start a reply. */\nexport function isReplySwipe(dx: number, dy: number): boolean {\n return Math.abs(dx) >= screenPatternRecipe.replySwipeDistance && Math.abs(dx) > Math.abs(dy) * 2;\n}\n\n/** The UI never infers permission or camera availability; product adapters own both. */\nexport type PhotoSource = \"library\" | \"camera\";\nexport type PhotoSourceLabels = Readonly<{ title:string; library:string; camera:string; cancel:string }>;\n\nexport type SavedItem = Readonly<{ id: string; title: string }>;\nexport type SavedCollection = Readonly<{ id: string; title: string; itemIds: readonly string[] }>;\nexport type SavedItemsLabels = Readonly<{ allItems: string; privateNotice: string; back: string; empty: string; createCollection: string }>;\n\n/** Unsave removes an item from every collection view without rewriting product-owned membership. */\nexport function resolveSavedItems<T extends SavedItem>(items:readonly T[],collections:readonly SavedCollection[],collectionId:string|null|undefined,selectedItemId:string|null|undefined){\n const ids=new Set<string>(),groups=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id)||!item.title.trim())throw new TypeError(\"Saved items require unique ids and nonempty titles\");ids.add(item.id);}\n for(const group of collections){if(!group.id.trim()||groups.has(group.id)||!group.title.trim())throw new TypeError(\"Saved collections require unique ids and nonempty titles\");groups.add(group.id);}\n const collection=collections.find(item=>item.id===collectionId);\n // A deleted collection returns to the overview instead of showing a mislabeled all-posts view.\n const home=collectionId===undefined||(collectionId!==null&&!collection);\n const visible=home||collectionId===null?items:items.filter(item=>collection?.itemIds.includes(item.id));\n return {home,collection,visible,selected:home?undefined:visible.find(item=>item.id===selectedItemId)};\n}\n\n/**\n * SearchScreen phases (2026-10-06 search redesign, docs/screen-patterns.md#searchscreen-두-단계-검색).\n * Without a committed query (one-step search, the pre-1.13 behavior) any nonblank query is already\n * `results`. With one, typing a different query is `typing`: suggestions replace the results until the\n * user commits (Enter, a suggestion, a recent or suggested query). Comparing trimmed values keeps a\n * trailing space from flipping committed results back into suggestions.\n */\nexport type SearchScreenPhase = \"idle\" | \"typing\" | \"results\";\nexport function resolveSearchScreenPhase(query: string, committedQuery?: string): SearchScreenPhase {\n const typed = query.trim();\n if (!typed) return \"idle\";\n if (committedQuery === undefined) return \"results\";\n return typed === committedQuery.trim() ? \"results\" : \"typing\";\n}\n\n/**\n * The value a commit records, or `null` when there is nothing to commit. Blank commits are dropped\n * so a stray Enter never writes an empty recent search or fetches \"everything\".\n */\nexport function resolveSearchCommit(value: string): string | null {\n const next = value.trim();\n return next ? next : null;\n}\n\n/**\n * Why a committed search shows nothing. `filters` keeps the filter rail and offers \"clear all\";\n * `query` hides the rail (no filter can fix it) and offers suggested queries instead. Reference\n * services hide the rail on every zero result, which removes the only way to undo a filter.\n * `null` count means the result is still loading, so no cause is reported yet.\n */\nexport type SearchEmptyCause = \"none\" | \"filters\" | \"query\";\nexport function resolveSearchEmptyCause(count: number | null, appliedFilterCount: number): SearchEmptyCause {\n if (count === null || count > 0) return \"none\";\n if (count < 0 || !Number.isSafeInteger(count) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {\n throw new RangeError(\"Search counts must be nonnegative integers\");\n }\n return appliedFilterCount > 0 ? \"filters\" : \"query\";\n}\n\n/**\n * Index to focus after removing item `removedIndex` from a list that now has `remaining` items:\n * the item that slid into its place, else the new last item, else -1 (move to the fallback target).\n * Removing a focused chip or row would otherwise drop focus to <body>.\n */\nexport function resolveFocusAfterRemoval(removedIndex: number, remaining: number): number {\n if (!Number.isInteger(removedIndex) || removedIndex < 0 || !Number.isInteger(remaining) || remaining < 0) {\n throw new RangeError(\"Removal indexes must be nonnegative integers\");\n }\n return remaining === 0 ? -1 : Math.min(removedIndex, remaining - 1);\n}\n\n/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */\nexport const searchScreenRecipe = {\n /** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */\n recentVisible: 5,\n /** Suggestions under the \"search for ‘q’\" row. */\n suggestionVisible: 6,\n /** Skeleton rows while the first page of results loads. */\n loadingRows: 4,\n /** Between idle sections (recent, suggested). */\n sectionGap: spacing.xl,\n /** Section title to its list, and results header to applied filters. */\n headerGap: spacing.sm,\n /** Between chips in applied filters and suggested queries. */\n chipGap: spacing.xs,\n} as const;\n\n/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */\nexport function timestampRevealOffset(dx: number, dy: number): number {\n // A 12px dead zone and 2:1 direction bias avoid turning taps/list scrolling into a time gesture.\n return dx > 12 && dx > Math.abs(dy) * 2 ? Math.min(dx, 88) : 0;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"screen-patterns.js","sourceRoot":"","sources":["../src/screen-patterns.ts"],"names":[],"mappings":"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","sourcesContent":["import { layout, spacing } from \"./foundations.js\";\n\n/** Initial load replaces the body; refresh/save errors belong beside retained content. */\nexport type ScreenContentState =\n | Readonly<{ kind: \"ready\" }>\n | Readonly<{ kind: \"loading\" | \"empty\" | \"error\" | \"restricted\"; title: string; description?: string }>;\n\nexport function resolveScreenContentState(state: ScreenContentState = { kind: \"ready\" }) {\n if (![\"ready\", \"loading\", \"empty\", \"error\", \"restricted\"].includes(state.kind)) {\n throw new TypeError(\"Unknown screen content state\");\n }\n if (state.kind !== \"ready\" && !state.title.trim()) {\n throw new TypeError(\"Screen state requires a localized title\");\n }\n return {\n kind: state.kind,\n replacesContent: state.kind !== \"ready\",\n busy: state.kind === \"loading\",\n announcement: state.kind === \"error\" ? \"assertive\" : \"polite\",\n } as const;\n}\n\n// Reuse reading-width and spacing tokens; do not derive geometry from a product's device or artwork.\nexport const screenPatternRecipe = {\n maxWidth: layout.readingMaxWidth,\n padding: spacing.md,\n sectionGap: spacing.xl,\n itemGap: spacing.sm,\n stateGap: spacing.md,\n // Compact screens must keep back/title/save on one row; scale this floor for large text instead of reserving a desktop-width title.\n headerMinWidth: layout.readingMaxWidth / 6,\n // Reserve the opposite edge for authorship and cap the composer so history remains visible.\n messageMaxWidth: \"84%\",\n // Social thumbnails stay compact; selection order and removal replace a second toolbar.\n attachmentSize: 80,\n attachmentRemoveSize: 24,\n reactionHoldMs: 450,\n reactionMoveTolerance: 10,\n reactionMenuWidth: 320,\n replySwipeDistance: 60,\n composerMinLines: 1,\n composerMaxLines: 5,\n} as const;\n\n/** Controlled composer: HJM never clears drafts or interprets a fulfilled callback as server delivery. */\nexport type MessageComposerDescriptor = Readonly<{\n value: string;\n label: string;\n sendLabel: string;\n /** Short visual copy may differ from the complete accessible input name. */\n placeholder?: string;\n description?: string;\n error?: string;\n invalid?: boolean;\n /** Newline remains the compatible default; desktop chat may explicitly opt into send. */\n submitMode?: \"newline\" | \"send\";\n disabled?: boolean;\n pending?: boolean;\n attachmentCount?: number;\n}>;\n/** Safari may report the final IME Enter as keyCode 229 after isComposing becomes false. */\nexport function shouldSubmitMessageKey(event: Readonly<{ key: string; shiftKey?: boolean; isComposing?: boolean; keyCode?: number }>, mode: \"newline\" | \"send\" = \"newline\"): boolean {\n return mode === \"send\" && event.key === \"Enter\" && !event.shiftKey && !event.isComposing && event.keyCode !== 229;\n}\nexport function canSubmitMessage({ value, disabled = false, pending = false, attachmentCount = 0 }: MessageComposerDescriptor): boolean {\n if (!Number.isSafeInteger(attachmentCount) || attachmentCount < 0) throw new RangeError(\"Attachment count must be a nonnegative integer\");\n return !disabled && !pending && (value.trim().length > 0 || attachmentCount > 0);\n}\n\n/** Bubble alignment expresses authorship, not delivery; delivery text always comes from the product receipt. */\nexport type ChatMessageDescriptor = Readonly<{\n direction: \"incoming\" | \"outgoing\";\n author: string;\n timestamp: string;\n deliveryLabel?: string;\n /** Inward drag reveals time (incoming right, outgoing left) while delivery state remains visible; default preserves existing captions. */\n timestampPresentation?: \"always\" | \"swipe\";\n /** Connected authorship tip; opt-in keeps existing card-like conversations unchanged. */\n bubbleTail?: boolean;\n}>;\n\n/** A denied permission goes to settings; rendering never requests OS access automatically. */\nexport type PermissionScreenStatus = \"prompt\" | \"denied\" | \"granted\" | \"unavailable\";\nexport function resolvePermissionAction(status:PermissionScreenStatus) {\n switch(status){case \"prompt\":return \"request\";case \"denied\":return \"settings\";case \"granted\":return \"continue\";case \"unavailable\":return null;default:throw new TypeError(\"Unknown permission state\");}\n}\nexport function resolveOnboardingStep(total:number,index:number){\n if(!Number.isInteger(total)||total<1||!Number.isInteger(index)||index<0||index>=total)throw new RangeError(\"Onboarding requires a valid step index\");\n return {first:index===0,last:index===total-1};\n}\n\n/** Replies are one level deep; reject orphaned/duplicate records instead of silently losing them. */\nexport function validateCommentThread(items:readonly {id:string;parentId:string|null}[]){\n const roots=new Set(items.filter(item=>item.parentId===null).map(item=>item.id));const ids=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id))throw new TypeError(\"Comment ids must be unique and nonempty\");ids.add(item.id);if(item.parentId!==null&&(!roots.has(item.parentId)||item.parentId===item.id))throw new TypeError(\"Replies require an existing root comment\");}\n}\n\n/** Source bytes, permission requests, ordering and upload limits remain product-owned. */\nexport type MessageAttachmentDescriptor = Readonly<{ id: string; removeLabel: string; /** Local preparation can lock removal without locking text entry. */ disabled?: boolean }>;\nexport function validateMessageAttachments(attachments: readonly MessageAttachmentDescriptor[]): void {\n const ids = new Set<string>();\n for (const item of attachments) {\n if (!item.id.trim() || ids.has(item.id) || !item.removeLabel.trim()) throw new TypeError(\"Attachments require unique ids and localized removal labels\");\n ids.add(item.id);\n }\n}\n\n/** Require horizontal intent so a timeline scroll cannot accidentally start a reply. */\nexport function isReplySwipe(dx: number, dy: number): boolean {\n return Math.abs(dx) >= screenPatternRecipe.replySwipeDistance && Math.abs(dx) > Math.abs(dy) * 2;\n}\n\n/** The UI never infers permission or camera availability; product adapters own both. */\nexport type PhotoSource = \"library\" | \"camera\";\nexport type PhotoSourceLabels = Readonly<{ title:string; library:string; camera:string; cancel:string }>;\n\nexport type SavedItem = Readonly<{ id: string; title: string }>;\nexport type SavedCollection = Readonly<{ id: string; title: string; itemIds: readonly string[] }>;\nexport type SavedItemsLabels = Readonly<{ allItems: string; privateNotice: string; back: string; empty: string; createCollection: string }>;\n\n/** Unsave removes an item from every collection view without rewriting product-owned membership. */\nexport function resolveSavedItems<T extends SavedItem>(items:readonly T[],collections:readonly SavedCollection[],collectionId:string|null|undefined,selectedItemId:string|null|undefined){\n const ids=new Set<string>(),groups=new Set<string>();\n for(const item of items){if(!item.id.trim()||ids.has(item.id)||!item.title.trim())throw new TypeError(\"Saved items require unique ids and nonempty titles\");ids.add(item.id);}\n for(const group of collections){if(!group.id.trim()||groups.has(group.id)||!group.title.trim())throw new TypeError(\"Saved collections require unique ids and nonempty titles\");groups.add(group.id);}\n const collection=collections.find(item=>item.id===collectionId);\n // A deleted collection returns to the overview instead of showing a mislabeled all-posts view.\n const home=collectionId===undefined||(collectionId!==null&&!collection);\n const visible=home||collectionId===null?items:items.filter(item=>collection?.itemIds.includes(item.id));\n return {home,collection,visible,selected:home?undefined:visible.find(item=>item.id===selectedItemId)};\n}\n\n/**\n * SearchScreen phases (2026-10-06 search redesign, docs/screen-patterns.md#searchscreen-두-단계-검색).\n * Without a committed query (one-step search, the pre-1.13 behavior) any nonblank query is already\n * `results`. With one, typing a different query is `typing`: suggestions replace the results until the\n * user commits (Enter, a suggestion, a recent or suggested query). Comparing trimmed values keeps a\n * trailing space from flipping committed results back into suggestions.\n */\nexport type SearchScreenPhase = \"idle\" | \"typing\" | \"results\";\nexport function resolveSearchScreenPhase(query: string, committedQuery?: string): SearchScreenPhase {\n const typed = query.trim();\n if (!typed) return \"idle\";\n if (committedQuery === undefined) return \"results\";\n return typed === committedQuery.trim() ? \"results\" : \"typing\";\n}\n\n/**\n * The value a commit records, or `null` when there is nothing to commit. Blank commits are dropped\n * so a stray Enter never writes an empty recent search or fetches \"everything\".\n */\nexport function resolveSearchCommit(value: string): string | null {\n const next = value.trim();\n return next ? next : null;\n}\n\n/**\n * Why a committed search shows nothing. `filters` keeps the filter rail and offers \"clear all\";\n * `query` hides the rail (no filter can fix it) and offers suggested queries instead. Reference\n * services hide the rail on every zero result, which removes the only way to undo a filter.\n * `null` count means the result is still loading, so no cause is reported yet.\n */\nexport type SearchEmptyCause = \"none\" | \"filters\" | \"query\";\nexport function resolveSearchEmptyCause(count: number | null, appliedFilterCount: number): SearchEmptyCause {\n // Loading/nonempty results must not bypass the same count contract used by empty results.\n if ((count !== null && (count < 0 || !Number.isSafeInteger(count))) || !Number.isSafeInteger(appliedFilterCount) || appliedFilterCount < 0) {\n throw new RangeError(\"Search counts must be nonnegative integers\");\n }\n if (count === null || count > 0) return \"none\";\n return appliedFilterCount > 0 ? \"filters\" : \"query\";\n}\n\n/**\n * Index to focus after removing item `removedIndex` from a list that now has `remaining` items:\n * the item that slid into its place, else the new last item, else -1 (move to the fallback target).\n * Removing a focused chip or row would otherwise drop focus to <body>.\n */\nexport function resolveFocusAfterRemoval(removedIndex: number, remaining: number): number {\n if (!Number.isInteger(removedIndex) || removedIndex < 0 || !Number.isInteger(remaining) || remaining < 0) {\n throw new RangeError(\"Removal indexes must be nonnegative integers\");\n }\n return remaining === 0 ? -1 : Math.min(removedIndex, remaining - 1);\n}\n\n/** Recipe for the SearchScreen phase sections; geometry stays on shared spacing tokens. */\nexport const searchScreenRecipe = {\n /** Debounced typing delay; explicit submit remains a separate host action. */\n debounceMs: 300,\n /** Recent searches shown before the user types; reference services show 5–10 and let the rest scroll away. */\n recentVisible: 5,\n /** Suggestions under the \"search for ‘q’\" row. */\n suggestionVisible: 6,\n /** Skeleton rows while the first page of results loads. */\n loadingRows: 4,\n /** Between idle sections (recent, suggested). */\n sectionGap: spacing.xl,\n /** Section title to its list, and results header to applied filters. */\n headerGap: spacing.sm,\n /** Between chips in applied filters and suggested queries. */\n chipGap: spacing.xs,\n} as const;\n\n/** Keep finite negative delays at zero; reject values whose timer behavior varies by host. */\nexport function resolveSearchDebounceMs(value: number = searchScreenRecipe.debounceMs): number {\n if (!Number.isFinite(value)) throw new RangeError(\"Search debounceMs must be finite\");\n return Math.max(0, value);\n}\n\n/** Horizontal intent preserves vertical scrolling and uses one reveal distance across renderers. */\nexport function timestampRevealOffset(dx: number, dy: number): number {\n // A 12px dead zone and 2:1 direction bias avoid turning taps/list scrolling into a time gesture.\n return dx > 12 && dx > Math.abs(dy) * 2 ? Math.min(dx, 88) : 0;\n}\n"]}
|
package/dist/toast.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type ToastId = string;
|
|
|
5
5
|
export type ToastPresentation = "standard" | "liquid";
|
|
6
6
|
export type ToastAnnouncementPriority = "normal" | "high";
|
|
7
7
|
export type ToastPauseReason = "pointer" | "focus" | "window" | "gesture" | "programmatic" | "presentation" | "occlusion";
|
|
8
|
-
export type ToastDismissReason = "timeout" | "action" | "close-action" | "escape" | "swipe" | "programmatic" | "queue-overflow" | "interrupted";
|
|
8
|
+
export type ToastDismissReason = "timeout" | "action" | "press" | "close-action" | "escape" | "swipe" | "programmatic" | "queue-overflow" | "interrupted";
|
|
9
9
|
export type ToastDuplicatePolicy = "update" | "ignore";
|
|
10
10
|
export type ToastTimerUpdatePolicy = "preserve" | "restart";
|
|
11
11
|
export type ToastOverflowPolicy = "discard-oldest" | "discard-newest";
|
|
@@ -47,6 +47,11 @@ export type ToastDescriptor = Readonly<{
|
|
|
47
47
|
/** `null` persists; short durations clamp to the configured floor. */
|
|
48
48
|
durationMs?: number | null;
|
|
49
49
|
action?: ToastActionDescriptor;
|
|
50
|
+
/** Whole-card activation; sibling action/close controls remain independent. */
|
|
51
|
+
press?: Readonly<{
|
|
52
|
+
accessibilityLabel: string;
|
|
53
|
+
onPress(): void;
|
|
54
|
+
}>;
|
|
50
55
|
/** Decorative leading visual that replaces the tone badge (ReactNode on Web/Native). */
|
|
51
56
|
media?: unknown;
|
|
52
57
|
/** Step progress of a running task, announced with the current step label. */
|
|
@@ -71,6 +76,7 @@ export type ResolvedToastDescriptor = Readonly<{
|
|
|
71
76
|
announcement: string;
|
|
72
77
|
durationMs: number | null;
|
|
73
78
|
action: ResolvedToastActionDescriptor | null;
|
|
79
|
+
press: NonNullable<ToastDescriptor["press"]> | null;
|
|
74
80
|
media: unknown;
|
|
75
81
|
progress: ToastProgressDescriptor | null;
|
|
76
82
|
closeLabel: string;
|
|
@@ -111,6 +117,7 @@ export type ToastSessionSnapshot = Readonly<{
|
|
|
111
117
|
descriptor: ResolvedToastDescriptor;
|
|
112
118
|
timer: ToastTimerSnapshot;
|
|
113
119
|
actionInvoked: boolean;
|
|
120
|
+
pressInvoked: boolean;
|
|
114
121
|
dismissReason: ToastDismissReason | null;
|
|
115
122
|
}>;
|
|
116
123
|
export type ToastSession = Readonly<{
|
|
@@ -124,6 +131,7 @@ export type ToastSession = Readonly<{
|
|
|
124
131
|
/** Advances a renderer-owned clock; no global timer or Date dependency exists. */
|
|
125
132
|
advanceTime(elapsedMs: number): boolean;
|
|
126
133
|
invokeAction(): boolean;
|
|
134
|
+
invokePress(): boolean;
|
|
127
135
|
dismiss(reason: ToastDismissReason): boolean;
|
|
128
136
|
/** Renderers call this once exit motion or immediate reduced-motion exit finishes. */
|
|
129
137
|
completeExit(): boolean;
|
|
@@ -168,6 +176,7 @@ export type ToastStore = Readonly<{
|
|
|
168
176
|
subscribe(listener: () => void): () => void;
|
|
169
177
|
publish(descriptor: ToastDescriptor, options?: ToastPublishOptions): ToastPublishResult;
|
|
170
178
|
invokeAction(id: ToastId): boolean;
|
|
179
|
+
invokePress(id: ToastId): boolean;
|
|
171
180
|
dismiss(id: ToastId, reason: ToastDismissReason): boolean;
|
|
172
181
|
/** Stable-id programmatic close shortcut. */
|
|
173
182
|
close(id: ToastId): boolean;
|
package/dist/toast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../src/toast.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../src/toast.ts"],"names":[],"mappings":"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"}
|
package/dist/toast.js
CHANGED
|
@@ -63,6 +63,12 @@ export function validateToastDescriptor(descriptor) {
|
|
|
63
63
|
throw new RangeError("Toast progress current must index an existing step");
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
if (descriptor.press) {
|
|
67
|
+
assertCopy(descriptor.press.accessibilityLabel, "press.accessibilityLabel");
|
|
68
|
+
if (typeof descriptor.press.onPress !== "function") {
|
|
69
|
+
throw new TypeError("Toast press.onPress must be a function");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
66
72
|
if (descriptor.action) {
|
|
67
73
|
assertCopy(descriptor.action.label, "action.label");
|
|
68
74
|
if (descriptor.action.accessibilityLabel !== undefined) {
|
|
@@ -76,7 +82,7 @@ export function resolveToastDuration(descriptor) {
|
|
|
76
82
|
if (descriptor.durationMs === null)
|
|
77
83
|
return null;
|
|
78
84
|
if (descriptor.durationMs === undefined) {
|
|
79
|
-
return descriptor.action ? null : toastBehaviorDefaults.durationMs;
|
|
85
|
+
return descriptor.action || descriptor.press ? null : toastBehaviorDefaults.durationMs;
|
|
80
86
|
}
|
|
81
87
|
return Math.max(descriptor.durationMs, toastBehaviorDefaults.minimumDurationMs);
|
|
82
88
|
}
|
|
@@ -105,6 +111,7 @@ export function resolveToastDescriptor(descriptor) {
|
|
|
105
111
|
announcement,
|
|
106
112
|
durationMs: resolveToastDuration(descriptor),
|
|
107
113
|
action,
|
|
114
|
+
press: descriptor.press ?? null,
|
|
108
115
|
media: descriptor.media ?? null,
|
|
109
116
|
progress: descriptor.progress ?? null,
|
|
110
117
|
closeLabel: descriptor.closeLabel,
|
|
@@ -127,6 +134,8 @@ export function createToastSession(descriptor) {
|
|
|
127
134
|
let revision = 0;
|
|
128
135
|
let elapsedMs = 0;
|
|
129
136
|
let actionInvoked = false;
|
|
137
|
+
let pressInvoked = false;
|
|
138
|
+
let invokingControl = false;
|
|
130
139
|
let dismissReason = null;
|
|
131
140
|
let dismissNotified = false;
|
|
132
141
|
let cachedSnapshot = null;
|
|
@@ -168,6 +177,7 @@ export function createToastSession(descriptor) {
|
|
|
168
177
|
descriptor: resolved,
|
|
169
178
|
timer: timerSnapshot(),
|
|
170
179
|
actionInvoked,
|
|
180
|
+
pressInvoked,
|
|
171
181
|
dismissReason,
|
|
172
182
|
};
|
|
173
183
|
return cachedSnapshot;
|
|
@@ -214,6 +224,7 @@ export function createToastSession(descriptor) {
|
|
|
214
224
|
resolved = next;
|
|
215
225
|
revision += 1;
|
|
216
226
|
actionInvoked = false;
|
|
227
|
+
pressInvoked = false;
|
|
217
228
|
dismissReason = null;
|
|
218
229
|
dismissNotified = false;
|
|
219
230
|
if (phase === "queued" || timerPolicy === "restart")
|
|
@@ -259,15 +270,17 @@ export function createToastSession(descriptor) {
|
|
|
259
270
|
},
|
|
260
271
|
invokeAction() {
|
|
261
272
|
const action = resolved.action;
|
|
262
|
-
if (phase !== "visible" || action === null || actionInvoked)
|
|
273
|
+
if (phase !== "visible" || action === null || actionInvoked || invokingControl)
|
|
263
274
|
return false;
|
|
264
275
|
const invokedRevision = revision;
|
|
265
276
|
actionInvoked = true;
|
|
277
|
+
invokingControl = true;
|
|
266
278
|
notify();
|
|
267
279
|
try {
|
|
268
280
|
action.onAction();
|
|
269
281
|
}
|
|
270
282
|
finally {
|
|
283
|
+
invokingControl = false;
|
|
271
284
|
if (action.dismissOnAction &&
|
|
272
285
|
phase === "visible" &&
|
|
273
286
|
revision === invokedRevision) {
|
|
@@ -279,6 +292,30 @@ export function createToastSession(descriptor) {
|
|
|
279
292
|
}
|
|
280
293
|
return true;
|
|
281
294
|
},
|
|
295
|
+
invokePress() {
|
|
296
|
+
const press = resolved.press;
|
|
297
|
+
if (phase !== "visible" || press === null || pressInvoked || invokingControl)
|
|
298
|
+
return false;
|
|
299
|
+
const invokedRevision = revision;
|
|
300
|
+
pressInvoked = true;
|
|
301
|
+
invokingControl = true;
|
|
302
|
+
notify();
|
|
303
|
+
try {
|
|
304
|
+
press.onPress();
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
invokingControl = false;
|
|
308
|
+
// Match the reference's callback-then-dismiss ordering without closing a new
|
|
309
|
+
// revision published by navigation, or re-entering another control mid-callback.
|
|
310
|
+
if (phase === "visible" && revision === invokedRevision) {
|
|
311
|
+
phase = "closing";
|
|
312
|
+
dismissReason = "press";
|
|
313
|
+
pausedBy.clear();
|
|
314
|
+
notify();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
},
|
|
282
319
|
dismiss(reason) {
|
|
283
320
|
if (phase === "closing" || phase === "closed")
|
|
284
321
|
return false;
|
|
@@ -485,6 +522,10 @@ export function createToastStore(options = {}) {
|
|
|
485
522
|
notify();
|
|
486
523
|
return { outcome: "added", id: descriptor.id, position: "queued" };
|
|
487
524
|
},
|
|
525
|
+
invokePress(id) {
|
|
526
|
+
assertActive();
|
|
527
|
+
return sessions.get(id)?.invokePress() ?? false;
|
|
528
|
+
},
|
|
488
529
|
invokeAction(id) {
|
|
489
530
|
assertActive();
|
|
490
531
|
return sessions.get(id)?.invokeAction() ?? false;
|