@hjmds/design-contracts 1.13.1 → 1.15.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/agreement.d.ts +7 -1
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +14 -3
- package/dist/agreement.js.map +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/catalog.d.ts +4 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/content-transition.d.ts +17 -0
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +21 -0
- package/dist/content-transition.js.map +1 -1
- package/dist/date-entry.d.ts +71 -0
- package/dist/date-entry.d.ts.map +1 -0
- package/dist/date-entry.js +78 -0
- package/dist/date-entry.js.map +1 -0
- package/dist/design-profile-layout.d.ts +11 -0
- package/dist/design-profile-layout.d.ts.map +1 -0
- package/dist/design-profile-layout.js +18 -0
- package/dist/design-profile-layout.js.map +1 -0
- package/dist/design-profile.d.ts +89 -0
- package/dist/design-profile.d.ts.map +1 -0
- package/dist/design-profile.js +262 -0
- package/dist/design-profile.js.map +1 -0
- package/dist/design-system-provider.d.ts +6 -0
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +4 -2
- package/dist/design-system-provider.js.map +1 -1
- package/dist/document-resource.d.ts +104 -0
- package/dist/document-resource.d.ts.map +1 -0
- package/dist/document-resource.js +77 -0
- package/dist/document-resource.js.map +1 -0
- package/dist/effect-surface.d.ts +6 -1
- package/dist/effect-surface.d.ts.map +1 -1
- package/dist/effect-surface.js +5 -2
- package/dist/effect-surface.js.map +1 -1
- package/dist/field-group.d.ts +47 -0
- package/dist/field-group.d.ts.map +1 -0
- package/dist/field-group.js +92 -0
- package/dist/field-group.js.map +1 -0
- package/dist/gooey-navigation.d.ts +19 -1
- package/dist/gooey-navigation.d.ts.map +1 -1
- package/dist/gooey-navigation.js +37 -2
- package/dist/gooey-navigation.js.map +1 -1
- package/dist/image.d.ts +17 -0
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +19 -0
- package/dist/image.js.map +1 -1
- package/dist/internal/effect-noise.d.ts +2 -0
- package/dist/internal/effect-noise.d.ts.map +1 -0
- package/dist/internal/effect-noise.js +4 -0
- package/dist/internal/effect-noise.js.map +1 -0
- package/dist/palette-contrast.d.ts +6 -0
- package/dist/palette-contrast.d.ts.map +1 -1
- package/dist/palette-contrast.js +17 -0
- package/dist/palette-contrast.js.map +1 -1
- package/dist/progressive-blur.d.ts +32 -0
- package/dist/progressive-blur.d.ts.map +1 -0
- package/dist/progressive-blur.js +28 -0
- package/dist/progressive-blur.js.map +1 -0
- package/dist/reference-controls.d.ts +32 -0
- package/dist/reference-controls.d.ts.map +1 -0
- package/dist/reference-controls.js +28 -0
- package/dist/reference-controls.js.map +1 -0
- package/dist/screen-patterns.d.ts +16 -1
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +4 -0
- package/dist/screen-patterns.js.map +1 -1
- package/dist/scroll-progress.d.ts +7 -1
- package/dist/scroll-progress.d.ts.map +1 -1
- package/dist/scroll-progress.js +21 -2
- package/dist/scroll-progress.js.map +1 -1
- package/dist/text-annotation.d.ts +43 -0
- package/dist/text-annotation.d.ts.map +1 -0
- package/dist/text-annotation.js +137 -0
- package/dist/text-annotation.js.map +1 -0
- package/dist/toast-liquid.d.ts +3 -1
- package/dist/toast-liquid.d.ts.map +1 -1
- package/dist/toast-liquid.js +6 -2
- package/dist/toast-liquid.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/agreement.md +14 -0
- package/docs/asset.md +7 -0
- package/docs/brand-boundary.md +14 -5
- package/docs/code-block.md +12 -1
- package/docs/collapsible.md +6 -0
- package/docs/design-profile.md +263 -0
- package/docs/design-system-provider.md +7 -0
- package/docs/dialog.md +20 -1
- package/docs/effect-surface.md +19 -3
- 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/gooey-navigation.md +37 -6
- package/docs/heading.md +15 -0
- package/docs/image.md +17 -0
- package/docs/optional-adapters.md +25 -0
- package/docs/popover.md +15 -0
- package/docs/rating.md +6 -1
- package/docs/reference-controls.md +40 -0
- package/docs/task-list.md +15 -1
- package/docs/text-annotation.md +85 -0
- package/docs/theming.md +18 -5
- package/docs/usage/README.md +23 -1
- package/docs/usage/components/activity-heatmap.md +3 -1
- package/docs/usage/components/agreement.md +15 -3
- package/docs/usage/components/alert-dialog.md +16 -1
- package/docs/usage/components/asset.md +9 -2
- package/docs/usage/components/avatar.md +33 -1
- package/docs/usage/components/badge.md +3 -1
- package/docs/usage/components/bottom-cta.md +3 -1
- package/docs/usage/components/bottom-navigation.md +5 -1
- package/docs/usage/components/button.md +7 -1
- package/docs/usage/components/calendar.md +3 -1
- package/docs/usage/components/card.md +26 -3
- package/docs/usage/components/carousel.md +10 -1
- package/docs/usage/components/chat-message.md +11 -1
- package/docs/usage/components/chip.md +5 -0
- package/docs/usage/components/code-block.md +14 -2
- package/docs/usage/components/collapsible.md +10 -2
- package/docs/usage/components/combobox.md +12 -1
- package/docs/usage/components/command-palette.md +11 -6
- package/docs/usage/components/content-transition.md +29 -5
- package/docs/usage/components/context-menu.md +7 -1
- package/docs/usage/components/date-picker.md +3 -1
- package/docs/usage/components/design-system-provider.md +7 -5
- package/docs/usage/components/dialog.md +70 -1
- package/docs/usage/components/effect-surface.md +4 -2
- package/docs/usage/components/empty-state.md +10 -2
- package/docs/usage/components/field.md +26 -1
- package/docs/usage/components/form.md +13 -3
- package/docs/usage/components/heading.md +7 -1
- package/docs/usage/components/image-comparison.md +82 -0
- package/docs/usage/components/image.md +83 -2
- package/docs/usage/components/keyboard-avoiding.md +6 -1
- package/docs/usage/components/link.md +3 -1
- package/docs/usage/components/list-row.md +5 -1
- package/docs/usage/components/list.md +8 -1
- package/docs/usage/components/load-more.md +3 -1
- package/docs/usage/components/mentions.md +3 -1
- package/docs/usage/components/menu.md +3 -1
- package/docs/usage/components/menubar.md +7 -1
- package/docs/usage/components/message-composer.md +16 -5
- package/docs/usage/components/notice.md +9 -0
- package/docs/usage/components/number-field.md +3 -1
- package/docs/usage/components/onboarding-screen.md +16 -9
- package/docs/usage/components/overview-screen.md +73 -0
- package/docs/usage/components/password-field.md +5 -1
- package/docs/usage/components/popover.md +14 -1
- package/docs/usage/components/progress.md +28 -0
- package/docs/usage/components/progressive-blur.md +113 -0
- package/docs/usage/components/rating.md +74 -0
- package/docs/usage/components/saved-items-screen.md +3 -1
- package/docs/usage/components/screen-layout.md +9 -1
- package/docs/usage/components/search-field.md +13 -1
- package/docs/usage/components/search-screen.md +5 -0
- package/docs/usage/components/segmented-control.md +17 -0
- package/docs/usage/components/select.md +9 -0
- package/docs/usage/components/sheet.md +14 -1
- package/docs/usage/components/skeleton.md +9 -0
- package/docs/usage/components/slider.md +3 -1
- package/docs/usage/components/statistic.md +14 -1
- package/docs/usage/components/surface.md +8 -1
- package/docs/usage/components/tabs.md +11 -2
- package/docs/usage/components/tag.md +3 -1
- package/docs/usage/components/tags-input.md +11 -1
- package/docs/usage/components/text-area.md +3 -1
- package/docs/usage/components/text-transition.md +8 -2
- package/docs/usage/components/text.md +3 -1
- package/docs/usage/components/toast.md +23 -1
- package/docs/usage/components/top-bar.md +3 -1
- package/docs/usage/components/upload-item.md +3 -1
- package/docs/usage/compositions/action-feedback.md +77 -0
- package/docs/usage/compositions/adaptive-content.md +81 -0
- package/docs/usage/compositions/command-records.md +106 -0
- package/docs/usage/compositions/content-transition-comparison.md +108 -0
- package/docs/usage/compositions/context-toolbar.md +85 -0
- package/docs/usage/compositions/date-entry.md +108 -0
- package/docs/usage/compositions/date-time-selection.md +110 -0
- package/docs/usage/compositions/design-profile-comparison.md +114 -0
- package/docs/usage/compositions/document-resource.md +124 -0
- package/docs/usage/compositions/field-group.md +104 -0
- package/docs/usage/compositions/illustrated-outcome.md +91 -0
- package/docs/usage/compositions/live-list.md +104 -0
- package/docs/usage/compositions/optional-adapters.md +1 -1
- package/docs/usage/compositions/origin-dialog.md +108 -0
- package/docs/usage/compositions/selection-motion.md +73 -0
- package/docs/usage/compositions/texture-comparison.md +86 -0
- package/docs/usage/compositions/upload-recovery.md +80 -0
- package/docs/usage/compositions/video-dialog.md +100 -0
- package/docs/usage/screens/flow-onboarding.md +5 -3
- package/docs/usage/screens/product-bento.md +117 -0
- package/docs/usage/tokens/color.md +9 -2
- package/docs/usage/tokens/elevation-opacity.md +8 -1
- package/docs/usage/tokens/motion.md +10 -1
- package/docs/usage/tokens/radius.md +8 -1
- package/docs/usage/tokens/typography.md +15 -1
- package/package.json +49 -1
|
@@ -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 disabled?: boolean;\n pending?: boolean;\n attachmentCount?: number;\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}>;\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 }>;\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"]}
|
|
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}>;\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"]}
|
|
@@ -4,5 +4,11 @@ export type ScrollMetrics = Readonly<{
|
|
|
4
4
|
contentSize: number;
|
|
5
5
|
viewportSize: number;
|
|
6
6
|
}>;
|
|
7
|
-
export declare function resolveScrollProgress(
|
|
7
|
+
export declare function resolveScrollProgress(metrics: ScrollMetrics): number;
|
|
8
|
+
export type ScrollEdges = Readonly<{
|
|
9
|
+
before: boolean;
|
|
10
|
+
after: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
/** Whether more content exists in either logical direction; not a scroll action. */
|
|
13
|
+
export declare function resolveScrollEdges(metrics: ScrollMetrics): ScrollEdges;
|
|
8
14
|
//# sourceMappingURL=scroll-progress.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-progress.d.ts","sourceRoot":"","sources":["../src/scroll-progress.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"scroll-progress.d.ts","sourceRoot":"","sources":["../src/scroll-progress.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAMpG,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAOpE;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACxE,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,CAWtE"}
|
package/dist/scroll-progress.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
if (![offset, contentSize, viewportSize].every(Number.isFinite) || contentSize < 0 || viewportSize < 0)
|
|
1
|
+
function validateScrollMetrics({ offset, contentSize, viewportSize }) {
|
|
2
|
+
if (![offset, contentSize, viewportSize].every(Number.isFinite) || contentSize < 0 || viewportSize < 0) {
|
|
3
3
|
throw new RangeError('Scroll metrics must be finite with nonnegative sizes');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export function resolveScrollProgress(metrics) {
|
|
7
|
+
validateScrollMetrics(metrics);
|
|
8
|
+
const { offset, contentSize, viewportSize } = metrics;
|
|
4
9
|
// Unmeasured hosts are not complete; content that fits a measured viewport is.
|
|
5
10
|
if (viewportSize === 0)
|
|
6
11
|
return 0;
|
|
7
12
|
const extent = contentSize - viewportSize;
|
|
8
13
|
return extent <= 0 ? 1 : Math.min(1, Math.max(0, offset / extent));
|
|
9
14
|
}
|
|
15
|
+
/** Whether more content exists in either logical direction; not a scroll action. */
|
|
16
|
+
export function resolveScrollEdges(metrics) {
|
|
17
|
+
validateScrollMetrics(metrics);
|
|
18
|
+
const { offset, contentSize, viewportSize } = metrics;
|
|
19
|
+
// The reference blur obscured the last row even at the end. One logical pixel
|
|
20
|
+
// tolerates integer content/client dimensions paired with fractional offsets;
|
|
21
|
+
// exact equality would leave that edge covered. See progressive-blur adoption.
|
|
22
|
+
const tolerance = 1;
|
|
23
|
+
const extent = contentSize - viewportSize;
|
|
24
|
+
if (viewportSize === 0 || extent <= tolerance)
|
|
25
|
+
return { before: false, after: false };
|
|
26
|
+
const boundedOffset = Math.min(extent, Math.max(0, offset));
|
|
27
|
+
return { before: boundedOffset > tolerance, after: extent - boundedOffset > tolerance };
|
|
28
|
+
}
|
|
10
29
|
//# sourceMappingURL=scroll-progress.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-progress.js","sourceRoot":"","sources":["../src/scroll-progress.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"scroll-progress.js","sourceRoot":"","sources":["../src/scroll-progress.ts"],"names":[],"mappings":"AAEA,SAAS,qBAAqB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAiB;IACjF,IAAI,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,WAAW,GAAG,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACvG,MAAM,IAAI,UAAU,CAAC,sDAAsD,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AACD,MAAM,UAAU,qBAAqB,CAAC,OAAsB;IAC1D,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACtD,+EAA+E;IAC/E,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;IAC1C,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;AACrE,CAAC;AAGD,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,OAAsB;IACvD,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACtD,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,MAAM,SAAS,GAAG,CAAC,CAAC;IACpB,MAAM,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;IAC1C,IAAI,YAAY,KAAK,CAAC,IAAI,MAAM,IAAI,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtF,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,EAAE,CAAC;AAC1F,CAAC","sourcesContent":["/** Logical forward offset. Horizontal RTL hosts normalize their own scrollLeft. */\nexport type ScrollMetrics = Readonly<{ offset: number; contentSize: number; viewportSize: number }>;\nfunction validateScrollMetrics({ offset, contentSize, viewportSize }: ScrollMetrics): void {\n if (![offset, contentSize, viewportSize].every(Number.isFinite) || contentSize < 0 || viewportSize < 0) {\n throw new RangeError('Scroll metrics must be finite with nonnegative sizes');\n }\n}\nexport function resolveScrollProgress(metrics: ScrollMetrics): number {\n validateScrollMetrics(metrics);\n const { offset, contentSize, viewportSize } = metrics;\n // Unmeasured hosts are not complete; content that fits a measured viewport is.\n if (viewportSize === 0) return 0;\n const extent = contentSize - viewportSize;\n return extent <= 0 ? 1 : Math.min(1, Math.max(0, offset / extent));\n}\n\nexport type ScrollEdges = Readonly<{ before: boolean; after: boolean }>;\n/** Whether more content exists in either logical direction; not a scroll action. */\nexport function resolveScrollEdges(metrics: ScrollMetrics): ScrollEdges {\n validateScrollMetrics(metrics);\n const { offset, contentSize, viewportSize } = metrics;\n // The reference blur obscured the last row even at the end. One logical pixel\n // tolerates integer content/client dimensions paired with fractional offsets;\n // exact equality would leave that edge covered. See progressive-blur adoption.\n const tolerance = 1;\n const extent = contentSize - viewportSize;\n if (viewportSize === 0 || extent <= tolerance) return { before: false, after: false };\n const boundedOffset = Math.min(extent, Math.max(0, offset));\n return { before: boundedOffset > tolerance, after: extent - boundedOffset > tolerance };\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental drawing geometry, not a public TextFormat variant. A renderer
|
|
3
|
+
* supplies measured visual line rectangles; estimating widths from character
|
|
4
|
+
* counts would break proportional fonts, Korean wrapping, and bidirectional text.
|
|
5
|
+
* See docs/text-annotation.md for the unfinished renderer boundary.
|
|
6
|
+
*/
|
|
7
|
+
export declare const textAnnotationActions: readonly ["highlight", "underline", "box", "circle", "strike-through", "crossed-off", "bracket"];
|
|
8
|
+
export type TextAnnotationAction = typeof textAnnotationActions[number];
|
|
9
|
+
export type TextAnnotationRect = Readonly<{
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
}>;
|
|
15
|
+
export type TextAnnotationFragment = TextAnnotationRect & Readonly<{
|
|
16
|
+
lineIndex: number;
|
|
17
|
+
}>;
|
|
18
|
+
export type TextAnnotationPath = Readonly<{
|
|
19
|
+
d: string;
|
|
20
|
+
/** Filled marker strokes belong behind the text; outlines are unfilled. */
|
|
21
|
+
paint: 'fill' | 'stroke';
|
|
22
|
+
strokeWidth: number;
|
|
23
|
+
lineIndex: number;
|
|
24
|
+
}>;
|
|
25
|
+
export type TextAnnotationGeometry = Readonly<{
|
|
26
|
+
paths: readonly TextAnnotationPath[];
|
|
27
|
+
bounds: TextAnnotationRect | null;
|
|
28
|
+
}>;
|
|
29
|
+
/** Merge adjacent font/bidi runs only within their measured visual line. A gap
|
|
30
|
+
* in a partial selection stays unpainted even when both runs share a line.
|
|
31
|
+
* Hosts supply line identity from the same layout engine that paints the text.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeTextAnnotationFragments(fragments: readonly TextAnnotationFragment[]): TextAnnotationRect[];
|
|
34
|
+
/**
|
|
35
|
+
* Small deterministic offsets give the two passes a hand-drawn character.
|
|
36
|
+
* Random-per-render geometry was rejected: unrelated state updates must not
|
|
37
|
+
* make the annotation jump or create server/client output differences.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveTextAnnotationGeometry(lines: readonly TextAnnotationRect[], action: TextAnnotationAction, options?: Readonly<{
|
|
40
|
+
strokeWidth?: number;
|
|
41
|
+
padding?: number;
|
|
42
|
+
}>): TextAnnotationGeometry;
|
|
43
|
+
//# sourceMappingURL=text-annotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-annotation.d.ts","sourceRoot":"","sources":["../src/text-annotation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,kGAExB,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;AACxE,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACnG,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG,QAAQ,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC1F,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACnC,CAAC,CAAC;AAMH;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,GAAG,kBAAkB,EAAE,CAqB/G;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE,QAAQ,CAAC;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAM,GACjE,sBAAsB,CAoExB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental drawing geometry, not a public TextFormat variant. A renderer
|
|
3
|
+
* supplies measured visual line rectangles; estimating widths from character
|
|
4
|
+
* counts would break proportional fonts, Korean wrapping, and bidirectional text.
|
|
5
|
+
* See docs/text-annotation.md for the unfinished renderer boundary.
|
|
6
|
+
*/
|
|
7
|
+
export const textAnnotationActions = [
|
|
8
|
+
'highlight', 'underline', 'box', 'circle', 'strike-through', 'crossed-off', 'bracket',
|
|
9
|
+
];
|
|
10
|
+
function finite(value, name) {
|
|
11
|
+
if (!Number.isFinite(value))
|
|
12
|
+
throw new RangeError(`${name} must be finite`);
|
|
13
|
+
}
|
|
14
|
+
/** Merge adjacent font/bidi runs only within their measured visual line. A gap
|
|
15
|
+
* in a partial selection stays unpainted even when both runs share a line.
|
|
16
|
+
* Hosts supply line identity from the same layout engine that paints the text.
|
|
17
|
+
*/
|
|
18
|
+
export function mergeTextAnnotationFragments(fragments) {
|
|
19
|
+
for (const fragment of fragments) {
|
|
20
|
+
for (const name of ['x', 'y', 'width', 'height'])
|
|
21
|
+
finite(fragment[name], name);
|
|
22
|
+
if (fragment.width < 0 || fragment.height < 0 || !Number.isSafeInteger(fragment.lineIndex) || fragment.lineIndex < 0)
|
|
23
|
+
throw new RangeError('Invalid text annotation fragment');
|
|
24
|
+
finite(fragment.x + fragment.width, 'Fragment right');
|
|
25
|
+
finite(fragment.y + fragment.height, 'Fragment bottom');
|
|
26
|
+
}
|
|
27
|
+
const ordered = fragments.filter(fragment => fragment.width > 0 && fragment.height > 0).map(fragment => ({ ...fragment })).sort((a, b) => a.lineIndex - b.lineIndex || a.x - b.x);
|
|
28
|
+
const merged = [];
|
|
29
|
+
for (const fragment of ordered) {
|
|
30
|
+
const previous = merged[merged.length - 1];
|
|
31
|
+
// Skia exposes Float32 edges: measured adjacent runs differed by ~0.00001px.
|
|
32
|
+
// 0.01 logical px absorbs this rounding, not a visible inter-word/selection gap.
|
|
33
|
+
if (previous && previous.lineIndex === fragment.lineIndex && fragment.x <= previous.x + previous.width + 0.01) {
|
|
34
|
+
const y = Math.min(previous.y, fragment.y);
|
|
35
|
+
merged[merged.length - 1] = { lineIndex: previous.lineIndex, x: previous.x, y, width: Math.max(previous.x + previous.width, fragment.x + fragment.width) - previous.x, height: Math.max(previous.y + previous.height, fragment.y + fragment.height) - y };
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
merged.push(fragment);
|
|
39
|
+
}
|
|
40
|
+
return merged.map(({ x, y, width, height }) => {
|
|
41
|
+
finite(width, 'Merged width');
|
|
42
|
+
finite(height, 'Merged height');
|
|
43
|
+
return { x, y, width, height };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Small deterministic offsets give the two passes a hand-drawn character.
|
|
48
|
+
* Random-per-render geometry was rejected: unrelated state updates must not
|
|
49
|
+
* make the annotation jump or create server/client output differences.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveTextAnnotationGeometry(lines, action, options = {}) {
|
|
52
|
+
if (!textAnnotationActions.includes(action))
|
|
53
|
+
throw new TypeError('Unknown text annotation action');
|
|
54
|
+
// These are drawing parameters, not layout spacing. 1.5px keeps a doubled
|
|
55
|
+
// outline lighter than a focus ring; the 2px gap avoids touching glyph edges.
|
|
56
|
+
const strokeWidth = options.strokeWidth ?? 1.5;
|
|
57
|
+
const padding = options.padding ?? 2;
|
|
58
|
+
finite(strokeWidth, 'strokeWidth');
|
|
59
|
+
finite(padding, 'padding');
|
|
60
|
+
if (strokeWidth <= 0)
|
|
61
|
+
throw new RangeError('strokeWidth must be positive');
|
|
62
|
+
if (padding < 0)
|
|
63
|
+
throw new RangeError('padding must not be negative');
|
|
64
|
+
const paths = [];
|
|
65
|
+
let bounds = null;
|
|
66
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
67
|
+
for (const name of ['x', 'y', 'width', 'height'])
|
|
68
|
+
finite(line[name], name);
|
|
69
|
+
if (line.width < 0 || line.height < 0)
|
|
70
|
+
throw new RangeError('Line dimensions must not be negative');
|
|
71
|
+
// Empty lines and a not-yet-laid-out host do not produce stray marker dots.
|
|
72
|
+
if (line.width === 0 || line.height === 0)
|
|
73
|
+
continue;
|
|
74
|
+
// Scale the wobble down for tiny fragments; it must not swallow punctuation.
|
|
75
|
+
const wobble = Math.min(1, line.width / 8, line.height / 8);
|
|
76
|
+
// The surrounding loop must clear the actual text even if callers choose
|
|
77
|
+
// zero padding or a thick pen. Strike-through/crossed-off intentionally do not.
|
|
78
|
+
const gap = action === 'circle' ? Math.max(padding, strokeWidth / 2 + wobble) : padding;
|
|
79
|
+
const l = line.x - gap, r = line.x + line.width + gap;
|
|
80
|
+
const t = line.y - gap, b = line.y + line.height + gap;
|
|
81
|
+
const cx = l + (r - l) / 2, cy = t + (b - t) / 2;
|
|
82
|
+
// A modest outward bow keeps a loop distinct from the box without extending
|
|
83
|
+
// an ellipse by 40% of a long line's width into neighboring text or the screen edge.
|
|
84
|
+
const bow = action === 'circle' ? Math.min(line.width, line.height) * 0.12 : 0;
|
|
85
|
+
const add = (d, paint) => paths.push({ d, paint, strokeWidth, lineIndex });
|
|
86
|
+
if (action === 'highlight') {
|
|
87
|
+
add(`M ${l} ${t + wobble} Q ${cx} ${t - wobble} ${r} ${t} L ${r - wobble} ${b} Q ${cx} ${b + wobble} ${l} ${b - wobble} Z`, 'fill');
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
for (const offset of [-wobble / 2, wobble / 2]) {
|
|
91
|
+
const x1 = l + offset, x2 = r + offset, y1 = t + offset, y2 = b + offset;
|
|
92
|
+
switch (action) {
|
|
93
|
+
case 'underline':
|
|
94
|
+
add(`M ${x1} ${y2} Q ${cx} ${y2 + wobble} ${x2} ${y2 - wobble / 2}`, 'stroke');
|
|
95
|
+
break;
|
|
96
|
+
case 'strike-through':
|
|
97
|
+
add(`M ${x1} ${cy + offset} Q ${cx} ${cy - wobble} ${x2} ${cy + offset}`, 'stroke');
|
|
98
|
+
break;
|
|
99
|
+
case 'crossed-off':
|
|
100
|
+
add(`M ${x1} ${y1} Q ${cx} ${cy - wobble} ${x2} ${y2} M ${x2} ${y1} Q ${cx} ${cy + wobble} ${x1} ${y2}`, 'stroke');
|
|
101
|
+
break;
|
|
102
|
+
case 'box':
|
|
103
|
+
add(`M ${x1} ${y1} Q ${cx} ${y1 - wobble} ${x2} ${y1} L ${x2} ${y2} Q ${cx} ${y2 + wobble} ${x1} ${y2} Z`, 'stroke');
|
|
104
|
+
break;
|
|
105
|
+
case 'circle':
|
|
106
|
+
// The original inscribed ellipse cut through end glyphs at 32px.
|
|
107
|
+
// Bow each edge outward through the padded corners instead: every
|
|
108
|
+
// segment stays outside the text rectangle while retaining a loose loop.
|
|
109
|
+
add(`M ${x1} ${cy} Q ${x1 - bow} ${y1} ${x1} ${y1} Q ${cx} ${y1 - bow} ${x2} ${y1} Q ${x2 + bow} ${cy} ${x2} ${y2} Q ${cx} ${y2 + bow} ${x1} ${y2} Q ${x1 - bow} ${cy} ${x1} ${cy} Z`, 'stroke');
|
|
110
|
+
break;
|
|
111
|
+
case 'bracket': {
|
|
112
|
+
const arm = Math.min(line.height / 3, line.width / 4);
|
|
113
|
+
add(`M ${x1 + arm} ${y1} L ${x1} ${y1} L ${x1} ${y2} L ${x1 + arm} ${y2} M ${x2 - arm} ${y1} L ${x2} ${y1} L ${x2} ${y2} L ${x2 - arm} ${y2}`, 'stroke');
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Include control points, both sketch passes and stroke caps so the SVG
|
|
120
|
+
// viewBox cannot clip an underline or a bracket at large text sizes.
|
|
121
|
+
const outset = Math.max(2 * wobble, bow + wobble / 2) + strokeWidth / 2;
|
|
122
|
+
const left = l - outset, top = t - outset, right = r + outset, bottom = b + outset;
|
|
123
|
+
if (!bounds)
|
|
124
|
+
bounds = { x: left, y: top, width: right - left, height: bottom - top };
|
|
125
|
+
else {
|
|
126
|
+
const x = Math.min(bounds.x, left), y = Math.min(bounds.y, top);
|
|
127
|
+
bounds = { x, y, width: Math.max(bounds.x + bounds.width, right) - x, height: Math.max(bounds.y + bounds.height, bottom) - y };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Overflow can occur even with finite inputs. Reject it here instead of
|
|
131
|
+
// handing NaN/Infinity geometry to a native SVG implementation.
|
|
132
|
+
if (bounds)
|
|
133
|
+
for (const value of Object.values(bounds))
|
|
134
|
+
finite(value, 'Annotation bounds');
|
|
135
|
+
return { paths, bounds };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=text-annotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-annotation.js","sourceRoot":"","sources":["../src/text-annotation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Experimental drawing geometry, not a public TextFormat variant. A renderer\n * supplies measured visual line rectangles; estimating widths from character\n * counts would break proportional fonts, Korean wrapping, and bidirectional text.\n * See docs/text-annotation.md for the unfinished renderer boundary.\n */\nexport const textAnnotationActions = [\n 'highlight', 'underline', 'box', 'circle', 'strike-through', 'crossed-off', 'bracket',\n] as const;\nexport type TextAnnotationAction = typeof textAnnotationActions[number];\nexport type TextAnnotationRect = Readonly<{ x: number; y: number; width: number; height: number }>;\nexport type TextAnnotationFragment = TextAnnotationRect & Readonly<{ lineIndex: number }>;\nexport type TextAnnotationPath = Readonly<{\n d: string;\n /** Filled marker strokes belong behind the text; outlines are unfilled. */\n paint: 'fill' | 'stroke';\n strokeWidth: number;\n lineIndex: number;\n}>;\nexport type TextAnnotationGeometry = Readonly<{\n paths: readonly TextAnnotationPath[];\n bounds: TextAnnotationRect | null;\n}>;\n\nfunction finite(value: number, name: string): void {\n if (!Number.isFinite(value)) throw new RangeError(`${name} must be finite`);\n}\n\n/** Merge adjacent font/bidi runs only within their measured visual line. A gap\n * in a partial selection stays unpainted even when both runs share a line.\n * Hosts supply line identity from the same layout engine that paints the text.\n */\nexport function mergeTextAnnotationFragments(fragments: readonly TextAnnotationFragment[]): TextAnnotationRect[] {\n for (const fragment of fragments) {\n for (const name of ['x', 'y', 'width', 'height'] as const) finite(fragment[name], name);\n if (fragment.width < 0 || fragment.height < 0 || !Number.isSafeInteger(fragment.lineIndex) || fragment.lineIndex < 0) throw new RangeError('Invalid text annotation fragment');\n finite(fragment.x + fragment.width, 'Fragment right'); finite(fragment.y + fragment.height, 'Fragment bottom');\n }\n const ordered = fragments.filter(fragment => fragment.width > 0 && fragment.height > 0).map(fragment => ({ ...fragment })).sort((a, b) => a.lineIndex - b.lineIndex || a.x - b.x);\n const merged: TextAnnotationFragment[] = [];\n for (const fragment of ordered) {\n const previous = merged[merged.length - 1];\n // Skia exposes Float32 edges: measured adjacent runs differed by ~0.00001px.\n // 0.01 logical px absorbs this rounding, not a visible inter-word/selection gap.\n if (previous && previous.lineIndex === fragment.lineIndex && fragment.x <= previous.x + previous.width + 0.01) {\n const y = Math.min(previous.y, fragment.y);\n merged[merged.length - 1] = { lineIndex: previous.lineIndex, x: previous.x, y, width: Math.max(previous.x + previous.width, fragment.x + fragment.width) - previous.x, height: Math.max(previous.y + previous.height, fragment.y + fragment.height) - y };\n } else merged.push(fragment);\n }\n return merged.map(({ x, y, width, height }) => {\n finite(width, 'Merged width'); finite(height, 'Merged height');\n return { x, y, width, height };\n });\n}\n\n/**\n * Small deterministic offsets give the two passes a hand-drawn character.\n * Random-per-render geometry was rejected: unrelated state updates must not\n * make the annotation jump or create server/client output differences.\n */\nexport function resolveTextAnnotationGeometry(\n lines: readonly TextAnnotationRect[],\n action: TextAnnotationAction,\n options: Readonly<{ strokeWidth?: number; padding?: number }> = {},\n): TextAnnotationGeometry {\n if (!(textAnnotationActions as readonly string[]).includes(action)) throw new TypeError('Unknown text annotation action');\n // These are drawing parameters, not layout spacing. 1.5px keeps a doubled\n // outline lighter than a focus ring; the 2px gap avoids touching glyph edges.\n const strokeWidth = options.strokeWidth ?? 1.5;\n const padding = options.padding ?? 2;\n finite(strokeWidth, 'strokeWidth'); finite(padding, 'padding');\n if (strokeWidth <= 0) throw new RangeError('strokeWidth must be positive');\n if (padding < 0) throw new RangeError('padding must not be negative');\n const paths: TextAnnotationPath[] = [];\n let bounds: TextAnnotationRect | null = null;\n for (const [lineIndex, line] of lines.entries()) {\n for (const name of ['x', 'y', 'width', 'height'] as const) finite(line[name], name);\n if (line.width < 0 || line.height < 0) throw new RangeError('Line dimensions must not be negative');\n // Empty lines and a not-yet-laid-out host do not produce stray marker dots.\n if (line.width === 0 || line.height === 0) continue;\n // Scale the wobble down for tiny fragments; it must not swallow punctuation.\n const wobble = Math.min(1, line.width / 8, line.height / 8);\n // The surrounding loop must clear the actual text even if callers choose\n // zero padding or a thick pen. Strike-through/crossed-off intentionally do not.\n const gap = action === 'circle' ? Math.max(padding, strokeWidth / 2 + wobble) : padding;\n const l = line.x - gap, r = line.x + line.width + gap;\n const t = line.y - gap, b = line.y + line.height + gap;\n const cx = l + (r - l) / 2, cy = t + (b - t) / 2;\n // A modest outward bow keeps a loop distinct from the box without extending\n // an ellipse by 40% of a long line's width into neighboring text or the screen edge.\n const bow = action === 'circle' ? Math.min(line.width, line.height) * 0.12 : 0;\n const add = (d: string, paint: 'fill' | 'stroke') => paths.push({ d, paint, strokeWidth, lineIndex });\n if (action === 'highlight') {\n add(`M ${l} ${t + wobble} Q ${cx} ${t - wobble} ${r} ${t} L ${r - wobble} ${b} Q ${cx} ${b + wobble} ${l} ${b - wobble} Z`, 'fill');\n } else {\n for (const offset of [-wobble / 2, wobble / 2]) {\n const x1 = l + offset, x2 = r + offset, y1 = t + offset, y2 = b + offset;\n switch (action) {\n case 'underline':\n add(`M ${x1} ${y2} Q ${cx} ${y2 + wobble} ${x2} ${y2 - wobble / 2}`, 'stroke'); break;\n case 'strike-through':\n add(`M ${x1} ${cy + offset} Q ${cx} ${cy - wobble} ${x2} ${cy + offset}`, 'stroke'); break;\n case 'crossed-off':\n add(`M ${x1} ${y1} Q ${cx} ${cy - wobble} ${x2} ${y2} M ${x2} ${y1} Q ${cx} ${cy + wobble} ${x1} ${y2}`, 'stroke'); break;\n case 'box':\n add(`M ${x1} ${y1} Q ${cx} ${y1 - wobble} ${x2} ${y1} L ${x2} ${y2} Q ${cx} ${y2 + wobble} ${x1} ${y2} Z`, 'stroke'); break;\n case 'circle':\n // The original inscribed ellipse cut through end glyphs at 32px.\n // Bow each edge outward through the padded corners instead: every\n // segment stays outside the text rectangle while retaining a loose loop.\n add(`M ${x1} ${cy} Q ${x1 - bow} ${y1} ${x1} ${y1} Q ${cx} ${y1 - bow} ${x2} ${y1} Q ${x2 + bow} ${cy} ${x2} ${y2} Q ${cx} ${y2 + bow} ${x1} ${y2} Q ${x1 - bow} ${cy} ${x1} ${cy} Z`, 'stroke'); break;\n case 'bracket': {\n const arm = Math.min(line.height / 3, line.width / 4);\n add(`M ${x1 + arm} ${y1} L ${x1} ${y1} L ${x1} ${y2} L ${x1 + arm} ${y2} M ${x2 - arm} ${y1} L ${x2} ${y1} L ${x2} ${y2} L ${x2 - arm} ${y2}`, 'stroke'); break;\n }\n }\n }\n }\n // Include control points, both sketch passes and stroke caps so the SVG\n // viewBox cannot clip an underline or a bracket at large text sizes.\n const outset = Math.max(2 * wobble, bow + wobble / 2) + strokeWidth / 2;\n const left = l - outset, top = t - outset, right = r + outset, bottom = b + outset;\n if (!bounds) bounds = { x: left, y: top, width: right - left, height: bottom - top };\n else {\n const x = Math.min(bounds.x, left), y = Math.min(bounds.y, top);\n bounds = { x, y, width: Math.max(bounds.x + bounds.width, right) - x, height: Math.max(bounds.y + bounds.height, bottom) - y };\n }\n }\n // Overflow can occur even with finite inputs. Reject it here instead of\n // handing NaN/Infinity geometry to a native SVG implementation.\n if (bounds) for (const value of Object.values(bounds)) finite(value, 'Annotation bounds');\n return { paths, bounds };\n}\n"]}
|
package/dist/toast-liquid.d.ts
CHANGED
|
@@ -101,7 +101,9 @@ export declare function resolveLiquidToastLayout(input: Readonly<{
|
|
|
101
101
|
y: number;
|
|
102
102
|
}>;
|
|
103
103
|
}>): LiquidToastLayout;
|
|
104
|
-
|
|
104
|
+
/** A themed corner changes only the settled card, never the circular origin.
|
|
105
|
+
* Keep the default recipe for existing callers and clamp to measured paint bounds. */
|
|
106
|
+
export declare function buildLiquidToastGeometry(drop: number, expand: number, layout: LiquidToastLayout, cardRadius?: number): {
|
|
105
107
|
x: number;
|
|
106
108
|
y: number;
|
|
107
109
|
width: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast-liquid.d.ts","sourceRoot":"","sources":["../src/toast-liquid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BpB,CAAC;AAEX,4FAA4F;AAC5F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IACvE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAAC,CAAC;AAEH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAOzE;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACpF,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAC5E,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;CACxD,CAAC,CAAC;AAEH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAClF,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD,CAAC,GAAG,iBAAiB,CA2BrB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"toast-liquid.d.ts","sourceRoot":"","sources":["../src/toast-liquid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BpB,CAAC;AAEX,4FAA4F;AAC5F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IACvE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAAC,CAAC;AAEH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAOzE;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACpF,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAC5E,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;CACxD,CAAC,CAAC;AAEH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAClF,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD,CAAC,GAAG,iBAAiB,CA2BrB;AAED;sFACsF;AACtF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,UAAU,GAAE,MAAiC;;;;;;;;;;;EA0B9I"}
|
package/dist/toast-liquid.js
CHANGED
|
@@ -75,8 +75,12 @@ export function resolveLiquidToastLayout(input) {
|
|
|
75
75
|
fits: width >= 240 && height > 0 && cardTop + height <= availableHeight,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
/** A themed corner changes only the settled card, never the circular origin.
|
|
79
|
+
* Keep the default recipe for existing callers and clamp to measured paint bounds. */
|
|
80
|
+
export function buildLiquidToastGeometry(drop, expand, layout, cardRadius = liquidToastRecipe.radius) {
|
|
79
81
|
"worklet";
|
|
82
|
+
if (!Number.isFinite(cardRadius) || cardRadius < 0)
|
|
83
|
+
throw new RangeError("Liquid Toast card radius must be finite and non-negative");
|
|
80
84
|
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
81
85
|
const mix = (a, b, t) => a + (b - a) * t;
|
|
82
86
|
const grow = 1 - (1 - clamp(drop / 0.7)) ** 1.25;
|
|
@@ -95,7 +99,7 @@ export function buildLiquidToastGeometry(drop, expand, layout) {
|
|
|
95
99
|
const neckY = layout.anchorY + layout.anchorHeight / 2;
|
|
96
100
|
return {
|
|
97
101
|
x: centerX - width / 2, y: centerY - height / 2, width, height,
|
|
98
|
-
radius: Math.max(0, Math.min(mix(droplet / 2,
|
|
102
|
+
radius: Math.max(0, Math.min(mix(droplet / 2, cardRadius, expand), width / 2, height / 2)),
|
|
99
103
|
neckX: layout.anchorX + layout.anchorWidth / 2 - neckWidth / 2,
|
|
100
104
|
neckY, neckWidth, neckHeight: Math.max(0, centerY - neckY),
|
|
101
105
|
offsetY: centerY - (layout.cardTop + layout.height / 2),
|
package/dist/toast-liquid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast-liquid.js","sourceRoot":"","sources":["../src/toast-liquid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Geometry adapted from rit3zh/expo-dynamic-notifications, commit 5de059a5cbef.\n * The MIT notice is shipped in THIRD_PARTY_NOTICES.md. See docs/plans/liquid-toast.md.\n * Kept separate from the lifecycle: animation must never own a second queue.\n */\nexport const liquidToastRecipe = {\n // 2026-10-02: use a circular in-app origin rather than a second Dynamic Island.\n // Keep the public capsule key; changing geometry does not require callers to migrate.\n capsule: { width: 32, height: 32 },\n // Preserve the settled card top (58) when increasing the anchor height from 24 to 32.\n gap: 26,\n minHeight: 74,\n maxWidth: 396,\n // 2026-10-02 card redesign: the medium 12-unit corner supports a banner silhouette.\n radius: 12,\n dropSize: 52,\n neckWidth: 60,\n blur: 14.3,\n gain: 22,\n threshold: 0.43,\n // These intentionally exceed general HJM motion: the neck needs time to separate.\n // Original choreography is the comparison baseline, not a new global motion scale.\n spring: {\n drop: { duration: 1150, dampingRatio: 0.82 },\n expand: { duration: 1000, dampingRatio: 0.8 },\n reveal: { duration: 700, dampingRatio: 1 },\n tint: { duration: 700, dampingRatio: 1 },\n collapse: { duration: 660, dampingRatio: 0.92, velocity: 2 },\n return: { duration: 1150, dampingRatio: 0.9 },\n fade: { duration: 360, dampingRatio: 1 },\n drag: { duration: 560, dampingRatio: 0.7 },\n },\n // Keep the drop anchor-colored through descent; early tint looked like a gray blob on iPhone 17 (2026-09-30).\n delay: { tint: 420, expand: 340, reveal: 560, collapse: 100, return: 280 },\n swipe: { distance: -18, velocity: -420 },\n} as const;\n\n/** Island frames are explicitly verified window coordinates, never inferred from insets. */\nexport type LiquidToastAnchor = Readonly<{ kind: \"capsule\" }> | Readonly<{\n kind: \"island\";\n frame: Readonly<{ x: number; y: number; width: number; height: number }>;\n}>;\n\nexport function validateLiquidToastAnchor(anchor: LiquidToastAnchor): void {\n if (anchor.kind === \"capsule\") return;\n if (anchor.kind !== \"island\") throw new TypeError(\"Unsupported liquid toast anchor\");\n const frame = anchor.frame;\n if (!frame || ![frame.x, frame.y, frame.width, frame.height].every(Number.isFinite) || frame.x < 0 || frame.y < 0 || frame.width <= 0 || frame.height <= 0) {\n throw new RangeError(\"Liquid Toast island frame must be finite with positive dimensions\");\n }\n}\n\nexport type LiquidToastLayout = Readonly<{\n width: number; height: number; cardWidth: number; cardTop: number; cardLeft: number;\n anchorX: number; anchorY: number; anchorWidth: number; anchorHeight: number;\n canvasTop: number; canvasHeight: number; fits: boolean;\n}>;\n\nexport function resolveLiquidToastLayout(input: Readonly<{\n width: number; height: number; availableHeight: number; anchor: LiquidToastAnchor;\n windowOrigin?: Readonly<{ x: number; y: number }>;\n}>): LiquidToastLayout {\n const { width, height, availableHeight, anchor, windowOrigin } = input;\n validateLiquidToastAnchor(anchor);\n if (![width, height, availableHeight].every(Number.isFinite) || width < 0 || height < 0 || availableHeight < 0) {\n throw new RangeError(\"Liquid Toast measurements must be finite and non-negative\");\n }\n let anchorWidth: number = liquidToastRecipe.capsule.width;\n let anchorHeight: number = liquidToastRecipe.capsule.height;\n let anchorX = (width - anchorWidth) / 2;\n let anchorY = 0;\n if (anchor.kind === \"island\" && windowOrigin) {\n const x = anchor.frame.x - windowOrigin.x;\n const y = anchor.frame.y - windowOrigin.y;\n // Rotation/stale host measurements fall back to capsule instead of drawing over a cutout.\n if (x >= 0 && x + anchor.frame.width <= width && y + anchor.frame.height <= availableHeight) {\n anchorX = x; anchorY = y; anchorWidth = anchor.frame.width; anchorHeight = anchor.frame.height;\n }\n }\n const cardWidth = Math.min(width, liquidToastRecipe.maxWidth);\n const cardTop = Math.max(0, anchorY + anchorHeight + liquidToastRecipe.gap);\n const canvasTop = Math.min(0, anchorY) - liquidToastRecipe.blur * 2;\n return {\n width, height, cardWidth, cardTop, cardLeft: (width - cardWidth) / 2,\n anchorX, anchorY, anchorWidth, anchorHeight, canvasTop,\n canvasHeight: cardTop + height - canvasTop + 32,\n fits: width >= 240 && height > 0 && cardTop + height <= availableHeight,\n };\n}\n\nexport function buildLiquidToastGeometry(drop: number, expand: number, layout: LiquidToastLayout) {\n \"worklet\";\n const clamp = (n: number) => Math.max(0, Math.min(1, n));\n const mix = (a: number, b: number, t: number) => a + (b - a) * t;\n const grow = 1 - (1 - clamp(drop / 0.7)) ** 1.25;\n const n = clamp(drop / 0.82);\n const peak = 1.6 / 3;\n const normal = peak ** 1.6 * (1 - peak) ** 1.4;\n const neck = n <= 0 || n >= 1 ? 0 : (n ** 1.6 * (1 - n) ** 1.4) / normal;\n const stretch = 1 + 0.38 * neck;\n const droplet = liquidToastRecipe.dropSize * grow;\n const width = Math.max(0, Math.min(layout.width, mix(droplet / stretch, layout.cardWidth, expand)));\n const height = Math.max(0, mix(droplet * stretch, layout.height, expand));\n const originY = layout.anchorY + layout.anchorHeight * 0.66;\n const centerY = mix(originY, layout.cardTop + layout.height / 2, drop);\n const centerX = mix(layout.anchorX + layout.anchorWidth / 2, layout.width / 2, clamp(expand));\n const neckWidth = Math.max(0, Math.min(liquidToastRecipe.neckWidth, width) * neck);\n const neckY = layout.anchorY + layout.anchorHeight / 2;\n return {\n x: centerX - width / 2, y: centerY - height / 2, width, height,\n radius: Math.max(0, Math.min(mix(droplet / 2, liquidToastRecipe.radius, expand), width / 2, height / 2)),\n neckX: layout.anchorX + layout.anchorWidth / 2 - neckWidth / 2,\n neckY, neckWidth, neckHeight: Math.max(0, centerY - neckY),\n offsetY: centerY - (layout.cardTop + layout.height / 2),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"toast-liquid.js","sourceRoot":"","sources":["../src/toast-liquid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Geometry adapted from rit3zh/expo-dynamic-notifications, commit 5de059a5cbef.\n * The MIT notice is shipped in THIRD_PARTY_NOTICES.md. See docs/plans/liquid-toast.md.\n * Kept separate from the lifecycle: animation must never own a second queue.\n */\nexport const liquidToastRecipe = {\n // 2026-10-02: use a circular in-app origin rather than a second Dynamic Island.\n // Keep the public capsule key; changing geometry does not require callers to migrate.\n capsule: { width: 32, height: 32 },\n // Preserve the settled card top (58) when increasing the anchor height from 24 to 32.\n gap: 26,\n minHeight: 74,\n maxWidth: 396,\n // 2026-10-02 card redesign: the medium 12-unit corner supports a banner silhouette.\n radius: 12,\n dropSize: 52,\n neckWidth: 60,\n blur: 14.3,\n gain: 22,\n threshold: 0.43,\n // These intentionally exceed general HJM motion: the neck needs time to separate.\n // Original choreography is the comparison baseline, not a new global motion scale.\n spring: {\n drop: { duration: 1150, dampingRatio: 0.82 },\n expand: { duration: 1000, dampingRatio: 0.8 },\n reveal: { duration: 700, dampingRatio: 1 },\n tint: { duration: 700, dampingRatio: 1 },\n collapse: { duration: 660, dampingRatio: 0.92, velocity: 2 },\n return: { duration: 1150, dampingRatio: 0.9 },\n fade: { duration: 360, dampingRatio: 1 },\n drag: { duration: 560, dampingRatio: 0.7 },\n },\n // Keep the drop anchor-colored through descent; early tint looked like a gray blob on iPhone 17 (2026-09-30).\n delay: { tint: 420, expand: 340, reveal: 560, collapse: 100, return: 280 },\n swipe: { distance: -18, velocity: -420 },\n} as const;\n\n/** Island frames are explicitly verified window coordinates, never inferred from insets. */\nexport type LiquidToastAnchor = Readonly<{ kind: \"capsule\" }> | Readonly<{\n kind: \"island\";\n frame: Readonly<{ x: number; y: number; width: number; height: number }>;\n}>;\n\nexport function validateLiquidToastAnchor(anchor: LiquidToastAnchor): void {\n if (anchor.kind === \"capsule\") return;\n if (anchor.kind !== \"island\") throw new TypeError(\"Unsupported liquid toast anchor\");\n const frame = anchor.frame;\n if (!frame || ![frame.x, frame.y, frame.width, frame.height].every(Number.isFinite) || frame.x < 0 || frame.y < 0 || frame.width <= 0 || frame.height <= 0) {\n throw new RangeError(\"Liquid Toast island frame must be finite with positive dimensions\");\n }\n}\n\nexport type LiquidToastLayout = Readonly<{\n width: number; height: number; cardWidth: number; cardTop: number; cardLeft: number;\n anchorX: number; anchorY: number; anchorWidth: number; anchorHeight: number;\n canvasTop: number; canvasHeight: number; fits: boolean;\n}>;\n\nexport function resolveLiquidToastLayout(input: Readonly<{\n width: number; height: number; availableHeight: number; anchor: LiquidToastAnchor;\n windowOrigin?: Readonly<{ x: number; y: number }>;\n}>): LiquidToastLayout {\n const { width, height, availableHeight, anchor, windowOrigin } = input;\n validateLiquidToastAnchor(anchor);\n if (![width, height, availableHeight].every(Number.isFinite) || width < 0 || height < 0 || availableHeight < 0) {\n throw new RangeError(\"Liquid Toast measurements must be finite and non-negative\");\n }\n let anchorWidth: number = liquidToastRecipe.capsule.width;\n let anchorHeight: number = liquidToastRecipe.capsule.height;\n let anchorX = (width - anchorWidth) / 2;\n let anchorY = 0;\n if (anchor.kind === \"island\" && windowOrigin) {\n const x = anchor.frame.x - windowOrigin.x;\n const y = anchor.frame.y - windowOrigin.y;\n // Rotation/stale host measurements fall back to capsule instead of drawing over a cutout.\n if (x >= 0 && x + anchor.frame.width <= width && y + anchor.frame.height <= availableHeight) {\n anchorX = x; anchorY = y; anchorWidth = anchor.frame.width; anchorHeight = anchor.frame.height;\n }\n }\n const cardWidth = Math.min(width, liquidToastRecipe.maxWidth);\n const cardTop = Math.max(0, anchorY + anchorHeight + liquidToastRecipe.gap);\n const canvasTop = Math.min(0, anchorY) - liquidToastRecipe.blur * 2;\n return {\n width, height, cardWidth, cardTop, cardLeft: (width - cardWidth) / 2,\n anchorX, anchorY, anchorWidth, anchorHeight, canvasTop,\n canvasHeight: cardTop + height - canvasTop + 32,\n fits: width >= 240 && height > 0 && cardTop + height <= availableHeight,\n };\n}\n\n/** A themed corner changes only the settled card, never the circular origin.\n * Keep the default recipe for existing callers and clamp to measured paint bounds. */\nexport function buildLiquidToastGeometry(drop: number, expand: number, layout: LiquidToastLayout, cardRadius: number = liquidToastRecipe.radius) {\n \"worklet\";\n if (!Number.isFinite(cardRadius) || cardRadius < 0) throw new RangeError(\"Liquid Toast card radius must be finite and non-negative\");\n const clamp = (n: number) => Math.max(0, Math.min(1, n));\n const mix = (a: number, b: number, t: number) => a + (b - a) * t;\n const grow = 1 - (1 - clamp(drop / 0.7)) ** 1.25;\n const n = clamp(drop / 0.82);\n const peak = 1.6 / 3;\n const normal = peak ** 1.6 * (1 - peak) ** 1.4;\n const neck = n <= 0 || n >= 1 ? 0 : (n ** 1.6 * (1 - n) ** 1.4) / normal;\n const stretch = 1 + 0.38 * neck;\n const droplet = liquidToastRecipe.dropSize * grow;\n const width = Math.max(0, Math.min(layout.width, mix(droplet / stretch, layout.cardWidth, expand)));\n const height = Math.max(0, mix(droplet * stretch, layout.height, expand));\n const originY = layout.anchorY + layout.anchorHeight * 0.66;\n const centerY = mix(originY, layout.cardTop + layout.height / 2, drop);\n const centerX = mix(layout.anchorX + layout.anchorWidth / 2, layout.width / 2, clamp(expand));\n const neckWidth = Math.max(0, Math.min(liquidToastRecipe.neckWidth, width) * neck);\n const neckY = layout.anchorY + layout.anchorHeight / 2;\n return {\n x: centerX - width / 2, y: centerY - height / 2, width, height,\n radius: Math.max(0, Math.min(mix(droplet / 2, cardRadius, expand), width / 2, height / 2)),\n neckX: layout.anchorX + layout.anchorWidth / 2 - neckWidth / 2,\n neckY, neckWidth, neckHeight: Math.max(0, centerY - neckY),\n offsetY: centerY - (layout.cardTop + layout.height / 2),\n };\n}\n"]}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.15.0\" as const;\n"]}
|
package/docs/agreement.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Agreement contract
|
|
2
2
|
|
|
3
|
+
검토일: 2026-10-07.
|
|
4
|
+
|
|
3
5
|
**문제.** 가입·결제 앞에 서는 약관 동의 묶음이다. 전체 동의 한 줄과 개별 항목들,
|
|
4
6
|
필수/선택 구분, 각 항목의 전문 보기가 한 덩어리로 움직인다.
|
|
5
7
|
|
|
@@ -36,3 +38,15 @@ accessibility state와 `openDetail` action이다. 두 표면 모두 전체 동
|
|
|
36
38
|
이전에는 토글에서만 불려 `defaultCheckedIds`·`checkedIds`가 처음부터 필수를 채운 경우 첫 `satisfied: true`가 오지 않아,
|
|
37
39
|
이 콜백에 연결한 제출 버튼이 꺼진 채 남았다. 파생 상태가 바뀔 때마다 부르는 대안은 호출 수가 렌더 횟수에 묶여 버렸다.
|
|
38
40
|
호출 수를 세던 소비자는 마운트 1회가 늘어난다(Changeset `recipe-parity-followups`).
|
|
41
|
+
|
|
42
|
+
**제출 중 묶음 잠금(2026-10-07).** Utilverse 가입 화면은 서버 요청 동안 필수 약관의
|
|
43
|
+
체크 값을 고정한다. 개별 `required + disabled`를 허용하면 필수/선택 분모 의미가 바뀌므로
|
|
44
|
+
`descriptor.disabled`를 별도로 제공한다. 기본 false이며 전체/개별 toggle은 잠겼을 때
|
|
45
|
+
동일 selection을 반환하고 renderer는 변경 callback을 발생시키지 않는다. `all`, `satisfied`,
|
|
46
|
+
`missingRequiredIds`와 전문 읽기는 그대로다. onStateChange의 마운트/사용자 토글 계약도
|
|
47
|
+
그대로이며 제어 값·약관 버전 변경 후 판정은 제품이 resolver로 다시 계산한다. UI 잠금은
|
|
48
|
+
서버의 동의 버전 검증·중복 요청 보호를 대신하지 않는다.
|
|
49
|
+
|
|
50
|
+
## 큰 글자 배치 보완 (2026-10-07)
|
|
51
|
+
|
|
52
|
+
긴 전문 버튼이 제목 폭을 빼앗아 한국어가 세로로 끊기는 문제를 확인했다. 제목 기준 폭을 70%로 두고 전문 버튼은 줄바꿈한다. Native는 flexBasis만으로 축소를 막지 못하므로 같은 최소 폭을 적용한다. 체크/혼합 표시도 글꼴 문자가 아닌 고정 크기 도형으로 그려 2배 글자에서 원 밖으로 나가지 않는다. 검증 범위는 [약관 QA](../../../docs/qa/2026-10-07-agreement-lock.md)에 기록한다.
|
package/docs/asset.md
CHANGED
|
@@ -24,3 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
**겹침 비율은 Avatar와 같은 값이다.** 한 줄에 섞여 나오는 것이 이 컴포넌트가 생긴 이유라
|
|
26
26
|
두 값이 다르면 그 줄이 다시 어긋난다.
|
|
27
|
+
|
|
28
|
+
**둥근 액자는 프로필을 따르고 명시한 정사각·원형은 유지한다.** 2026-10-07 3dicons의
|
|
29
|
+
재질/각도 선택과 기존 Asset을 대조하다 양 renderer가 foundation 12px를 직접 사용해
|
|
30
|
+
종이·레트로·사용자 프로필의 radius를 우회함을 확인했다. 미게시 보강에서는 `rounded`를
|
|
31
|
+
가장 가까운 profile의 `tokens.radius.md`로 해석한다. square/circle까지 브랜드 값으로
|
|
32
|
+
바꾸는 대안은 매체의 명시적 geometry를 바꾸므로 사용하지 않는다. 크기·라벨·재생기·자산
|
|
33
|
+
선택의 소유권은 그대로다([조사/재현](../../../docs/qa/2026-10-07-3dicons-page-review.md)).
|
package/docs/brand-boundary.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 브랜드 경계 — 제품이 바꿀 수 있는 것과 그 경로
|
|
2
2
|
|
|
3
|
-
상태: 규범 · 2026-
|
|
3
|
+
상태: 규범 · 검토일: 2026-10-07 · 이 문서가 브랜드·테마 주입 규칙의 단일 원본이다.
|
|
4
4
|
[theming.md](./theming.md)는 사용법 예시, [consumer-policy.md](./consumer-policy.md)는 채택 정책,
|
|
5
5
|
[design-system-provider.md](./design-system-provider.md)는 환경 축 설계를 다루며 브랜드 규칙은 여기로 링크한다.
|
|
6
6
|
|
|
@@ -9,11 +9,11 @@ CSS 변수 재정의, 제품 자체 토큰 생성기)였고, 문서 세 개가
|
|
|
9
9
|
(`.hjm-*` 재정의 금지 vs 특이도를 맞추면 허용, 부분 덮어쓰기 배제 vs `brandPalette` 권장). 그리고 제품 팔레트의
|
|
10
10
|
대비를 아무도 검사하지 않았다. 이 문서가 그 셋을 하나로 정한다.
|
|
11
11
|
|
|
12
|
-
## 1. 지원하는
|
|
12
|
+
## 1. 지원하는 제품 설정 경로
|
|
13
13
|
|
|
14
|
-
- 제품 브랜드는 Provider의 `brandPalette` prop으로 넣는다(`<HjmProvider brandPalette={…}>`,
|
|
14
|
+
- 색만 바꾸는 제품 브랜드는 Provider의 `brandPalette` prop으로 넣는다(`<HjmProvider brandPalette={…}>`,
|
|
15
15
|
`<HjmNativeProvider brandPalette={…}>`, 1.5.0). theme별로 17개 `ThemeColors` key 중 필요한 것만 넘기고,
|
|
16
|
-
나머지는 HJM 기본값을 쓴다(부분 병합). Provider는 계속 OS theme·글자 크기·모션 설정을 따르고,
|
|
16
|
+
나머지는 선택된 프로필 또는 HJM 기본값을 쓴다(부분 병합). Provider는 계속 OS theme·글자 크기·모션 설정을 따르고,
|
|
17
17
|
중첩 Provider는 가장 가까운 상위의 `brandPalette`를 물려받는다.
|
|
18
18
|
- 상태 강조색(`statusAccents`·`statusAccentFills`)은 덮을 수 없다. "오류·성공"과 "브랜드"가 같은 색이 되는
|
|
19
19
|
것을 막는다.
|
|
@@ -24,11 +24,20 @@ CSS 변수 재정의, 제품 자체 토큰 생성기)였고, 문서 세 개가
|
|
|
24
24
|
브랜드 경로가 아니다. 부분 병합으로 표현할 수 없는 요구는 계약 공백이므로 이슈로 올린다.
|
|
25
25
|
17개 key를 모두 채운 `brandPalette`는 허용되지만 아래 대비 검사를 반드시 통과해야 한다.
|
|
26
26
|
|
|
27
|
+
2026-10-07 사용자 요청으로 색뿐 아니라 질감·컴포넌트 기본 전환·구성·화면 배치를 한 번 선택하는
|
|
28
|
+
[디자인 프로필](./design-profile.md)을 추가했다(현재 실험·미게시). `defineHjmDesignProfile`로 정규화한
|
|
29
|
+
앱 소유 데이터를 Provider의 `designProfile`로 주입한다. CSS/전체 value 우회나 테마별 상태 엔진 복제는
|
|
30
|
+
허용하지 않는다. `brandPalette`는 프로필의 해당 light/dark 팔레트 위에 병합된다. 글꼴 자산·로고·문구·데이터는
|
|
31
|
+
제품 소유이며 현재 연결 범위와 미지원 표현은 프로필 계약에 명시한다.
|
|
32
|
+
|
|
27
33
|
## 2. 모든 브랜드 팔레트는 대비 검사를 통과해야 한다
|
|
28
34
|
|
|
29
35
|
`@hjmds/design-contracts/palette-contrast`의 `checkBrandPaletteContrast(brandPalette)`가 Provider와 같은 방식으로
|
|
30
36
|
theme별 병합 결과를 검사하고, theme별 기준 미달 쌍을 돌려준다. 반환 객체의 `light`와 `dark` 배열이 모두 비어 있어야 한다(MUST). 제품은 이 호출을
|
|
31
37
|
자기 테스트에 두어 팔레트 변경마다 실행한다. 전체 팔레트는 `checkPaletteContrast(palette)`를 쓴다.
|
|
38
|
+
프로필도 쓸 때는 기본 팔레트만 검사하는 `checkBrandPaletteContrast` 대신 각 모드의
|
|
39
|
+
`{ ...designProfile.palette[mode], ...brandPalette[mode] }` 최종 결과를 `checkPaletteContrast`로 검사한다.
|
|
40
|
+
프로필 자체의 검증이 뒤에 얹은 브랜드색의 대비까지 보장하지는 않는다.
|
|
32
41
|
|
|
33
42
|
| 쌍 | 최소 | 근거 |
|
|
34
43
|
| --- | --- | --- |
|
|
@@ -84,6 +93,6 @@ HJM 기본 light·dark 팔레트도 같은 검사를 통과한다(`test/palette-
|
|
|
84
93
|
HJM: semantic color role과 light/dark 대비, spacing·type scale·radius·motion·touch target, component anatomy·상태·
|
|
85
94
|
키보드·screen-reader 행동, loading·empty·error·disabled·focus 문법.
|
|
86
95
|
|
|
87
|
-
제품: 이름·로고·대표 이미지, `brandPalette`로 표현한 브랜드 색, 도메인 상태 → `AccentTone` 매핑, 콘텐츠 밀도와
|
|
96
|
+
제품: 이름·로고·대표 이미지, 검증한 `designProfile` 데이터와 `brandPalette`로 표현한 브랜드 색, 도메인 상태 → `AccentTone` 매핑, 콘텐츠 밀도와
|
|
88
97
|
정보 우선순위, copy·i18n·내비게이션 구조. 제품 고유 gradient는 light/dark·on-color 대비와 reduce-motion을 제품 테스트로
|
|
89
98
|
검증한다([consumer-policy.md §4](./consumer-policy.md#4-제품-정체성-경계)).
|