@hjmds/react 1.30.0 → 1.32.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/auth-screen.d.ts +18 -3
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +22 -6
- package/dist/auth-screen.js.map +1 -1
- package/dist/bottom-cta.d.ts +6 -1
- package/dist/bottom-cta.d.ts.map +1 -1
- package/dist/bottom-cta.js +1 -1
- package/dist/bottom-cta.js.map +1 -1
- package/dist/bottom-navigation.d.ts +10 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +80 -8
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/carousel.d.ts +29 -1
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +27 -3
- package/dist/carousel.js.map +1 -1
- package/dist/display.d.ts +8 -2
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +12 -7
- package/dist/display.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +2 -1
- package/dist/forms.js.map +1 -1
- package/dist/image-choice.d.ts +20 -2
- package/dist/image-choice.d.ts.map +1 -1
- package/dist/image-choice.js +60 -7
- package/dist/image-choice.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/field-error.d.ts +19 -0
- package/dist/internal/field-error.d.ts.map +1 -0
- package/dist/internal/field-error.js +19 -0
- package/dist/internal/field-error.js.map +1 -0
- package/dist/internal/initial-focus.d.ts.map +1 -1
- package/dist/internal/initial-focus.js +49 -7
- package/dist/internal/initial-focus.js.map +1 -1
- package/dist/internal/media-album.d.ts +70 -0
- package/dist/internal/media-album.d.ts.map +1 -0
- package/dist/internal/media-album.js +52 -0
- package/dist/internal/media-album.js.map +1 -0
- package/dist/internal/message-reactions.d.ts +39 -2
- package/dist/internal/message-reactions.d.ts.map +1 -1
- package/dist/internal/message-reactions.js +31 -6
- package/dist/internal/message-reactions.js.map +1 -1
- package/dist/layout.d.ts +19 -3
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +10 -3
- package/dist/layout.js.map +1 -1
- package/dist/navigation.d.ts +3 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +4 -2
- package/dist/navigation.js.map +1 -1
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +2 -1
- package/dist/number-field.js.map +1 -1
- package/dist/overlays.d.ts +65 -0
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +36 -3
- package/dist/overlays.js.map +1 -1
- package/dist/pagination.d.ts +13 -11
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +12 -5
- package/dist/pagination.js.map +1 -1
- package/dist/public-exports.generated.d.ts +8 -0
- package/dist/public-exports.generated.d.ts.map +1 -1
- package/dist/public-exports.generated.js +24 -0
- package/dist/public-exports.generated.js.map +1 -1
- package/dist/screen-flows.d.ts +118 -14
- package/dist/screen-flows.d.ts.map +1 -1
- package/dist/screen-flows.js +95 -23
- package/dist/screen-flows.js.map +1 -1
- package/dist/screens.d.ts +69 -10
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +77 -15
- package/dist/screens.js.map +1 -1
- package/dist/select.d.ts.map +1 -1
- package/dist/select.js +2 -1
- package/dist/select.js.map +1 -1
- package/dist/selection.d.ts.map +1 -1
- package/dist/selection.js +3 -2
- package/dist/selection.js.map +1 -1
- package/dist/steps.d.ts +11 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +11 -6
- package/dist/steps.js.map +1 -1
- package/dist/styles.css +181 -22
- package/dist/styles.layered.css +181 -22
- package/dist/supplemental-display.d.ts.map +1 -1
- package/dist/supplemental-display.js +2 -48
- package/dist/supplemental-display.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +1 -0
- package/dist/theme.js.map +1 -1
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +1 -1
- package/dist/toast.js.map +1 -1
- package/package.json +3 -3
package/dist/screens.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { type ScreenInitialFocus } from "@hjmds/design-contracts/screen-patterns";
|
|
1
|
+
import { type ScreenInitialFocus, type IconTileArtwork, type IconTileFrame, type IconTileShape, type IconTileSize } from "@hjmds/design-contracts/screen-patterns";
|
|
2
2
|
import { type ComposerLengthFeedback } from "@hjmds/design-contracts/communication";
|
|
3
3
|
import { type DesignProfileScreenPresentation } from "@hjmds/design-contracts/design-profile-layout";
|
|
4
4
|
import { type MessageMenuAction } from "./internal/message-reactions.js";
|
|
5
|
+
import { type MediaAlbum } from "./internal/media-album.js";
|
|
6
|
+
export type { MediaAlbum, MediaAlbumPageStatusProps } from "./internal/media-album.js";
|
|
7
|
+
export { MediaAlbumPageStatus } from "./internal/media-album.js";
|
|
5
8
|
import type { ReactionPickerProps } from "./reaction-picker.js";
|
|
6
9
|
import { type Ref, type ReactNode } from "react";
|
|
7
|
-
import { type MessageAttachmentDescriptor, type ChatMessageDescriptor, type MessageComposerDescriptor, type ScreenContentState, type ScreenContentInset } from "@hjmds/design-contracts/screen-patterns";
|
|
10
|
+
import { type MessageAttachmentDescriptor, type ChatReactionSummary, type ChatMessageDescriptor, type MessageComposerDescriptor, type ScreenContentState, type ScreenContentInset } from "@hjmds/design-contracts/screen-patterns";
|
|
8
11
|
import { type ScreenExit } from "./internal/screen-exit.js";
|
|
9
12
|
import { type ListRowProps } from "./display.js";
|
|
10
13
|
import type { HjmCompositionStyleProp } from "./composition-style.js";
|
|
@@ -12,13 +15,22 @@ export type ScreenLayoutProps = Readonly<{
|
|
|
12
15
|
title: string;
|
|
13
16
|
/** The shared header owns badge placement; the product supplies its meaning and label. */
|
|
14
17
|
titleBadge?: ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Identity mark or artwork before the title (team logo, tool artwork). Decorative: the title stays the heading and
|
|
20
|
+
* the accessible name. With `titleVisibility="hidden"` the mark stands alone in the title slot as the heading,
|
|
21
|
+
* named by `title` (2026-10-18: yajalal team detail and Utilverse tool headers rebuilt the header to get this).
|
|
22
|
+
*/
|
|
23
|
+
titleLeading?: ReactNode;
|
|
15
24
|
/** Hide redundant route names while retaining descriptions and a named screen. */
|
|
16
25
|
titleVisibility?: "visible" | "hidden";
|
|
17
26
|
/** Centered by default (2026-10-17 user rule); `start` remains available. Extra actions sit at the trailing edge. */
|
|
18
27
|
headerAlignment?: "start" | "center";
|
|
19
28
|
/** Preserve host navigation while the shared shell owns content and state. */
|
|
20
29
|
header?: ReactNode;
|
|
21
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* `none`: host owns every inset. `body-edge`: only the body drops its inline padding so list rows own their inset.
|
|
32
|
+
* `body-bleed`: the body drops every padding (header, notice and footer keep theirs) for an edge-to-edge map or canvas.
|
|
33
|
+
*/
|
|
22
34
|
contentInset?: ScreenContentInset;
|
|
23
35
|
/** Center a product prompt in the available body without inventing a query state. */
|
|
24
36
|
contentAlignment?: "start" | "center";
|
|
@@ -37,6 +49,8 @@ export type ScreenLayoutProps = Readonly<{
|
|
|
37
49
|
footer?: ReactNode;
|
|
38
50
|
state?: ScreenContentState;
|
|
39
51
|
stateAction?: ReactNode;
|
|
52
|
+
/** Brand visual for `state.kind === "loading"`; the shared state group keeps copy, centering and announcement (as CompositionLayout). */
|
|
53
|
+
loadingIndicator?: ReactNode;
|
|
40
54
|
children?: ReactNode;
|
|
41
55
|
/** A virtualized list must own scrolling; never nest it inside a second scroll container. */
|
|
42
56
|
scroll?: "screen" | "content";
|
|
@@ -57,9 +71,7 @@ export type ScreenLayoutProps = Readonly<{
|
|
|
57
71
|
initialFocus?: ScreenInitialFocus;
|
|
58
72
|
}>;
|
|
59
73
|
/** Shared screen shell. Routing, data, permission checks and mutation state remain product-owned. */
|
|
60
|
-
export declare function ScreenLayout({ title, titleBadge, titleVisibility, headerAlignment, header, contentInset, contentAlignment, surface, presentation: suppliedPresentation, description, exit, actions, notice, footer, state, stateAction, children, scroll, as: Element, className, layoutStyle, initialFocus
|
|
61
|
-
hjmTitleLeading?: ReactNode;
|
|
62
|
-
}>): import("react").JSX.Element;
|
|
74
|
+
export declare function ScreenLayout({ title, titleBadge, titleLeading, titleVisibility, headerAlignment, header, contentInset, contentAlignment, surface, presentation: suppliedPresentation, description, exit, actions, notice, footer, state, stateAction, loadingIndicator, children, scroll, as: Element, className, layoutStyle, initialFocus }: ScreenLayoutProps): import("react").JSX.Element;
|
|
63
75
|
/**
|
|
64
76
|
* Style for a product scroller that owns the body with `scroll="content"`: it reaches across the body gutter
|
|
65
77
|
* (`--hjm-screen-body-inline`) and pads it back, so items stay on the gutter while the clip moves to the screen edges and
|
|
@@ -93,7 +105,7 @@ export type NotificationItemProps = Omit<ListRowProps, "description" | "selected
|
|
|
93
105
|
description?: ReactNode;
|
|
94
106
|
}>;
|
|
95
107
|
export declare function NotificationItem({ read, statusLabel, timestamp, description, title, className, ...props }: NotificationItemProps): import("react").JSX.Element;
|
|
96
|
-
export type ChatScreenProps = Omit<ScreenLayoutProps, "footer"> & Readonly<{
|
|
108
|
+
export type ChatScreenProps = Omit<ScreenLayoutProps, "footer" | "titleLeading"> & Readonly<{
|
|
97
109
|
composer: ReactNode;
|
|
98
110
|
/** Shown while the reader is away from the latest message; the host owns scroll position and visibility. */
|
|
99
111
|
jumpToLatest?: Readonly<{
|
|
@@ -195,11 +207,24 @@ export type ChatMessageProps = ChatMessageDescriptor & Readonly<{
|
|
|
195
207
|
disabled?: boolean;
|
|
196
208
|
icon?: ReactNode;
|
|
197
209
|
}>;
|
|
210
|
+
summary?: ChatMessageReactionSummary;
|
|
198
211
|
}>;
|
|
199
212
|
/** Canonical layout-only placement on the message row. The swipe offset still owns `transform`. */
|
|
200
213
|
layoutStyle?: HjmCompositionStyleProp;
|
|
214
|
+
}> & Readonly<{
|
|
215
|
+
/**
|
|
216
|
+
* Paged alternative to `media` (2026-10-18, Utilverse): HJM mounts only the shown photo, so a product that authorizes
|
|
217
|
+
* each image fetches one page at a time. Exclusive with `media` (validated at render): one message has one photo set.
|
|
218
|
+
*/
|
|
219
|
+
mediaAlbum?: MediaAlbum;
|
|
201
220
|
}>;
|
|
202
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Current reactions drawn beside the time as one button that opens the same hold menu (2026-10-18 BurnTok DM, which
|
|
223
|
+
* showed counts as status tags because the menu could not be opened from outside). Counts and the localized
|
|
224
|
+
* accessible name ("Reactions: heart 2. Open menu") come from the product; `items` in display order.
|
|
225
|
+
*/
|
|
226
|
+
export type ChatMessageReactionSummary = ChatReactionSummary;
|
|
227
|
+
export declare function ChatMessage({ direction, author, timestamp, timestampPresentation, bubbleTail, groupPosition, deliveryLabel, avatar, reply, replyQuote, media, mediaAlbum, actions, replyAction, replyLink, reactions, children, interactiveContent, layoutStyle }: ChatMessageProps): import("react").JSX.Element;
|
|
203
228
|
/** Shared structure for embedded compositions. The parent supplies size; this never creates a second scroller. */
|
|
204
229
|
export type CompositionLayoutProps = Readonly<{
|
|
205
230
|
title?: string;
|
|
@@ -231,12 +256,46 @@ export type NotificationRowProps = Readonly<{
|
|
|
231
256
|
/** Router host owns the sole target and localized accessible name, as with ConversationRow. */
|
|
232
257
|
export declare function NotificationRow({ preview, metadata, avatar, avatarBadge, thumbnail, unread }: NotificationRowProps): import("react").JSX.Element;
|
|
233
258
|
export type IconTileProps = Readonly<{
|
|
234
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Icon artwork. A function receives the rendered edge (`IconTileArtwork`) so fixed-size artwork such as an image
|
|
261
|
+
* sizes itself to a `size="fill"` icon without the product re-computing the cell clamp.
|
|
262
|
+
*/
|
|
263
|
+
icon: ReactNode | ((artwork: IconTileArtwork) => ReactNode);
|
|
235
264
|
label: string;
|
|
236
265
|
overlay?: ReactNode;
|
|
237
266
|
labelLines?: 1 | 2;
|
|
238
267
|
ghost?: boolean;
|
|
268
|
+
/** `none`: the icon is complete product artwork, so no frame fill or hairline is drawn. */
|
|
269
|
+
frame?: IconTileFrame;
|
|
270
|
+
/** Frame (and ghost outline) corner shape from the product's icon theme. */
|
|
271
|
+
shape?: IconTileShape;
|
|
272
|
+
/** `fill`: the icon spans the grid cell between iconTileRecipe.fillMinSize and fillMaxSize. */
|
|
273
|
+
size?: IconTileSize;
|
|
239
274
|
}>;
|
|
240
275
|
/** Presentation shared by launcher and profile grids; the host owns navigation or drag behavior. */
|
|
241
|
-
export declare function IconTile({ icon, label, overlay, labelLines, ghost }: IconTileProps): import("react").JSX.Element;
|
|
276
|
+
export declare function IconTile({ icon, label, overlay, labelLines, ghost, frame, shape, size }: IconTileProps): import("react").JSX.Element;
|
|
277
|
+
export type TypingIndicatorProps = Readonly<{
|
|
278
|
+
/** Localized status read by assistive tech ("Seoyeon is typing"); the visible row shows only the dots. */
|
|
279
|
+
label: string;
|
|
280
|
+
/** Typer's photo in a group room, sized like ChatMessage avatars (`chatMessageRecipe.avatarSize`). */
|
|
281
|
+
avatar?: ReactNode;
|
|
282
|
+
layoutStyle?: HjmCompositionStyleProp;
|
|
283
|
+
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Incoming "is typing" row for a ChatScreen timeline (2026-10-18 BurnTok DM drew its own). The host shows it only while
|
|
286
|
+
* presence says someone is typing and places it after the last message; HJM owns the bubble, dots and announcement.
|
|
287
|
+
*/
|
|
288
|
+
export declare function TypingIndicator({ label, avatar, layoutStyle }: TypingIndicatorProps): import("react").JSX.Element;
|
|
289
|
+
export type ChatDateSeparatorProps = Readonly<{
|
|
290
|
+
/** Product-formatted date or time ("Today", "Oct 18", "3:04 PM"). */
|
|
291
|
+
label: string;
|
|
292
|
+
/** Machine-readable value for the Web `<time>` element; Native ignores it. */
|
|
293
|
+
dateTime?: string;
|
|
294
|
+
layoutStyle?: HjmCompositionStyleProp;
|
|
295
|
+
}>;
|
|
296
|
+
/**
|
|
297
|
+
* Centered date (or time) row between ChatScreen timeline groups (2026-10-18 Utilverse chat drew a caption). Grouping
|
|
298
|
+
* rules stay with the product; the next message keeps its own group gap, so this row adds only the space before it.
|
|
299
|
+
*/
|
|
300
|
+
export declare function ChatDateSeparator({ label, dateTime, layoutStyle }: ChatDateSeparatorProps): import("react").JSX.Element;
|
|
242
301
|
//# sourceMappingURL=screens.d.ts.map
|
package/dist/screens.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screens.d.ts","sourceRoot":"","sources":["../src/screens.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqF,KAAK,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"screens.d.ts","sourceRoot":"","sources":["../src/screens.tsx"],"names":[],"mappings":"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"}
|
package/dist/screens.js
CHANGED
|
@@ -5,29 +5,34 @@ import { iconButtonRecipe, listRowRecipe } from "@hjmds/design-contracts/recipes
|
|
|
5
5
|
import { resolveComposerLimit } from "@hjmds/design-contracts/communication";
|
|
6
6
|
import { Progress } from "./feedback.js";
|
|
7
7
|
import { resolveDesignProfileScreen } from "@hjmds/design-contracts/design-profile-layout";
|
|
8
|
-
import { MessageReactions } from "./internal/message-reactions.js";
|
|
8
|
+
import { MessageReactions, ReactionSummaryButton, ReactionSummaryText, omitReactionSummary } from "./internal/message-reactions.js";
|
|
9
|
+
import { MediaAlbumView, useMediaAlbumIndex } from "./internal/media-album.js";
|
|
10
|
+
import { validateMediaSetChoice } from "@hjmds/design-contracts/screen-patterns";
|
|
11
|
+
// Loading/failed page rendered inside `mediaAlbum.renderPage` (2026-10-18); comment rows use the same component.
|
|
12
|
+
export { MediaAlbumPageStatus } from "./internal/media-album.js";
|
|
9
13
|
import { Button, IconButton } from "./actions.js";
|
|
10
14
|
import { useOptionalHjmTheme } from "./provider.js";
|
|
11
15
|
import { TextArea } from "./forms.js";
|
|
12
16
|
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
13
|
-
import { isReplySwipe, timestampRevealOffset, shouldSubmitMessageKey, canSubmitMessage, validateMessageAttachments, resolveScreenContentState, screenPatternRecipe, chatMessageRecipe, composerQuickInsertRecipe, resolveChatMessageGroupDisplay, resolveChatMediaColumns, screenHeaderRecipe, validateScreenExit } from "@hjmds/design-contracts/screen-patterns";
|
|
17
|
+
import { isReplySwipe, timestampRevealOffset, shouldSubmitMessageKey, canSubmitMessage, validateMessageAttachments, resolveScreenContentState, screenPatternRecipe, chatMessageRecipe, composerQuickInsertRecipe, resolveChatMessageGroupDisplay, resolveChatMediaColumns, resolveChatReactionSummary, typingIndicatorRecipe, chatDateSeparatorRecipe, screenHeaderRecipe, validateScreenExit } from "@hjmds/design-contracts/screen-patterns";
|
|
14
18
|
import { Heading } from "./heading.js";
|
|
15
19
|
import { Spinner } from "./internal/spinner.js";
|
|
16
20
|
import { ScreenExitButton } from "./internal/screen-exit.js";
|
|
17
21
|
import { ScreenFooterContext } from "./internal/screen-footer.js";
|
|
18
|
-
import { Section, Stack, Text } from "./layout.js";
|
|
22
|
+
import { Section, Stack, Text, VisuallyHidden } from "./layout.js";
|
|
19
23
|
import { ListRow } from "./display.js";
|
|
20
24
|
import { classNames, useDesignProfileDefaults } from "./internal.js";
|
|
21
25
|
/** Shared screen shell. Routing, data, permission checks and mutation state remain product-owned. */
|
|
22
|
-
export function ScreenLayout({ title, titleBadge, titleVisibility = "visible", headerAlignment = "center", header, contentInset = "default", contentAlignment = "start", surface = "default", presentation: suppliedPresentation, description, exit, actions, notice, footer, state = { kind: "ready" }, stateAction, children, scroll = "screen", as: Element = "main", className, layoutStyle, initialFocus = initialFocusDefaults.screen
|
|
26
|
+
export function ScreenLayout({ title, titleBadge, titleLeading, titleVisibility = "visible", headerAlignment = "center", header, contentInset = "default", contentAlignment = "start", surface = "default", presentation: suppliedPresentation, description, exit, actions, notice, footer, state = { kind: "ready" }, stateAction, loadingIndicator, children, scroll = "screen", as: Element = "main", className, layoutStyle, initialFocus = initialFocusDefaults.screen }) {
|
|
23
27
|
const id = useId();
|
|
24
28
|
if (exit)
|
|
25
29
|
validateScreenExit(exit, false);
|
|
26
30
|
const leading = exit ? _jsx(ScreenExitButton, { ...exit }) : null;
|
|
27
|
-
const
|
|
31
|
+
const hasMark = titleLeading !== undefined && titleLeading !== null && titleLeading !== false;
|
|
32
|
+
const showHeader = titleVisibility === "visible" || hasMark || !!description || !!leading || !!actions;
|
|
28
33
|
// Same header band as Native (screenHeaderRecipe): a fixed 52 band (48 icon-only) so the title sits at the same
|
|
29
34
|
// height with or without exit/actions buttons (2026-10-17: Web grew 58→76 when a 44 button was present).
|
|
30
|
-
const compactHeader = header == null && headerAlignment === "center" && titleVisibility === "hidden" && !description;
|
|
35
|
+
const compactHeader = header == null && headerAlignment === "center" && titleVisibility === "hidden" && !hasMark && !description;
|
|
31
36
|
// Screens historically work with the stylesheet alone. Optional context keeps
|
|
32
37
|
// that path while inheriting profiles from an enclosing Provider when present.
|
|
33
38
|
const designProfile = useDesignProfileDefaults();
|
|
@@ -50,13 +55,18 @@ export function ScreenLayout({ title, titleBadge, titleVisibility = "visible", h
|
|
|
50
55
|
lastBodyFocus.current = null;
|
|
51
56
|
});
|
|
52
57
|
const recipe = screenPatternRecipe;
|
|
53
|
-
|
|
58
|
+
// The mark is decorative beside a visible title; standing alone it becomes the heading, named by `title`.
|
|
59
|
+
const mark = hasMark ? _jsx("span", { className: "hjm-screen__title-leading", "aria-hidden": "true", children: titleLeading }) : null;
|
|
60
|
+
const titleRow = (level, justify) => titleVisibility === "visible"
|
|
61
|
+
? _jsxs(Stack, { axis: "inline", gap: "xs", align: "center", justify: justify, wrap: true, children: [mark, _jsx(Heading, { level: level, semanticLevel: 1, id: id, children: title }), titleBadge] })
|
|
62
|
+
: mark ? _jsx("div", { role: "heading", "aria-level": 1, "aria-label": title, style: { display: "flex", justifyContent: justify === "center" ? "center" : "flex-start" }, children: mark }) : null;
|
|
63
|
+
return _jsx(Element, { "aria-labelledby": header || titleVisibility === "hidden" ? undefined : id, "aria-label": header || titleVisibility === "hidden" ? title : undefined, "data-content-inset": contentInset, "data-presentation": presentation, className: classNames("hjm-screen", className), style: { backgroundColor: surface === "transparent" || designProfile?.material.canvas ? "transparent" : undefined, "--hjm-screen-width": `${profileLayout?.maxWidth ?? recipe.maxWidth}px`, "--hjm-screen-padding": `${contentInset === "none" ? 0 : recipe.padding}px`, "--hjm-screen-gap": `${profileLayout?.gap ?? recipe.sectionGap}px`, "--hjm-screen-item-gap": `${recipe.itemGap}px`, "--hjm-screen-state-gap": `${recipe.stateGap}px`, "--hjm-screen-header-min": `${recipe.headerMinWidth}px`, ...layoutStyle }, children: _jsxs(InitialFocusScope, { mode: initialFocus, children: [header ?? (showHeader ? headerAlignment === "center" ? _jsxs("header", { className: "hjm-screen__header", style: { display: "grid", gridTemplateColumns: "1fr minmax(0, auto) 1fr", alignItems: "center", boxSizing: "border-box", minBlockSize: compactHeader ? screenHeaderRecipe.compactMinHeight : screenHeaderRecipe.minHeight, paddingBlock: compactHeader ? screenHeaderRecipe.compactPaddingVertical : screenHeaderRecipe.paddingVertical }, children: [_jsx("div", { children: leading }), _jsxs("div", { className: "hjm-screen__heading", style: { textAlign: "center", minWidth: 0 }, children: [titleRow(screenHeaderRecipe.centeredTitleLevel, "center"), description ? _jsx(Text, { as: "p", tone: "muted", children: description }) : null] }), _jsx("div", { style: { justifySelf: "end" }, children: actions })] }) : _jsxs("header", { className: "hjm-screen__header", style: profileLayout ? { flexDirection: profileLayout.headerAxis, alignItems: profileLayout.centered ? "center" : undefined, textAlign: profileLayout.centered ? "center" : undefined } : undefined, children: [leading, _jsxs("div", { className: "hjm-screen__heading", children: [titleRow(screenHeaderRecipe.titleLevel, profileLayout?.centered ? "center" : "start"), description ? _jsx(Text, { as: "p", tone: "muted", children: description }) : null] }), actions] }) : null), notice ? _jsx("div", { className: "hjm-screen__notice", children: notice }) : null, _jsx("div", { ref: bodyRef, role: "region", "aria-label": title, tabIndex: bodyScroll === "content" ? -1 : 0, onFocusCapture: event => { lastBodyFocus.current = event.target; }, onBlurCapture: event => {
|
|
54
64
|
// A connected input blurred to the document intentionally relinquishes
|
|
55
65
|
// focus too; only removal of the currently focused node needs recovery.
|
|
56
66
|
if (event.relatedTarget ? !event.currentTarget.contains(event.relatedTarget) : event.target.isConnected) {
|
|
57
67
|
lastBodyFocus.current = null;
|
|
58
68
|
}
|
|
59
|
-
}, className: "hjm-screen__body", "data-content-alignment": contentAlignment, "data-scroll": bodyScroll, "data-replacement": resolved.replacesContent || undefined, "aria-busy": resolved.busy || undefined, children: state.kind === "ready" ? (contentAlignment === "center" ? _jsx("div", { className: "hjm-screen__centered-content", children: children }) : children) : _jsxs("div", { className: "hjm-screen__state", children: [resolved.busy ? _jsx(Spinner, { label: [state.title, state.description].filter(Boolean).join(". ") }) : _jsx("div", { role: state.kind === "error" ? "alert" : "status", children: _jsxs(Stack, { gap: "sm", align: "center", children: [_jsx(Text, { variant: "title", children: state.title }), state.description ? _jsx(Text, { tone: "muted", children: state.description }) : null] }) }), stateAction] }) }), footer ? _jsx("footer", { className: "hjm-screen__footer", children: _jsx(ScreenFooterContext.Provider, { value: contentInset !== "none", children: footer }) }) : null] }) });
|
|
69
|
+
}, className: "hjm-screen__body", "data-content-alignment": contentAlignment, "data-scroll": bodyScroll, "data-replacement": resolved.replacesContent || undefined, "aria-busy": resolved.busy || undefined, children: state.kind === "ready" ? (contentAlignment === "center" ? _jsx("div", { className: "hjm-screen__centered-content", children: children }) : children) : _jsxs("div", { className: "hjm-screen__state", children: [resolved.busy ? (loadingIndicator ? _jsx("div", { role: "status", "aria-busy": "true", "aria-label": [state.title, state.description].filter(Boolean).join(". "), children: _jsx("div", { "aria-hidden": "true", children: loadingIndicator }) }) : _jsx(Spinner, { label: [state.title, state.description].filter(Boolean).join(". ") })) : _jsx("div", { role: state.kind === "error" ? "alert" : "status", children: _jsxs(Stack, { gap: "sm", align: "center", children: [_jsx(Text, { variant: "title", children: state.title }), state.description ? _jsx(Text, { tone: "muted", children: state.description }) : null] }) }), stateAction] }) }), footer ? _jsx("footer", { className: "hjm-screen__footer", children: _jsx(ScreenFooterContext.Provider, { value: contentInset !== "none", children: footer }) }) : null] }) });
|
|
60
70
|
}
|
|
61
71
|
/**
|
|
62
72
|
* Style for a product scroller that owns the body with `scroll="content"`: it reaches across the body gutter
|
|
@@ -83,7 +93,7 @@ export function ChatScreen({ composer, jumpToLatest, avatar, scroll = "content",
|
|
|
83
93
|
const showComposer = !props.state || props.state.kind === "ready" || props.state.kind === "empty";
|
|
84
94
|
// Anchor the jump control to the composer edge so it floats over the timeline without measuring the footer.
|
|
85
95
|
// Conversations center the peer between back and tools and use the measured 10px timeline gutter (2026-10-16).
|
|
86
|
-
return _jsx(ScreenLayout, { ...props, className: classNames("hjm-chat-screen", props.className) ?? "hjm-chat-screen",
|
|
96
|
+
return _jsx(ScreenLayout, { ...props, className: classNames("hjm-chat-screen", props.className) ?? "hjm-chat-screen", titleLeading: avatar, scroll: scroll, footer: showComposer ? _jsxs("div", { className: "hjm-chat-screen__footer", children: [jumpToLatest ? _jsx("div", { className: "hjm-chat-screen__jump", children: _jsx(IconButton, { label: jumpToLatest.label, size: "small", shape: "circle", tone: "secondary", onClick: jumpToLatest.onPress, children: jumpToLatest.icon }) }) : null, composer] }) : null });
|
|
87
97
|
}
|
|
88
98
|
export function MessageComposer({ leading, quickInserts, value, label, sendLabel, placeholder = label, description, error, invalid = false, submitMode = "newline", onBlur, disabled = false, pending = false, onValueChange, onSend, maxLength, lengthFeedback, sendDisabled = false, additionalContent = false, leadingAction, inputRef, context, replyTo, sendIcon, sendPresentation = "inline", attachmentAction, attachments = [], onRemoveAttachment, layoutStyle }) {
|
|
89
99
|
validateMessageAttachments(attachments);
|
|
@@ -111,24 +121,33 @@ export function MessageComposer({ leading, quickInserts, value, label, sendLabel
|
|
|
111
121
|
} }, onChange: event => { if (!locked)
|
|
112
122
|
onValueChange(event.currentTarget.value); }, minVisibleLines: screenPatternRecipe.composerMinLines, maxVisibleLines: screenPatternRecipe.composerMaxLines, ...(sendIcon ? { trailing: _jsx("span", { className: "hjm-message-composer__trailing", style: { minInlineSize: sendDiameter, minBlockSize: sendDiameter }, children: _jsx("span", { className: "hjm-message-composer__visible", children: hasContent || pending ? send : attach }) }) } : {}) }), sendIcon ? null : send] })] });
|
|
113
123
|
const quickRow = quickInserts?.items.length ? _jsx("div", { className: "hjm-message-composer__quick", role: "group", "aria-label": quickInserts.label, style: { "--hjm-quick-size": `${composerQuickInsertRecipe.itemSize}px`, "--hjm-quick-emoji": `${composerQuickInsertRecipe.emojiSize}px` }, children: quickInserts.items.map(item => _jsx("button", { type: "button", className: "hjm-message-composer__quick-item", "aria-label": item.label, disabled: locked, onClick: () => onValueChange(value + item.emoji), children: item.emoji }, item.id)) }) : null;
|
|
124
|
+
// The error is announced by TextArea's shared Field error region (internal/field-error.tsx); no composer-only copy.
|
|
114
125
|
return _jsxs("div", { className: "hjm-message-composer", "data-send-presentation": sendPresentation, style: { "--hjm-message-composer-gap": `${screenPatternRecipe.itemGap}px`, "--hjm-attachment-size": `${screenPatternRecipe.attachmentSize}px`, "--hjm-attachment-remove-size": `${screenPatternRecipe.attachmentRemoveSize}px`, ...layoutStyle }, children: [context, quickRow, _jsxs("div", { className: "hjm-message-composer__reply-card", "data-replying": replyTo ? true : undefined, children: [replyTo ? _jsxs("div", { className: "hjm-message-composer__reply", children: [_jsxs("div", { className: "hjm-message-composer__reply-text", children: [_jsx(Text, { emphasis: "strong", children: replyTo.author }), _jsx(Text, { variant: "caption", tone: "muted", className: "hjm-message-composer__reply-excerpt", children: replyTo.excerpt })] }), _jsx(IconButton, { label: replyTo.cancelLabel, tone: "secondary", size: "small", shape: "circle", disabled: locked, onClick: replyTo.onCancel, children: _jsx(Text, { children: "\u00D7" }) })] }) : null, composerBody] }), lengthFeedback && limit?.near ? _jsx(Progress, { shape: "circular", label: lengthFeedback.label, value: Math.min(limit.used, limit.max), max: limit.max, valueText: lengthFeedback.formatCount(limit.used, limit.max), children: _jsx(Text, { variant: "caption", children: lengthFeedback.formatCount(limit.used, limit.max) }) }) : null] });
|
|
115
126
|
}
|
|
116
|
-
export function ChatMessage({ direction, author, timestamp, timestampPresentation = "always", bubbleTail = true, groupPosition = "single", deliveryLabel, avatar, reply, replyQuote, media = [], actions, replyAction, replyLink, reactions, children, interactiveContent = false, layoutStyle }) {
|
|
127
|
+
export function ChatMessage({ direction, author, timestamp, timestampPresentation = "always", bubbleTail = true, groupPosition = "single", deliveryLabel, avatar, reply, replyQuote, media = [], mediaAlbum, actions, replyAction, replyLink, reactions, children, interactiveContent = false, layoutStyle }) {
|
|
117
128
|
const theme = useOptionalHjmTheme();
|
|
118
129
|
const display = resolveChatMessageGroupDisplay(groupPosition);
|
|
119
130
|
// A run shows its time once, beside its last bubble; delivery state stays on every bubble.
|
|
120
131
|
const visibleTime = display.showTimestamp ? timestamp : "";
|
|
121
132
|
const quote = replyQuote ? _jsxs(_Fragment, { children: [_jsx(Text, { emphasis: "strong", children: replyQuote.title }), _jsx(Text, { tone: "muted", className: "hjm-chat-message__quote-excerpt", children: replyQuote.excerpt })] }) : reply;
|
|
122
133
|
// The quote stays inside the bubble; with replyLink a click on it jumps to the original (2026-10-16 reference).
|
|
123
|
-
|
|
134
|
+
validateMediaSetChoice(media.length > 0, mediaAlbum !== undefined);
|
|
135
|
+
const [albumIndex, setAlbumIndex] = useMediaAlbumIndex(mediaAlbum);
|
|
136
|
+
const mediaBlock = mediaAlbum ? _jsx(MediaAlbumView, { album: mediaAlbum, index: albumIndex, onIndexChange: setAlbumIndex, width: chatMessageRecipe.mediaWidth, radius: chatMessageRecipe.bubbleRadius })
|
|
137
|
+
: media.length ? _jsx("div", { className: "hjm-chat-message__media", style: { inlineSize: chatMessageRecipe.mediaWidth, gap: chatMessageRecipe.mediaGap, gridTemplateColumns: `repeat(${resolveChatMediaColumns(media.length)}, minmax(0, 1fr))` }, children: media.map(item => { const style = { aspectRatio: media.length > 1 ? 1 : item.aspectRatio ?? 1 }; return item.onPress ? _jsx("button", { type: "button", className: "hjm-chat-message__media-item", "aria-label": item.label, style: style, onClick: item.onPress, children: item.preview }, item.id) : _jsx("div", { role: "img", "aria-label": item.label, className: "hjm-chat-message__media-item", style: style, children: item.preview }, item.id); }) }) : null;
|
|
124
138
|
// A photo-only message drops the bubble surface so the photo itself is the message.
|
|
125
|
-
const photoOnly = media.length > 0 && !reply && !replyQuote && (children === null || children === undefined || children === false || children === "");
|
|
139
|
+
const photoOnly = (media.length > 0 || mediaAlbum !== undefined) && !reply && !replyQuote && (children === null || children === undefined || children === false || children === "");
|
|
126
140
|
const quoteBlock = quote ? (replyLink ? _jsx("button", { type: "button", className: "hjm-chat-message__reply hjm-chat-message__quote", "aria-label": replyLink.label, onClick: replyLink.onPress, children: quote }) : _jsx("div", { className: "hjm-chat-message__reply", children: quote })) : null;
|
|
127
141
|
const revealTime = timestampPresentation === "swipe" && !!timestamp;
|
|
128
142
|
// Only the pointer that began a drag can finish it; another touch must not reply or cancel it.
|
|
129
143
|
const start = useRef(null);
|
|
130
144
|
const [offset, setOffset] = useState(0);
|
|
131
145
|
const finish = () => { start.current = null; setOffset(0); };
|
|
146
|
+
const menu = useRef(null);
|
|
147
|
+
const summary = reactions?.summary;
|
|
148
|
+
const reactionMenu = reactions ? omitReactionSummary(reactions) : undefined;
|
|
149
|
+
const summaryItems = summary ? resolveChatReactionSummary(summary) : [];
|
|
150
|
+
const summaryButton = reactions && summary && summaryItems.length ? (summary.readOnly ? _jsx(ReactionSummaryText, { label: summary.label, items: summaryItems }) : _jsx(ReactionSummaryButton, { label: summary.label, items: summaryItems, selected: reactions.value, disabled: reactions.disabled ?? false, onPress: () => menu.current?.open() })) : null;
|
|
132
151
|
return _jsxs("article", { tabIndex: replyAction && !replyAction.disabled ? 0 : undefined, "aria-keyshortcuts": replyAction ? "Alt+ArrowLeft Alt+ArrowRight" : undefined, "aria-description": replyAction?.label, onKeyDown: event => { if (event.target === event.currentTarget && event.altKey && (event.key === "ArrowLeft" || event.key === "ArrowRight") && replyAction && !replyAction.disabled) {
|
|
133
152
|
event.preventDefault();
|
|
134
153
|
replyAction.onPress();
|
|
@@ -146,7 +165,7 @@ export function ChatMessage({ direction, author, timestamp, timestampPresentatio
|
|
|
146
165
|
finish(); }, onPointerLeave: event => { if (start.current?.pointerId === event.pointerId)
|
|
147
166
|
finish(); }, style: { ...layoutStyle, position: "relative", overflow: "hidden", marginBlockStart: display.spacingBefore,
|
|
148
167
|
// Reserve tip room only where it protrudes (own trailing edge, or leading edge without an avatar column).
|
|
149
|
-
...(bubbleTail ? (direction === "outgoing" ? { paddingInlineEnd: chatMessageRecipe.tailWidth } : avatar ? {} : { paddingInlineStart: chatMessageRecipe.tailWidth }) : {}), touchAction: revealTime || replyAction ? "pan-y" : undefined }, "aria-label": author, children: [revealTime ? _jsx("time", { style: { position: "absolute", ...(direction === "outgoing" ? { right: 0, justifyContent: "flex-end" } : { left: 0 }), top: 0, bottom: 0, width: 80, display: "flex", alignItems: "center", opacity: offset !== 0 ? 1 : 0, pointerEvents: "none" }, children: _jsx(Text, { variant: "caption", tone: "muted", children: timestamp }) }) : null, _jsxs("div", { className: "hjm-chat-message", style: { transform: `translateX(${offset}px)`, transition: start.current || theme?.environment.reducedMotion ? "none" : "transform var(--hjm-motion-normal, 200ms) ease-out", "--hjm-message-max-width": screenPatternRecipe.messageMaxWidth, "--hjm-chat-avatar-size": `${chatMessageRecipe.avatarSize}px` }, "data-direction": direction, "data-tail": (bubbleTail && display.showTail) || undefined, children: [avatar ? _jsx("div", { className: "hjm-chat-message__avatar", "aria-hidden": display.showAvatar ? undefined : true, children: display.showAvatar ? avatar : null }) : null, _jsxs("div", { className: "hjm-chat-message__content", children: [author && display.showAuthor && direction !== "outgoing" ? _jsx(Text, { variant: "caption", tone: "muted", children: author }) : null, _jsxs("div", { className: "hjm-chat-message__body", children: [
|
|
168
|
+
...(bubbleTail ? (direction === "outgoing" ? { paddingInlineEnd: chatMessageRecipe.tailWidth } : avatar ? {} : { paddingInlineStart: chatMessageRecipe.tailWidth }) : {}), touchAction: revealTime || replyAction ? "pan-y" : undefined }, "aria-label": author, children: [revealTime ? _jsx("time", { style: { position: "absolute", ...(direction === "outgoing" ? { right: 0, justifyContent: "flex-end" } : { left: 0 }), top: 0, bottom: 0, width: 80, display: "flex", alignItems: "center", opacity: offset !== 0 ? 1 : 0, pointerEvents: "none" }, children: _jsx(Text, { variant: "caption", tone: "muted", children: timestamp }) }) : null, _jsxs("div", { className: "hjm-chat-message", style: { transform: `translateX(${offset}px)`, transition: start.current || theme?.environment.reducedMotion ? "none" : "transform var(--hjm-motion-normal, 200ms) ease-out", "--hjm-message-max-width": screenPatternRecipe.messageMaxWidth, "--hjm-chat-avatar-size": `${chatMessageRecipe.avatarSize}px` }, "data-direction": direction, "data-tail": (bubbleTail && display.showTail) || undefined, children: [avatar ? _jsx("div", { className: "hjm-chat-message__avatar", "aria-hidden": display.showAvatar ? undefined : true, children: display.showAvatar ? avatar : null }) : null, _jsxs("div", { className: "hjm-chat-message__content", children: [author && display.showAuthor && direction !== "outgoing" ? _jsx(Text, { variant: "caption", tone: "muted", children: author }) : null, _jsxs("div", { className: "hjm-chat-message__body", children: [reactionMenu ? _jsxs(MessageReactions, { ...reactionMenu, menuRef: menu, interactiveContent: interactiveContent || mediaAlbum !== undefined, ...(timestamp ? { menuTitle: timestamp } : {}), ...(replyAction && !replyAction.disabled ? { replyAction } : {}), children: [quoteBlock, mediaBlock, photoOnly ? null : children] }) : _jsxs("div", { className: "hjm-chat-message__bubble", "data-photo-only": photoOnly || undefined, children: [quoteBlock, mediaBlock, photoOnly ? null : children] }), (!revealTime && visibleTime) || deliveryLabel || actions || summaryButton ? _jsxs("div", { className: "hjm-chat-message__meta", children: [summaryButton, (!revealTime && visibleTime) || deliveryLabel ? _jsx(Text, { variant: "caption", tone: "muted", children: [revealTime ? "" : visibleTime, deliveryLabel].filter(Boolean).join(" · ") }) : null, actions] }) : null] })] })] })] });
|
|
150
169
|
}
|
|
151
170
|
export function CompositionLayout({ title, headingLevel = 3, description, badge, leading, actions, notice, children, footer, state = { kind: "ready" }, stateAction, loadingIndicator, layoutStyle }) {
|
|
152
171
|
const resolved = resolveScreenContentState(state);
|
|
@@ -158,8 +177,51 @@ export function NotificationRow({ preview, metadata, avatar, avatarBadge, thumbn
|
|
|
158
177
|
return _jsxs("span", { "aria-hidden": "true", "data-hjm-notification-row": "true", style: { display: "flex", alignItems: "center", gap: r.gap, padding: `${r.paddingBlock}px ${r.paddingInline}px`, width: "100%", minWidth: 0, boxSizing: "border-box" }, children: [_jsxs("span", { style: { position: "relative", width: r.avatarSize, height: r.avatarSize, flexShrink: 0 }, children: [_jsx("span", { style: { display: "flex", width: "100%", height: "100%", overflow: "hidden", borderRadius: "50%" }, children: avatar }), avatarBadge ? _jsx("span", { style: { position: "absolute", bottom: 0, right: 0, display: "flex", alignItems: "center", justifyContent: "center", width: r.avatarBadgeSize, height: r.avatarBadgeSize, borderRadius: "50%", background: "var(--hjm-color-surface)", border: "2px solid var(--hjm-color-surface)", boxSizing: "border-box" }, children: avatarBadge }) : null] }), _jsxs("span", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: r.contentGap }, children: [_jsx(Text, { style: { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: r.previewLines, overflow: "hidden" }, children: preview }), _jsxs("span", { style: { display: "flex", alignItems: "center", gap: r.contentGap }, children: [_jsx(Text, { variant: "caption", tone: "muted", children: metadata }), unread ? _jsx("span", { "data-hjm-unread-dot": "true", style: { width: r.unreadSize, height: r.unreadSize, borderRadius: "50%", flexShrink: 0, background: "var(--hjm-color-content-brand)" } }) : null] })] }), thumbnail ? _jsx("span", { style: { width: r.thumbnailSize, height: r.thumbnailSize, flexShrink: 0, overflow: "hidden", borderRadius: "var(--hjm-radius-sm)" }, children: thumbnail }) : null] });
|
|
159
178
|
}
|
|
160
179
|
/** Presentation shared by launcher and profile grids; the host owns navigation or drag behavior. */
|
|
161
|
-
export function IconTile({ icon, label, overlay, labelLines = 1, ghost = false }) {
|
|
180
|
+
export function IconTile({ icon, label, overlay, labelLines = 1, ghost = false, frame = iconTileRecipe.defaults.frame, shape = iconTileRecipe.defaults.shape, size = iconTileRecipe.defaults.size }) {
|
|
162
181
|
const r = iconTileRecipe;
|
|
163
|
-
|
|
182
|
+
const framed = frame === "surface";
|
|
183
|
+
// Percent radius follows the rendered size, so a filling icon keeps its shape at every cell width.
|
|
184
|
+
const dimensions = size === "fill" ? { width: "100%", minWidth: r.fillMinSize, maxWidth: r.fillMaxSize, aspectRatio: "1" } : { width: r.iconSize, height: r.iconSize };
|
|
185
|
+
// A render-function icon needs the filling edge as a number. It is read before paint and on every resize; the
|
|
186
|
+
// computed width ignores transforms (a host's press scale) and keeps fractional cell widths.
|
|
187
|
+
const frameRef = useRef(null);
|
|
188
|
+
const measureEdge = size === "fill" && typeof icon === "function";
|
|
189
|
+
const [fillEdge, setFillEdge] = useState(r.fillMinSize);
|
|
190
|
+
useLayoutEffect(() => {
|
|
191
|
+
const element = frameRef.current;
|
|
192
|
+
if (!measureEdge || !element)
|
|
193
|
+
return;
|
|
194
|
+
setFillEdge(Number.parseFloat(getComputedStyle(element).width) || r.fillMinSize);
|
|
195
|
+
if (typeof ResizeObserver === "undefined")
|
|
196
|
+
return;
|
|
197
|
+
const observer = new ResizeObserver(([entry]) => { const width = entry?.borderBoxSize?.[0]?.inlineSize; if (width)
|
|
198
|
+
setFillEdge(width); });
|
|
199
|
+
observer.observe(element);
|
|
200
|
+
return () => observer.disconnect();
|
|
201
|
+
}, [measureEdge, r.fillMinSize]);
|
|
202
|
+
const edge = size === "fill" ? fillEdge : r.iconSize;
|
|
203
|
+
return _jsxs("span", { "data-hjm-icon-tile": "true", "data-frame": frame, "data-shape": shape, "data-size": size, style: { display: "flex", flexDirection: "column", alignItems: "center", gap: r.gap, minWidth: 0, width: "100%", overflow: "visible" }, children: [_jsxs("span", { ref: frameRef, style: { position: "relative", display: "flex", alignItems: "center", justifyContent: "center", ...dimensions, boxSizing: "border-box", flexShrink: 0, borderRadius: `${Math.round(r.shapes[shape] * 100)}%`, background: framed ? "var(--hjm-color-surface-alt)" : undefined, color: "var(--hjm-color-text)", boxShadow: ghost || !framed ? undefined : "0 0 0 1px var(--hjm-color-border)", outline: ghost ? "1px dashed var(--hjm-color-text-muted)" : undefined, opacity: ghost ? r.ghostOpacity : 1, fontSize: 32 }, children: [typeof icon === "function" ? icon({ edge }) : icon, overlay] }), _jsx(Text, { variant: "caption", style: { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: labelLines, width: "100%", maxWidth: labelLines === 2 ? r.multilineLabelMaxWidth : r.labelMaxWidth, minHeight: labelLines === 2 ? r.multilineLabelMinHeight : undefined, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "normal", overflowWrap: "anywhere", textAlign: "center", fontWeight: 500 }, children: label })] });
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Incoming "is typing" row for a ChatScreen timeline (2026-10-18 BurnTok DM drew its own). The host shows it only while
|
|
207
|
+
* presence says someone is typing and places it after the last message; HJM owns the bubble, dots and announcement.
|
|
208
|
+
*/
|
|
209
|
+
export function TypingIndicator({ label, avatar, layoutStyle }) {
|
|
210
|
+
if (!label.trim())
|
|
211
|
+
throw new TypeError("TypingIndicator requires a localized label");
|
|
212
|
+
const theme = useOptionalHjmTheme();
|
|
213
|
+
const r = typingIndicatorRecipe, m = chatMessageRecipe;
|
|
214
|
+
// A polite status: appearing is announced once without interrupting, and the dots stay hidden from assistive tech.
|
|
215
|
+
return _jsxs("div", { role: "status", className: "hjm-typing-indicator", "data-reduced-motion": theme?.environment.reducedMotion || undefined, style: { display: "flex", alignItems: "flex-start", gap: m.avatarGap, marginBlockStart: m.groupGap, paddingInlineStart: avatar ? 0 : m.tailWidth, ...layoutStyle, "--hjm-typing-dot": `${r.dotSize}px`, "--hjm-typing-cycle": `${r.cycleMs}ms`, "--hjm-typing-rest": r.restOpacity }, children: [avatar ? _jsx("span", { "aria-hidden": "true", style: { display: "flex", justifyContent: "center", flex: `0 0 ${m.avatarSize}px`, inlineSize: m.avatarSize }, children: avatar }) : null, _jsx("span", { "aria-hidden": "true", className: "hjm-typing-indicator__bubble", style: { display: "inline-flex", alignItems: "center", gap: r.dotGap, boxSizing: "border-box", minBlockSize: `calc(var(--hjm-type-body-line-height) + ${m.bubblePaddingBlock * 2}px)`, paddingBlock: m.bubblePaddingBlock, paddingInline: m.bubblePaddingInline, borderRadius: m.bubbleRadius, background: "var(--hjm-color-surface)" }, children: [0, 1, 2].map(index => _jsx("span", { className: "hjm-typing-indicator__dot", style: { animationDelay: `${index * r.staggerMs}ms` } }, index)) }), _jsx(VisuallyHidden, { children: label })] });
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Centered date (or time) row between ChatScreen timeline groups (2026-10-18 Utilverse chat drew a caption). Grouping
|
|
219
|
+
* rules stay with the product; the next message keeps its own group gap, so this row adds only the space before it.
|
|
220
|
+
*/
|
|
221
|
+
export function ChatDateSeparator({ label, dateTime, layoutStyle }) {
|
|
222
|
+
if (!label.trim())
|
|
223
|
+
throw new TypeError("ChatDateSeparator requires a localized label");
|
|
224
|
+
const r = chatDateSeparatorRecipe;
|
|
225
|
+
return _jsx("div", { className: "hjm-chat-date-separator", style: { display: "flex", justifyContent: "center", textAlign: "center", paddingBlockStart: r.paddingTop, paddingBlockEnd: r.paddingBottom, ...layoutStyle }, children: _jsx(Text, { variant: r.textVariant, tone: r.tone, children: dateTime ? _jsx("time", { dateTime: dateTime, children: label }) : label }) });
|
|
164
226
|
}
|
|
165
227
|
//# sourceMappingURL=screens.js.map
|