@12-apps/ui 6.52.0 → 6.52.1

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.
@@ -30,7 +30,7 @@ import {
30
30
  } from "../chunks/chunk-RADD26LG.js";
31
31
 
32
32
  // src/components/feedback/Dialog/Dialog.native.tsx
33
- import * as React3 from "react";
33
+ import * as React4 from "react";
34
34
  import { Modal, Pressable as Pressable2, ScrollView as ScrollView2, StyleSheet as StyleSheet2, View as View2 } from "react-native";
35
35
 
36
36
  // src/components/feedback/Dialog/Dialog.helpers.ts
@@ -187,6 +187,11 @@ function overlayFor(theme, variant) {
187
187
  if (variant === "drawer") return { flex: 1, alignItems: "flex-end" };
188
188
  return centredOverlay(theme);
189
189
  }
190
+ function dialogKeyboardLift(theme, variant, overlap) {
191
+ if (overlap <= 0) return null;
192
+ const margin = variant === "fullscreen" || variant === "drawer" ? 0 : theme.spacing(DIALOG_MARGIN_UNITS);
193
+ return { paddingBottom: margin + overlap };
194
+ }
190
195
  function dialogLook(theme, a) {
191
196
  const radius = dialogRadius(theme, a.borderRadius);
192
197
  const base = {
@@ -398,6 +403,39 @@ var styles = StyleSheet.create({
398
403
  }
399
404
  });
400
405
 
406
+ // src/platform/keyboard-overlap.native.ts
407
+ import * as React3 from "react";
408
+ import {
409
+ Keyboard,
410
+ Platform
411
+ } from "react-native";
412
+ function keyboardOverlap(viewBottom, keyboardTop) {
413
+ if (viewBottom === null || keyboardTop === null) return 0;
414
+ return Math.max(0, viewBottom - keyboardTop);
415
+ }
416
+ var KEYBOARD_EVENTS = Platform.OS === "ios" ? { show: "keyboardWillShow", hide: "keyboardWillHide" } : { show: "keyboardDidShow", hide: "keyboardDidHide" };
417
+ function useKeyboardOverlap() {
418
+ const [viewBottom, setViewBottom] = React3.useState(null);
419
+ const [keyboardTop, setKeyboardTop] = React3.useState(null);
420
+ React3.useEffect(() => {
421
+ const shown = Keyboard.addListener(KEYBOARD_EVENTS.show, (event) => {
422
+ setKeyboardTop(event.endCoordinates.screenY);
423
+ });
424
+ const hidden = Keyboard.addListener(KEYBOARD_EVENTS.hide, () => {
425
+ setKeyboardTop(null);
426
+ });
427
+ return () => {
428
+ shown.remove();
429
+ hidden.remove();
430
+ };
431
+ }, []);
432
+ const onLayout = React3.useCallback((event) => {
433
+ const { y, height } = event.nativeEvent.layout;
434
+ setViewBottom(y + height);
435
+ }, []);
436
+ return { overlap: keyboardOverlap(viewBottom, keyboardTop), onLayout };
437
+ }
438
+
401
439
  // src/components/feedback/Dialog/Dialog.native.tsx
402
440
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
403
441
  var styles2 = StyleSheet2.create({
@@ -421,8 +459,8 @@ function bodyOf(theme, children, hasTitle) {
421
459
  );
422
460
  }
423
461
  function hasHeaderChild(children) {
424
- return React3.Children.toArray(children).some(
425
- (child) => React3.isValidElement(child) && child.type === DialogHeader
462
+ return React4.Children.toArray(children).some(
463
+ (child) => React4.isValidElement(child) && child.type === DialogHeader
426
464
  );
427
465
  }
428
466
  function Dialog(rawProps) {
@@ -449,45 +487,53 @@ function Dialog(rawProps) {
449
487
  const testID = resolveTestId(others, "dialog");
450
488
  const { style, ...modalProps } = withoutTestIdProps(others);
451
489
  const dismiss = persistent ? void 0 : onClose;
452
- return /* @__PURE__ */ jsx2(Modal, { visible: open, transparent: true, onRequestClose: dismiss, ...modalProps, children: /* @__PURE__ */ jsxs2(View2, { style: look.overlay, children: [
453
- /* @__PURE__ */ jsx2(
454
- Pressable2,
455
- {
456
- "aria-hidden": true,
457
- tabIndex: -1,
458
- testID: `${testID}-backdrop`,
459
- onPress: dismiss,
460
- style: [StyleSheet2.absoluteFill, dialogBackdrop(glass)]
461
- }
462
- ),
463
- /* @__PURE__ */ jsxs2(View2, { testID, style: [look.paper, style], children: [
464
- pulse ? /* @__PURE__ */ jsx2(
465
- PulseRing,
466
- {
467
- color: theme.palette.primary.main,
468
- radius: look.radius,
469
- spread: DIALOG_PULSE.spread,
470
- durationMs: DIALOG_PULSE.durationMs,
471
- fadeAt: DIALOG_PULSE.fadeAt,
472
- opacity: DIALOG_PULSE.alpha,
473
- testID: `${testID}-pulse`
474
- }
475
- ) : null,
476
- /* @__PURE__ */ jsxs2(DialogTitledContext.Provider, { value: Boolean(title) || hasHeaderChild(children), children: [
477
- title ? /* @__PURE__ */ jsx2(
478
- DialogHeader,
490
+ const keyboard = useKeyboardOverlap();
491
+ return /* @__PURE__ */ jsx2(Modal, { visible: open, transparent: true, onRequestClose: dismiss, ...modalProps, children: /* @__PURE__ */ jsxs2(
492
+ View2,
493
+ {
494
+ style: [look.overlay, dialogKeyboardLift(theme, variant, keyboard.overlap)],
495
+ onLayout: keyboard.onLayout,
496
+ children: [
497
+ /* @__PURE__ */ jsx2(
498
+ Pressable2,
479
499
  {
480
- title,
481
- subtitle: description,
482
- showCloseButton,
483
- onClose,
484
- dataTestId: testID
500
+ "aria-hidden": true,
501
+ tabIndex: -1,
502
+ testID: `${testID}-backdrop`,
503
+ onPress: dismiss,
504
+ style: [StyleSheet2.absoluteFill, dialogBackdrop(glass)]
485
505
  }
486
- ) : null,
487
- bodyOf(theme, children, Boolean(title))
488
- ] })
489
- ] })
490
- ] }) });
506
+ ),
507
+ /* @__PURE__ */ jsxs2(View2, { testID, style: [look.paper, style], children: [
508
+ pulse ? /* @__PURE__ */ jsx2(
509
+ PulseRing,
510
+ {
511
+ color: theme.palette.primary.main,
512
+ radius: look.radius,
513
+ spread: DIALOG_PULSE.spread,
514
+ durationMs: DIALOG_PULSE.durationMs,
515
+ fadeAt: DIALOG_PULSE.fadeAt,
516
+ opacity: DIALOG_PULSE.alpha,
517
+ testID: `${testID}-pulse`
518
+ }
519
+ ) : null,
520
+ /* @__PURE__ */ jsxs2(DialogTitledContext.Provider, { value: Boolean(title) || hasHeaderChild(children), children: [
521
+ title ? /* @__PURE__ */ jsx2(
522
+ DialogHeader,
523
+ {
524
+ title,
525
+ subtitle: description,
526
+ showCloseButton,
527
+ onClose,
528
+ dataTestId: testID
529
+ }
530
+ ) : null,
531
+ bodyOf(theme, children, Boolean(title))
532
+ ] })
533
+ ] })
534
+ ]
535
+ }
536
+ ) });
491
537
  }
492
538
  Dialog.displayName = "Dialog";
493
539
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/feedback/Dialog/Dialog.native.tsx","../../../src/components/feedback/Dialog/Dialog.helpers.ts","../../../src/components/feedback/Dialog/Dialog.metrics.ts","../../../src/components/feedback/Dialog/Dialog.look.native.ts","../../../src/components/feedback/Dialog/DialogParts.native.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Modal, Pressable, ScrollView, StyleSheet, View } from 'react-native';\n\nimport { hasSpacingSlot, withDialogDefaults } from './Dialog.helpers';\nimport { dialogBackdrop, dialogLook } from './Dialog.look.native';\nimport { DIALOG_BODY_PADDING_UNITS, DIALOG_PULSE } from './Dialog.metrics';\nimport type { DialogProps } from './Dialog.types.native';\nimport {\n DialogActions,\n DialogContent,\n DialogHeader,\n DialogTitledContext,\n dialogBodyTextStyle,\n} from './DialogParts.native';\nimport { PulseRing } from '../../../platform/pulse-ring.native';\nimport { resolveTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { renderTextChildren } from '../../../platform/text-children';\nimport { useUiTheme } from '../../../provider/use-ui-theme.native';\nimport type { UiTheme } from '../../../tokens/theme';\n\n// The shared stories import all four names from `./Dialog`, which resolves here\n// under Metro and in the native Storybook; the slots live in their own file to\n// keep this one about the envelope, and are re-exported so that import works.\nexport { DialogActions, DialogContent, DialogHeader } from './DialogParts.native';\n\n/**\n * The dialog body. Consumers that pass raw children (no `DialogContent`) would\n * render them flush against the paper edges — give them comfortable default\n * padding; `DialogContent`/`DialogActions` users keep managing their own.\n *\n * The web can also catch a slot passed through a COMPONENT boundary, with a\n * `:has()` selector that asks the DOM one step after React could. React Native\n * has no such selector, so only the element-type check runs here — see\n * NATIVE-NOTES.md.\n */\nconst styles = StyleSheet.create({\n /** Same sizing `DialogParts`' content slot uses, so a raw body behaves like one. */\n rawBody: { flexGrow: 1, flexShrink: 1 },\n});\n\nfunction bodyOf(theme: UiTheme, children: React.ReactNode, hasTitle: boolean): React.ReactNode {\n if (hasSpacingSlot(children, [DialogContent, DialogActions])) return children;\n const top = hasTitle ? DIALOG_BODY_PADDING_UNITS.topUnderTitle : DIALOG_BODY_PADDING_UNITS.top;\n // A `ScrollView`, not a `View`: MUI's paper is `overflow-y: auto`, so\n // overlong raw children scroll on the web. The native paper is `maxHeight:\n // '100%'` with its overflow hidden, so a plain wrapper clipped them with no\n // way to reach the rest. `DialogContent` already gets this treatment; raw\n // children were the one path that did not.\n return (\n <ScrollView\n style={styles.rawBody}\n contentContainerStyle={{\n paddingHorizontal: theme.spacing(DIALOG_BODY_PADDING_UNITS.horizontal),\n paddingBottom: theme.spacing(DIALOG_BODY_PADDING_UNITS.bottom),\n paddingTop: theme.spacing(top),\n }}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </ScrollView>\n );\n}\n\n/**\n * Whether a `<DialogHeader>` arrived as a CHILD rather than through the `title`\n * prop.\n *\n * The web tightens a body under a title with\n * `.MuiDialogTitle-root + &.MuiDialogContent-root` — a CSS sibling selector, so\n * it fires however the title got there. Native reads a context instead, and the\n * context was set from the `title` prop alone, which is the composition nothing\n * uses: every story and `Dialog.md` write the header as a child, and those\n * bodies took the untitled 24px inset where the web gives them 12px.\n */\nfunction hasHeaderChild(children: React.ReactNode): boolean {\n return React.Children.toArray(children).some(\n (child) => React.isValidElement(child) && child.type === DialogHeader,\n );\n}\n\nexport function Dialog(rawProps: DialogProps): React.JSX.Element {\n const {\n children,\n open,\n variant,\n size,\n title,\n description,\n showCloseButton,\n backdrop: _backdrop,\n persistent,\n glass,\n gradient: _gradient,\n glow,\n pulse,\n borderRadius,\n onClose,\n ...others\n } = withDialogDefaults(rawProps);\n\n const theme = useUiTheme();\n const look = dialogLook(theme, { variant, size, borderRadius, glass, glow, pulse });\n const testID = resolveTestId(others, 'dialog');\n const { style, ...modalProps } = withoutTestIdProps(others);\n\n // A persistent dialog refuses both routes out that are not a button: the\n // backdrop and Escape (which `Modal` routes through `onRequestClose`).\n const dismiss = persistent ? undefined : onClose;\n\n return (\n <Modal visible={open} transparent onRequestClose={dismiss} {...modalProps}>\n <View style={look.overlay}>\n <Pressable\n aria-hidden\n tabIndex={-1}\n testID={`${testID}-backdrop`}\n onPress={dismiss}\n style={[StyleSheet.absoluteFill, dialogBackdrop(glass)]}\n />\n <View testID={testID} style={[look.paper, style]}>\n {pulse ? (\n <PulseRing\n color={theme.palette.primary.main}\n radius={look.radius}\n spread={DIALOG_PULSE.spread}\n durationMs={DIALOG_PULSE.durationMs}\n fadeAt={DIALOG_PULSE.fadeAt}\n opacity={DIALOG_PULSE.alpha}\n testID={`${testID}-pulse`}\n />\n ) : null}\n <DialogTitledContext.Provider value={Boolean(title) || hasHeaderChild(children)}>\n {title ? (\n <DialogHeader\n title={title}\n subtitle={description}\n showCloseButton={showCloseButton}\n onClose={onClose}\n dataTestId={testID}\n />\n ) : null}\n {bodyOf(theme, children, Boolean(title))}\n </DialogTitledContext.Provider>\n </View>\n </View>\n </Modal>\n );\n}\n\nDialog.displayName = 'Dialog';\n","import * as React from 'react';\n\nimport type { DialogBaseProps } from './Dialog.base';\n\n/**\n * The defaults, and the slot detection, BOTH renderers run.\n *\n * Neither imports a renderer: the defaults are plain values and the slot check\n * is told which component identities count, because `DialogContent` on the web\n * and `DialogContent` on React Native are different functions answering to the\n * same name.\n */\n\ntype DialogDefaultedKeys =\n | 'variant'\n | 'size'\n | 'showCloseButton'\n | 'backdrop'\n | 'persistent'\n | 'glass'\n | 'gradient'\n | 'glow'\n | 'pulse'\n | 'borderRadius';\n\nexport const DIALOG_DEFAULTS: Required<Pick<DialogBaseProps, DialogDefaultedKeys>> = {\n variant: 'default',\n size: 'md',\n showCloseButton: true,\n backdrop: true,\n persistent: false,\n glass: false,\n gradient: false,\n glow: false,\n pulse: false,\n borderRadius: 'lg',\n};\n\n/** The renderer's own props with every defaulted field guaranteed present. */\nexport type DialogPropsWithDefaults<P extends DialogBaseProps> = P &\n Required<Pick<DialogBaseProps, DialogDefaultedKeys>>;\n\n/**\n * Apply {@link DIALOG_DEFAULTS} exactly like parameter defaults would: only a\n * missing/`undefined` prop falls back (a loop, so the component's cyclomatic\n * complexity doesn't pay one branch per defaulted prop).\n */\nexport function withDialogDefaults<P extends DialogBaseProps>(props: P): DialogPropsWithDefaults<P> {\n const merged = { ...props } as Record<string, unknown>;\n for (const [key, value] of Object.entries(DIALOG_DEFAULTS)) {\n if (merged[key] === undefined) merged[key] = value;\n }\n return merged as unknown as DialogPropsWithDefaults<P>;\n}\n\n/**\n * Does this hand us a spacing slot? Fragments are transparent: `<>` is a way of\n * passing several children, not a child that owns them, and a consumer who\n * groups a `DialogContent` and a `DialogActions` in one has still passed both.\n *\n * Only the slots themselves count, and only at the top. A `DialogContent`\n * genuinely nested inside a `<div>` is that div's content, and the dialog\n * padding the div is right.\n *\n * `slots` is passed in rather than closed over so the two renderers can hand it\n * their own `DialogContent` / `DialogActions`.\n */\nexport function hasSpacingSlot(children: React.ReactNode, slots: readonly unknown[]): boolean {\n return React.Children.toArray(children).some((child) => {\n if (!React.isValidElement(child)) return false;\n if (slots.includes(child.type)) return true;\n if (child.type !== React.Fragment) return false;\n return hasSpacingSlot((child.props as { children?: React.ReactNode }).children, slots);\n });\n}\n","import type { DialogBorderRadius, DialogSize } from './Dialog.base';\nimport { alpha } from '../../../tokens/color';\nimport { ABSOLUTE_INK } from '../../../tokens/ink.core';\nimport type { UiShadow } from '../../../tokens/shadow';\n\n/**\n * THE NUMBERS BOTH `Dialog` RENDERERS DRAW WITH.\n *\n * `Dialog.styles.ts` / `Dialog.tsx` (MUI) and `Dialog.look.native.ts` /\n * `Dialog.native.tsx` (React Native) read this one table. Spacing is in\n * SPACING UNITS wherever the web writes `theme.spacing(n)`; everything else is\n * px, a ratio or an alpha. Shadows are `UiShadow`s — see `src/tokens/shadow.ts`\n * for why they have a shape rather than a string.\n */\n\n/* ── The paper ────────────────────────────────────────────────────────────── */\n\n/** `theme.spacing(n)` per named radius; `lg` is the default. */\nexport const DIALOG_RADIUS_UNITS: Record<DialogBorderRadius, number> = {\n none: 0,\n sm: 0.5,\n md: 1,\n lg: 2,\n xl: 3,\n};\n\n/** How wide the paper may grow, per size, in px. Also the drawer's fixed width. */\nexport const DIALOG_MAX_WIDTH: Record<DialogSize, number> = {\n xs: 400,\n sm: 600,\n md: 800,\n lg: 1000,\n xl: 1200,\n};\n\n/** The paper is 90% of the viewport up to its max width, `theme.spacing(2)` clear of the edge. */\nexport const DIALOG_WIDTH_PERCENT = 90;\nexport const DIALOG_MARGIN_UNITS = 2;\n\n/** The hairline `glass` draws. */\nexport const DIALOG_BORDER_WIDTH = 1;\n\n/**\n * MUI's `theme.shadows[24]` — the elevation a `Dialog`'s Paper paints.\n *\n * Restated rather than derived because the web never names it: `MuiDialog`\n * passes `elevation={24}` to its Paper, and React Native has no Paper to\n * inherit that from.\n */\nexport const DIALOG_PAPER_SHADOW: readonly UiShadow[] = [\n { offsetX: 0, offsetY: 11, blurRadius: 15, spreadDistance: -7, color: alpha(ABSOLUTE_INK.black, 0.2) },\n { offsetX: 0, offsetY: 24, blurRadius: 38, spreadDistance: 3, color: alpha(ABSOLUTE_INK.black, 0.14) },\n { offsetX: 0, offsetY: 9, blurRadius: 46, spreadDistance: 8, color: alpha(ABSOLUTE_INK.black, 0.12) },\n];\n\n/* ── Variants and decorations ─────────────────────────────────────────────── */\n\n/** `glass`: a 0.1 wash of the paper behind a 20px blur, inside a primary hairline. */\nexport const DIALOG_GLASS = {\n backgroundAlpha: 0.1,\n blurPx: 20,\n borderAlpha: 0.2,\n shadow: { offsetY: 8, blurRadius: 32, alpha: 0.1 },\n} as const;\n\n/** `gradient`: a faint primary-into-secondary wash at 135°, over a 10px blur. */\nexport const DIALOG_GRADIENT = { angleDeg: 135, stopAlpha: 0.1, blurPx: 10 } as const;\n\n/** `glow`: a 40px halo of the primary hue. */\nexport const DIALOG_GLOW = { blurRadius: 40, alpha: 0.3 } as const;\n\n/**\n * `pulse`: a ring of the primary hue that grows 20px out of the paper's edge and\n * fades, every two seconds. The web draws it as an `::after` box-shadow spread\n * behind the paper (`z-index: -1`).\n */\nexport const DIALOG_PULSE = {\n alpha: 0.3,\n durationMs: 2000,\n spread: 20,\n /** The fraction of the cycle the ring is still visible for. */\n fadeAt: 0.7,\n zIndex: -1,\n} as const;\n\n/**\n * The scrim: half-black, or a fifth of it behind an 8px blur for `glass`.\n *\n * It paints on BOTH renderers whatever `backdrop` is set to. The web reads that\n * prop as `BackdropComponent={backdrop ? Backdrop : undefined}`, and `undefined`\n * is exactly what MUI falls back to `Backdrop` for — so the scrim is there\n * either way. Native mirrors that rather than inventing a difference; fixing it\n * is a web change, made once, for both.\n */\nexport const DIALOG_BACKDROP = { alpha: { plain: 0.5, glass: 0.2 }, blurPx: 8 } as const;\n\n/* ── The slots ────────────────────────────────────────────────────────────── */\n\n/** Raw children (no `DialogContent`/`DialogActions`) get this padding, in spacing units. */\nexport const DIALOG_BODY_PADDING_UNITS = {\n horizontal: 3,\n bottom: 2.5,\n top: 2.5,\n /** Tighter under a title, whose own bottom padding is most of the gap. */\n topUnderTitle: 0.5,\n} as const;\n\n/** A `DialogHeader` given raw `children` is a padded box over a hairline instead. */\nexport const DIALOG_HEADER_CHILDREN_PADDING_UNITS = 2;\n\n/**\n * The title row: MUI's `DialogTitle` padding in px, and the bottom the web\n * overrides in spacing units — tighter when a subtitle follows it.\n */\nexport const DIALOG_TITLE = {\n padding: { top: 16, horizontal: 24 },\n paddingBottomUnits: { withSubtitle: 1, plain: 2 },\n subtitleGapUnits: 0.5,\n} as const;\n\n/**\n * The close button: MUI's `IconButton` at its default 8px padding around a\n * 24px glyph, on a circular hover wash.\n */\nexport const DIALOG_CLOSE_BUTTON = { padding: 8, iconSize: 24 } as const;\n\n/** `DialogContent`: `theme.spacing(3)`, tightened by `dense`. */\nexport const DIALOG_CONTENT_PADDING_UNITS = { normal: 3, dense: 1.5 } as const;\n\n/**\n * Top padding for a body that sits directly under the title.\n *\n * MUI zeroes it there, assuming the title's own bottom padding is the whole\n * gap. That assumption breaks for a body opening with an outlined field: a\n * filled field floats its label ABOVE the input's border box, and the body is a\n * clipping box, so the label gets sliced in half (FUT-544). 12px clears the\n * shrunk label's 9px overhang and leaves the gap visually tight.\n */\nexport const DIALOG_TITLED_BODY_PADDING_TOP_UNITS = 1.5;\n\n/** `DialogActions`: `theme.spacing(2)` around, and one unit between by default. */\nexport const DIALOG_ACTIONS = { paddingUnits: 2, defaultSpacingUnits: 1 } as const;\n","import type { ViewStyle } from 'react-native';\n\nimport type { DialogBorderRadius, DialogSize, DialogVariant } from './Dialog.base';\nimport {\n DIALOG_BACKDROP,\n DIALOG_BORDER_WIDTH,\n DIALOG_GLASS,\n DIALOG_GLOW,\n DIALOG_MARGIN_UNITS,\n DIALOG_MAX_WIDTH,\n DIALOG_PAPER_SHADOW,\n DIALOG_RADIUS_UNITS,\n} from './Dialog.metrics';\nimport { alpha } from '../../../tokens/color';\nimport { BLACK, halo } from '../../../tokens/shadow';\nimport type { UiTheme } from '../../../tokens/theme';\n\n/**\n * WHAT THE NATIVE `Dialog` PAINTS — the arithmetic, with no JSX in sight.\n *\n * `variantStylesOf` builds one `sx` by spreading base, then the decorations\n * (`glow`, `pulse`), then the variant's own surface; whichever wrote a property\n * last wins. The composition here spreads the SAME things in the SAME order, so\n * `glow` survives on a `default` paper (whose surface declares no shadow) and is\n * buried by a `glass` one, on both renderers.\n */\n\n/** The named radius as a length. */\nexport function dialogRadius(theme: UiTheme, radius: DialogBorderRadius): number {\n return theme.spacing(DIALOG_RADIUS_UNITS[radius] ?? DIALOG_RADIUS_UNITS.lg);\n}\n\n/** The scrim behind the paper: half-black, or a fifth of it for `glass`. */\nexport function dialogBackdrop(glass: boolean): ViewStyle {\n return {\n backgroundColor: alpha(BLACK, glass ? DIALOG_BACKDROP.alpha.glass : DIALOG_BACKDROP.alpha.plain),\n };\n}\n\n/** The glass surface, shared by `variant=\"glass\"` and the `glass` flag. */\nfunction glassSurface(theme: UiTheme): ViewStyle {\n return {\n backgroundColor: alpha(theme.palette.background.paper, DIALOG_GLASS.backgroundAlpha),\n borderWidth: DIALOG_BORDER_WIDTH,\n borderStyle: 'solid',\n borderColor: alpha(theme.palette.primary.main, DIALOG_GLASS.borderAlpha),\n };\n}\n\nexport interface DialogLookArgs {\n variant: DialogVariant;\n size: DialogSize;\n borderRadius: DialogBorderRadius;\n glass: boolean;\n glow: boolean;\n pulse: boolean;\n}\n\nexport interface DialogLook {\n /** Where the paper sits inside the modal, and how far in from the edges. */\n overlay: ViewStyle;\n paper: ViewStyle;\n /** The paper's corner radius, for the pulse ring to follow. */\n radius: number;\n}\n\n/** The centred paper of `default` and `glass`, before the variant paints over it. */\nfunction centredOverlay(theme: UiTheme): ViewStyle {\n return {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n // The web gives the PAPER `margin: theme.spacing(2)`; an inset on the\n // overlay is the same gap and leaves the paper free to be `100%` wide.\n padding: theme.spacing(DIALOG_MARGIN_UNITS),\n };\n}\n\nfunction variantPaper(theme: UiTheme, a: DialogLookArgs, radius: number): ViewStyle {\n switch (a.variant) {\n case 'glass':\n return {\n ...glassSurface(theme),\n boxShadow: [\n {\n offsetX: 0,\n offsetY: DIALOG_GLASS.shadow.offsetY,\n blurRadius: DIALOG_GLASS.shadow.blurRadius,\n spreadDistance: 0,\n color: alpha(BLACK, DIALOG_GLASS.shadow.alpha),\n },\n ],\n };\n case 'fullscreen':\n return { borderRadius: 0, width: '100%', height: '100%', maxWidth: undefined };\n case 'drawer':\n return {\n // SQUARE, because that is what the web paints. Two things have to fail\n // for a drawer to have a corner there and both do: the `sx` builds\n // `${radius}px 0 0 ${radius}px` out of `theme.spacing()`, which already\n // returns a px string, so the declaration reads `16pxpx 0 0 16pxpx` and\n // the browser drops it; and that `sx` lands on a plain `Box` INSIDE the\n // Drawer, whose own Paper MUI renders with `square: true`. Nothing is\n // left to round the corners. See NATIVE-NOTES.md.\n borderRadius: 0,\n width: DIALOG_MAX_WIDTH[a.size] ?? DIALOG_MAX_WIDTH.md,\n maxWidth: '100%',\n height: '100%',\n maxHeight: undefined,\n };\n default:\n return a.glass ? { ...glassSurface(theme), borderRadius: radius } : {};\n }\n}\n\n/** `fullscreen` and `drawer` fill the screen, so neither is inset or centred. */\nfunction overlayFor(theme: UiTheme, variant: DialogVariant): ViewStyle {\n if (variant === 'fullscreen') return { flex: 1 };\n if (variant === 'drawer') return { flex: 1, alignItems: 'flex-end' };\n return centredOverlay(theme);\n}\n\n/**\n * Everything the dialog paints, decided once per render.\n *\n * The base carries what MUI's `Paper` gives every dialog — the paper colour and\n * `shadows[24]` — because React Native has no Paper to inherit them from; a\n * `glow` or a variant shadow then overrides it exactly as the `sx` does.\n *\n * `overflow` clips the paper's children to its corners, as MUI's own\n * `overflow-y: auto` does — except under `pulse` or `glow`, which have to paint\n * OUTSIDE the paper and are clipped by the view's own overflow on a device.\n */\nexport function dialogLook(theme: UiTheme, a: DialogLookArgs): DialogLook {\n const radius = dialogRadius(theme, a.borderRadius);\n\n const base: ViewStyle = {\n position: 'relative',\n borderRadius: radius,\n backgroundColor: theme.palette.background.paper,\n boxShadow: DIALOG_PAPER_SHADOW,\n width: '100%',\n maxWidth: DIALOG_MAX_WIDTH[a.size] ?? DIALOG_MAX_WIDTH.md,\n maxHeight: '100%',\n overflow: a.pulse || a.glow ? 'visible' : 'hidden',\n };\n\n return {\n radius,\n overlay: overlayFor(theme, a.variant),\n paper: {\n ...base,\n ...(a.glow\n ? { boxShadow: halo(DIALOG_GLOW.blurRadius, alpha(theme.palette.primary.main, DIALOG_GLOW.alpha)) }\n : null),\n ...variantPaper(theme, a, radius),\n },\n };\n}\n","import * as React from 'react';\nimport {\n Pressable,\n ScrollView,\n StyleSheet,\n Text as RNText,\n View,\n type TextStyle,\n type ViewStyle,\n} from 'react-native';\n\nimport {\n DIALOG_ACTIONS,\n DIALOG_CLOSE_BUTTON,\n DIALOG_CONTENT_PADDING_UNITS,\n DIALOG_HEADER_CHILDREN_PADDING_UNITS,\n DIALOG_TITLE,\n DIALOG_TITLED_BODY_PADDING_TOP_UNITS,\n} from './Dialog.metrics';\nimport type {\n DialogActionsAlignment,\n DialogActionsProps,\n DialogContentProps,\n DialogHeaderProps,\n} from './Dialog.types.native';\nimport { Icon } from '../../../icons/Icon.native';\nimport { childTestId, slotTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { renderTextChildren } from '../../../platform/text-children';\nimport { useUiTheme } from '../../../provider/use-ui-theme.native';\nimport { muiTypeStyle } from '../../../tokens/mui-type';\nimport type { UiTheme } from '../../../tokens/theme';\n\n/**\n * THE THREE SLOTS A DIALOG IS BUILT OUT OF, ON REACT NATIVE.\n *\n * The web overrides only padding, alignment and the dividers, so the rest of\n * the numbers are MUI's own and are restated in `Dialog.metrics.ts` rather than\n * re-invented.\n */\n\n/**\n * Is there a title above this body?\n *\n * The web asks the DOM — `.MuiDialogTitle-root + &` — because only the CSS knows\n * which `DialogContent` actually FOLLOWS the title. React Native has no sibling\n * selector, so `Dialog` says so through context instead; the difference is that\n * a second `DialogContent` further down gets the tighter top padding too.\n */\nexport const DialogTitledContext = React.createContext(false);\n\n/** The body text of a slot, in MUI's `body1`, so a bare string is not unstyled. */\nexport const dialogBodyTextStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'body1'),\n color: theme.palette.text.primary,\n});\n\n/** MUI's dialog title: `h6`, in the body ink. */\nexport const dialogTitleStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'h6'),\n color: theme.palette.text.primary,\n});\n\n/** The subtitle under it: `body2`, muted, one half-unit down. */\nexport const dialogSubtitleStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'body2'),\n color: theme.palette.text.secondary,\n marginTop: theme.spacing(DIALOG_TITLE.subtitleGapUnits),\n});\n\nfunction CloseButton({ onPress, testID }: { onPress: () => void; testID: string }): React.JSX.Element {\n const theme = useUiTheme();\n return (\n <Pressable\n role=\"button\"\n aria-label=\"close\"\n testID={testID}\n onPress={onPress}\n style={({ pressed }) => [\n styles.close,\n pressed ? { backgroundColor: theme.palette.action.hover } : null,\n ]}\n >\n <Icon name=\"Close\" size={DIALOG_CLOSE_BUTTON.iconSize} color={theme.palette.text.secondary} />\n </Pressable>\n );\n}\n\nexport function DialogHeader({\n children,\n title,\n subtitle,\n showCloseButton = true,\n onClose,\n ...others\n}: DialogHeaderProps): React.JSX.Element {\n const theme = useUiTheme();\n const { style, ...viewProps } = withoutTestIdProps(others);\n\n if (children) {\n return (\n <View\n testID={slotTestId(others, 'header', 'dialog')}\n style={[\n {\n padding: theme.spacing(DIALOG_HEADER_CHILDREN_PADDING_UNITS),\n borderBottomWidth: 1,\n borderBottomColor: theme.palette.divider,\n borderStyle: 'solid',\n },\n style,\n ]}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </View>\n );\n }\n\n const paddingBottom = theme.spacing(\n subtitle ? DIALOG_TITLE.paddingBottomUnits.withSubtitle : DIALOG_TITLE.paddingBottomUnits.plain,\n );\n\n return (\n <View\n testID={slotTestId(others, 'title', 'dialog')}\n style={[styles.header, { paddingBottom }, style]}\n {...viewProps}\n >\n <View style={styles.headerText}>\n {/*\n MUI's `DialogTitle` renders `component: \"h2\"`, so the web dialog has a\n heading and the native one had none. `role`/`aria-level` are what\n react-native-web maps to an `<h2>`; a device reads the same pair\n through `accessibilityRole=\"header\"`, which RN derives from `role`.\n */}\n <RNText role=\"heading\" aria-level={2} style={dialogTitleStyle(theme)}>\n {title}\n </RNText>\n {subtitle ? <RNText style={dialogSubtitleStyle(theme)}>{subtitle}</RNText> : null}\n </View>\n {showCloseButton && onClose ? (\n <CloseButton onPress={onClose} testID={childTestId(others, 'close', 'dialog')} />\n ) : null}\n </View>\n );\n}\n\nDialogHeader.displayName = 'DialogHeader';\n\nexport function DialogContent({\n children,\n dividers = false,\n dense = false,\n ...others\n}: DialogContentProps): React.JSX.Element {\n const theme = useUiTheme();\n const titled = React.useContext(DialogTitledContext);\n const { style, ...viewProps } = withoutTestIdProps(others);\n const padding = theme.spacing(\n dense ? DIALOG_CONTENT_PADDING_UNITS.dense : DIALOG_CONTENT_PADDING_UNITS.normal,\n );\n const paddingTop = titled\n ? theme.spacing(DIALOG_TITLED_BODY_PADDING_TOP_UNITS)\n : padding;\n\n return (\n <ScrollView\n testID={slotTestId(others, 'content', 'dialog')}\n style={[\n styles.content,\n dividers\n ? {\n borderTopWidth: 1,\n borderBottomWidth: 1,\n borderStyle: 'solid',\n borderColor: theme.palette.divider,\n }\n : null,\n style,\n ]}\n contentContainerStyle={{ padding, paddingTop }}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </ScrollView>\n );\n}\n\nDialogContent.displayName = 'DialogContent';\n\nconst JUSTIFY: Record<DialogActionsAlignment, NonNullable<ViewStyle['justifyContent']>> = {\n left: 'flex-start',\n center: 'center',\n right: 'flex-end',\n 'space-between': 'space-between',\n};\n\nexport function DialogActions({\n children,\n alignment = 'right',\n spacing = DIALOG_ACTIONS.defaultSpacingUnits,\n ...others\n}: DialogActionsProps): React.JSX.Element {\n const theme = useUiTheme();\n const { style, ...viewProps } = withoutTestIdProps(others);\n\n return (\n <View\n testID={slotTestId(others, 'actions', 'dialog')}\n style={[\n styles.actions,\n {\n padding: theme.spacing(DIALOG_ACTIONS.paddingUnits),\n justifyContent: JUSTIFY[alignment],\n gap: theme.spacing(spacing),\n },\n style,\n ]}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </View>\n );\n}\n\nDialogActions.displayName = 'DialogActions';\n\nconst styles = StyleSheet.create({\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexGrow: 0,\n flexShrink: 0,\n paddingTop: DIALOG_TITLE.padding.top,\n paddingHorizontal: DIALOG_TITLE.padding.horizontal,\n },\n headerText: {\n flexShrink: 1,\n },\n close: {\n padding: DIALOG_CLOSE_BUTTON.padding,\n borderRadius: 9999,\n alignItems: 'center',\n justifyContent: 'center',\n },\n content: {\n flexGrow: 1,\n flexShrink: 1,\n },\n actions: {\n flexDirection: 'row',\n alignItems: 'center',\n flexGrow: 0,\n flexShrink: 0,\n },\n});\n"],"mappings":"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","names":["React","Pressable","ScrollView","StyleSheet","View","React","jsx","jsxs","styles","StyleSheet","ScrollView","View","Pressable"]}
1
+ {"version":3,"sources":["../../../src/components/feedback/Dialog/Dialog.native.tsx","../../../src/components/feedback/Dialog/Dialog.helpers.ts","../../../src/components/feedback/Dialog/Dialog.metrics.ts","../../../src/components/feedback/Dialog/Dialog.look.native.ts","../../../src/components/feedback/Dialog/DialogParts.native.tsx","../../../src/platform/keyboard-overlap.native.ts"],"sourcesContent":["import * as React from 'react';\nimport { Modal, Pressable, ScrollView, StyleSheet, View } from 'react-native';\n\nimport { hasSpacingSlot, withDialogDefaults } from './Dialog.helpers';\nimport { dialogBackdrop, dialogKeyboardLift, dialogLook } from './Dialog.look.native';\nimport { DIALOG_BODY_PADDING_UNITS, DIALOG_PULSE } from './Dialog.metrics';\nimport type { DialogProps } from './Dialog.types.native';\nimport {\n DialogActions,\n DialogContent,\n DialogHeader,\n DialogTitledContext,\n dialogBodyTextStyle,\n} from './DialogParts.native';\nimport { useKeyboardOverlap } from '../../../platform/keyboard-overlap.native';\nimport { PulseRing } from '../../../platform/pulse-ring.native';\nimport { resolveTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { renderTextChildren } from '../../../platform/text-children';\nimport { useUiTheme } from '../../../provider/use-ui-theme.native';\nimport type { UiTheme } from '../../../tokens/theme';\n\n// The shared stories import all four names from `./Dialog`, which resolves here\n// under Metro and in the native Storybook; the slots live in their own file to\n// keep this one about the envelope, and are re-exported so that import works.\nexport { DialogActions, DialogContent, DialogHeader } from './DialogParts.native';\n\n/**\n * The dialog body. Consumers that pass raw children (no `DialogContent`) would\n * render them flush against the paper edges — give them comfortable default\n * padding; `DialogContent`/`DialogActions` users keep managing their own.\n *\n * The web can also catch a slot passed through a COMPONENT boundary, with a\n * `:has()` selector that asks the DOM one step after React could. React Native\n * has no such selector, so only the element-type check runs here — see\n * NATIVE-NOTES.md.\n */\nconst styles = StyleSheet.create({\n /** Same sizing `DialogParts`' content slot uses, so a raw body behaves like one. */\n rawBody: { flexGrow: 1, flexShrink: 1 },\n});\n\nfunction bodyOf(theme: UiTheme, children: React.ReactNode, hasTitle: boolean): React.ReactNode {\n if (hasSpacingSlot(children, [DialogContent, DialogActions])) return children;\n const top = hasTitle ? DIALOG_BODY_PADDING_UNITS.topUnderTitle : DIALOG_BODY_PADDING_UNITS.top;\n // A `ScrollView`, not a `View`: MUI's paper is `overflow-y: auto`, so\n // overlong raw children scroll on the web. The native paper is `maxHeight:\n // '100%'` with its overflow hidden, so a plain wrapper clipped them with no\n // way to reach the rest. `DialogContent` already gets this treatment; raw\n // children were the one path that did not.\n return (\n <ScrollView\n style={styles.rawBody}\n contentContainerStyle={{\n paddingHorizontal: theme.spacing(DIALOG_BODY_PADDING_UNITS.horizontal),\n paddingBottom: theme.spacing(DIALOG_BODY_PADDING_UNITS.bottom),\n paddingTop: theme.spacing(top),\n }}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </ScrollView>\n );\n}\n\n/**\n * Whether a `<DialogHeader>` arrived as a CHILD rather than through the `title`\n * prop.\n *\n * The web tightens a body under a title with\n * `.MuiDialogTitle-root + &.MuiDialogContent-root` — a CSS sibling selector, so\n * it fires however the title got there. Native reads a context instead, and the\n * context was set from the `title` prop alone, which is the composition nothing\n * uses: every story and `Dialog.md` write the header as a child, and those\n * bodies took the untitled 24px inset where the web gives them 12px.\n */\nfunction hasHeaderChild(children: React.ReactNode): boolean {\n return React.Children.toArray(children).some(\n (child) => React.isValidElement(child) && child.type === DialogHeader,\n );\n}\n\nexport function Dialog(rawProps: DialogProps): React.JSX.Element {\n const {\n children,\n open,\n variant,\n size,\n title,\n description,\n showCloseButton,\n backdrop: _backdrop,\n persistent,\n glass,\n gradient: _gradient,\n glow,\n pulse,\n borderRadius,\n onClose,\n ...others\n } = withDialogDefaults(rawProps);\n\n const theme = useUiTheme();\n const look = dialogLook(theme, { variant, size, borderRadius, glass, glow, pulse });\n const testID = resolveTestId(others, 'dialog');\n const { style, ...modalProps } = withoutTestIdProps(others);\n\n // A persistent dialog refuses both routes out that are not a button: the\n // backdrop and Escape (which `Modal` routes through `onRequestClose`).\n const dismiss = persistent ? undefined : onClose;\n\n // Edge-to-edge defeats the `SOFT_INPUT_ADJUST_RESIZE` that `Modal` asks for,\n // so the window never shrinks for the keyboard and nothing else moves the\n // paper: the overlay measures how much of it the keyboard covers and takes\n // that off the paper's room itself (FUT-3021).\n const keyboard = useKeyboardOverlap();\n\n return (\n <Modal visible={open} transparent onRequestClose={dismiss} {...modalProps}>\n <View\n style={[look.overlay, dialogKeyboardLift(theme, variant, keyboard.overlap)]}\n onLayout={keyboard.onLayout}\n >\n <Pressable\n aria-hidden\n tabIndex={-1}\n testID={`${testID}-backdrop`}\n onPress={dismiss}\n style={[StyleSheet.absoluteFill, dialogBackdrop(glass)]}\n />\n <View testID={testID} style={[look.paper, style]}>\n {pulse ? (\n <PulseRing\n color={theme.palette.primary.main}\n radius={look.radius}\n spread={DIALOG_PULSE.spread}\n durationMs={DIALOG_PULSE.durationMs}\n fadeAt={DIALOG_PULSE.fadeAt}\n opacity={DIALOG_PULSE.alpha}\n testID={`${testID}-pulse`}\n />\n ) : null}\n <DialogTitledContext.Provider value={Boolean(title) || hasHeaderChild(children)}>\n {title ? (\n <DialogHeader\n title={title}\n subtitle={description}\n showCloseButton={showCloseButton}\n onClose={onClose}\n dataTestId={testID}\n />\n ) : null}\n {bodyOf(theme, children, Boolean(title))}\n </DialogTitledContext.Provider>\n </View>\n </View>\n </Modal>\n );\n}\n\nDialog.displayName = 'Dialog';\n","import * as React from 'react';\n\nimport type { DialogBaseProps } from './Dialog.base';\n\n/**\n * The defaults, and the slot detection, BOTH renderers run.\n *\n * Neither imports a renderer: the defaults are plain values and the slot check\n * is told which component identities count, because `DialogContent` on the web\n * and `DialogContent` on React Native are different functions answering to the\n * same name.\n */\n\ntype DialogDefaultedKeys =\n | 'variant'\n | 'size'\n | 'showCloseButton'\n | 'backdrop'\n | 'persistent'\n | 'glass'\n | 'gradient'\n | 'glow'\n | 'pulse'\n | 'borderRadius';\n\nexport const DIALOG_DEFAULTS: Required<Pick<DialogBaseProps, DialogDefaultedKeys>> = {\n variant: 'default',\n size: 'md',\n showCloseButton: true,\n backdrop: true,\n persistent: false,\n glass: false,\n gradient: false,\n glow: false,\n pulse: false,\n borderRadius: 'lg',\n};\n\n/** The renderer's own props with every defaulted field guaranteed present. */\nexport type DialogPropsWithDefaults<P extends DialogBaseProps> = P &\n Required<Pick<DialogBaseProps, DialogDefaultedKeys>>;\n\n/**\n * Apply {@link DIALOG_DEFAULTS} exactly like parameter defaults would: only a\n * missing/`undefined` prop falls back (a loop, so the component's cyclomatic\n * complexity doesn't pay one branch per defaulted prop).\n */\nexport function withDialogDefaults<P extends DialogBaseProps>(props: P): DialogPropsWithDefaults<P> {\n const merged = { ...props } as Record<string, unknown>;\n for (const [key, value] of Object.entries(DIALOG_DEFAULTS)) {\n if (merged[key] === undefined) merged[key] = value;\n }\n return merged as unknown as DialogPropsWithDefaults<P>;\n}\n\n/**\n * Does this hand us a spacing slot? Fragments are transparent: `<>` is a way of\n * passing several children, not a child that owns them, and a consumer who\n * groups a `DialogContent` and a `DialogActions` in one has still passed both.\n *\n * Only the slots themselves count, and only at the top. A `DialogContent`\n * genuinely nested inside a `<div>` is that div's content, and the dialog\n * padding the div is right.\n *\n * `slots` is passed in rather than closed over so the two renderers can hand it\n * their own `DialogContent` / `DialogActions`.\n */\nexport function hasSpacingSlot(children: React.ReactNode, slots: readonly unknown[]): boolean {\n return React.Children.toArray(children).some((child) => {\n if (!React.isValidElement(child)) return false;\n if (slots.includes(child.type)) return true;\n if (child.type !== React.Fragment) return false;\n return hasSpacingSlot((child.props as { children?: React.ReactNode }).children, slots);\n });\n}\n","import type { DialogBorderRadius, DialogSize } from './Dialog.base';\nimport { alpha } from '../../../tokens/color';\nimport { ABSOLUTE_INK } from '../../../tokens/ink.core';\nimport type { UiShadow } from '../../../tokens/shadow';\n\n/**\n * THE NUMBERS BOTH `Dialog` RENDERERS DRAW WITH.\n *\n * `Dialog.styles.ts` / `Dialog.tsx` (MUI) and `Dialog.look.native.ts` /\n * `Dialog.native.tsx` (React Native) read this one table. Spacing is in\n * SPACING UNITS wherever the web writes `theme.spacing(n)`; everything else is\n * px, a ratio or an alpha. Shadows are `UiShadow`s — see `src/tokens/shadow.ts`\n * for why they have a shape rather than a string.\n */\n\n/* ── The paper ────────────────────────────────────────────────────────────── */\n\n/** `theme.spacing(n)` per named radius; `lg` is the default. */\nexport const DIALOG_RADIUS_UNITS: Record<DialogBorderRadius, number> = {\n none: 0,\n sm: 0.5,\n md: 1,\n lg: 2,\n xl: 3,\n};\n\n/** How wide the paper may grow, per size, in px. Also the drawer's fixed width. */\nexport const DIALOG_MAX_WIDTH: Record<DialogSize, number> = {\n xs: 400,\n sm: 600,\n md: 800,\n lg: 1000,\n xl: 1200,\n};\n\n/** The paper is 90% of the viewport up to its max width, `theme.spacing(2)` clear of the edge. */\nexport const DIALOG_WIDTH_PERCENT = 90;\nexport const DIALOG_MARGIN_UNITS = 2;\n\n/** The hairline `glass` draws. */\nexport const DIALOG_BORDER_WIDTH = 1;\n\n/**\n * MUI's `theme.shadows[24]` — the elevation a `Dialog`'s Paper paints.\n *\n * Restated rather than derived because the web never names it: `MuiDialog`\n * passes `elevation={24}` to its Paper, and React Native has no Paper to\n * inherit that from.\n */\nexport const DIALOG_PAPER_SHADOW: readonly UiShadow[] = [\n { offsetX: 0, offsetY: 11, blurRadius: 15, spreadDistance: -7, color: alpha(ABSOLUTE_INK.black, 0.2) },\n { offsetX: 0, offsetY: 24, blurRadius: 38, spreadDistance: 3, color: alpha(ABSOLUTE_INK.black, 0.14) },\n { offsetX: 0, offsetY: 9, blurRadius: 46, spreadDistance: 8, color: alpha(ABSOLUTE_INK.black, 0.12) },\n];\n\n/* ── Variants and decorations ─────────────────────────────────────────────── */\n\n/** `glass`: a 0.1 wash of the paper behind a 20px blur, inside a primary hairline. */\nexport const DIALOG_GLASS = {\n backgroundAlpha: 0.1,\n blurPx: 20,\n borderAlpha: 0.2,\n shadow: { offsetY: 8, blurRadius: 32, alpha: 0.1 },\n} as const;\n\n/** `gradient`: a faint primary-into-secondary wash at 135°, over a 10px blur. */\nexport const DIALOG_GRADIENT = { angleDeg: 135, stopAlpha: 0.1, blurPx: 10 } as const;\n\n/** `glow`: a 40px halo of the primary hue. */\nexport const DIALOG_GLOW = { blurRadius: 40, alpha: 0.3 } as const;\n\n/**\n * `pulse`: a ring of the primary hue that grows 20px out of the paper's edge and\n * fades, every two seconds. The web draws it as an `::after` box-shadow spread\n * behind the paper (`z-index: -1`).\n */\nexport const DIALOG_PULSE = {\n alpha: 0.3,\n durationMs: 2000,\n spread: 20,\n /** The fraction of the cycle the ring is still visible for. */\n fadeAt: 0.7,\n zIndex: -1,\n} as const;\n\n/**\n * The scrim: half-black, or a fifth of it behind an 8px blur for `glass`.\n *\n * It paints on BOTH renderers whatever `backdrop` is set to. The web reads that\n * prop as `BackdropComponent={backdrop ? Backdrop : undefined}`, and `undefined`\n * is exactly what MUI falls back to `Backdrop` for — so the scrim is there\n * either way. Native mirrors that rather than inventing a difference; fixing it\n * is a web change, made once, for both.\n */\nexport const DIALOG_BACKDROP = { alpha: { plain: 0.5, glass: 0.2 }, blurPx: 8 } as const;\n\n/* ── The slots ────────────────────────────────────────────────────────────── */\n\n/** Raw children (no `DialogContent`/`DialogActions`) get this padding, in spacing units. */\nexport const DIALOG_BODY_PADDING_UNITS = {\n horizontal: 3,\n bottom: 2.5,\n top: 2.5,\n /** Tighter under a title, whose own bottom padding is most of the gap. */\n topUnderTitle: 0.5,\n} as const;\n\n/** A `DialogHeader` given raw `children` is a padded box over a hairline instead. */\nexport const DIALOG_HEADER_CHILDREN_PADDING_UNITS = 2;\n\n/**\n * The title row: MUI's `DialogTitle` padding in px, and the bottom the web\n * overrides in spacing units — tighter when a subtitle follows it.\n */\nexport const DIALOG_TITLE = {\n padding: { top: 16, horizontal: 24 },\n paddingBottomUnits: { withSubtitle: 1, plain: 2 },\n subtitleGapUnits: 0.5,\n} as const;\n\n/**\n * The close button: MUI's `IconButton` at its default 8px padding around a\n * 24px glyph, on a circular hover wash.\n */\nexport const DIALOG_CLOSE_BUTTON = { padding: 8, iconSize: 24 } as const;\n\n/** `DialogContent`: `theme.spacing(3)`, tightened by `dense`. */\nexport const DIALOG_CONTENT_PADDING_UNITS = { normal: 3, dense: 1.5 } as const;\n\n/**\n * Top padding for a body that sits directly under the title.\n *\n * MUI zeroes it there, assuming the title's own bottom padding is the whole\n * gap. That assumption breaks for a body opening with an outlined field: a\n * filled field floats its label ABOVE the input's border box, and the body is a\n * clipping box, so the label gets sliced in half (FUT-544). 12px clears the\n * shrunk label's 9px overhang and leaves the gap visually tight.\n */\nexport const DIALOG_TITLED_BODY_PADDING_TOP_UNITS = 1.5;\n\n/** `DialogActions`: `theme.spacing(2)` around, and one unit between by default. */\nexport const DIALOG_ACTIONS = { paddingUnits: 2, defaultSpacingUnits: 1 } as const;\n","import type { ViewStyle } from 'react-native';\n\nimport type { DialogBorderRadius, DialogSize, DialogVariant } from './Dialog.base';\nimport {\n DIALOG_BACKDROP,\n DIALOG_BORDER_WIDTH,\n DIALOG_GLASS,\n DIALOG_GLOW,\n DIALOG_MARGIN_UNITS,\n DIALOG_MAX_WIDTH,\n DIALOG_PAPER_SHADOW,\n DIALOG_RADIUS_UNITS,\n} from './Dialog.metrics';\nimport { alpha } from '../../../tokens/color';\nimport { BLACK, halo } from '../../../tokens/shadow';\nimport type { UiTheme } from '../../../tokens/theme';\n\n/**\n * WHAT THE NATIVE `Dialog` PAINTS — the arithmetic, with no JSX in sight.\n *\n * `variantStylesOf` builds one `sx` by spreading base, then the decorations\n * (`glow`, `pulse`), then the variant's own surface; whichever wrote a property\n * last wins. The composition here spreads the SAME things in the SAME order, so\n * `glow` survives on a `default` paper (whose surface declares no shadow) and is\n * buried by a `glass` one, on both renderers.\n */\n\n/** The named radius as a length. */\nexport function dialogRadius(theme: UiTheme, radius: DialogBorderRadius): number {\n return theme.spacing(DIALOG_RADIUS_UNITS[radius] ?? DIALOG_RADIUS_UNITS.lg);\n}\n\n/** The scrim behind the paper: half-black, or a fifth of it for `glass`. */\nexport function dialogBackdrop(glass: boolean): ViewStyle {\n return {\n backgroundColor: alpha(BLACK, glass ? DIALOG_BACKDROP.alpha.glass : DIALOG_BACKDROP.alpha.plain),\n };\n}\n\n/** The glass surface, shared by `variant=\"glass\"` and the `glass` flag. */\nfunction glassSurface(theme: UiTheme): ViewStyle {\n return {\n backgroundColor: alpha(theme.palette.background.paper, DIALOG_GLASS.backgroundAlpha),\n borderWidth: DIALOG_BORDER_WIDTH,\n borderStyle: 'solid',\n borderColor: alpha(theme.palette.primary.main, DIALOG_GLASS.borderAlpha),\n };\n}\n\nexport interface DialogLookArgs {\n variant: DialogVariant;\n size: DialogSize;\n borderRadius: DialogBorderRadius;\n glass: boolean;\n glow: boolean;\n pulse: boolean;\n}\n\nexport interface DialogLook {\n /** Where the paper sits inside the modal, and how far in from the edges. */\n overlay: ViewStyle;\n paper: ViewStyle;\n /** The paper's corner radius, for the pulse ring to follow. */\n radius: number;\n}\n\n/** The centred paper of `default` and `glass`, before the variant paints over it. */\nfunction centredOverlay(theme: UiTheme): ViewStyle {\n return {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n // The web gives the PAPER `margin: theme.spacing(2)`; an inset on the\n // overlay is the same gap and leaves the paper free to be `100%` wide.\n padding: theme.spacing(DIALOG_MARGIN_UNITS),\n };\n}\n\nfunction variantPaper(theme: UiTheme, a: DialogLookArgs, radius: number): ViewStyle {\n switch (a.variant) {\n case 'glass':\n return {\n ...glassSurface(theme),\n boxShadow: [\n {\n offsetX: 0,\n offsetY: DIALOG_GLASS.shadow.offsetY,\n blurRadius: DIALOG_GLASS.shadow.blurRadius,\n spreadDistance: 0,\n color: alpha(BLACK, DIALOG_GLASS.shadow.alpha),\n },\n ],\n };\n case 'fullscreen':\n return { borderRadius: 0, width: '100%', height: '100%', maxWidth: undefined };\n case 'drawer':\n return {\n // SQUARE, because that is what the web paints. Two things have to fail\n // for a drawer to have a corner there and both do: the `sx` builds\n // `${radius}px 0 0 ${radius}px` out of `theme.spacing()`, which already\n // returns a px string, so the declaration reads `16pxpx 0 0 16pxpx` and\n // the browser drops it; and that `sx` lands on a plain `Box` INSIDE the\n // Drawer, whose own Paper MUI renders with `square: true`. Nothing is\n // left to round the corners. See NATIVE-NOTES.md.\n borderRadius: 0,\n width: DIALOG_MAX_WIDTH[a.size] ?? DIALOG_MAX_WIDTH.md,\n maxWidth: '100%',\n height: '100%',\n maxHeight: undefined,\n };\n default:\n return a.glass ? { ...glassSurface(theme), borderRadius: radius } : {};\n }\n}\n\n/** `fullscreen` and `drawer` fill the screen, so neither is inset or centred. */\nfunction overlayFor(theme: UiTheme, variant: DialogVariant): ViewStyle {\n if (variant === 'fullscreen') return { flex: 1 };\n if (variant === 'drawer') return { flex: 1, alignItems: 'flex-end' };\n return centredOverlay(theme);\n}\n\n/**\n * The overlay's bottom inset while the soft keyboard covers `overlap` dp of it\n * (FUT-3021), or nothing while it covers none.\n *\n * The inset grows by exactly the overlap, on top of the margin the variant\n * already keeps, so the space the paper may fill — its `maxHeight: '100%'`, or\n * the `height: '100%'` of `fullscreen` and `drawer` — shrinks by the same\n * amount and ends where the keyboard starts. A centred paper re-centres in what\n * is left, and a body taller than that scrolls inside `DialogContent` with the\n * actions still on screen.\n */\nexport function dialogKeyboardLift(theme: UiTheme, variant: DialogVariant, overlap: number): ViewStyle | null {\n if (overlap <= 0) return null;\n const margin = variant === 'fullscreen' || variant === 'drawer' ? 0 : theme.spacing(DIALOG_MARGIN_UNITS);\n return { paddingBottom: margin + overlap };\n}\n\n/**\n * Everything the dialog paints, decided once per render.\n *\n * The base carries what MUI's `Paper` gives every dialog — the paper colour and\n * `shadows[24]` — because React Native has no Paper to inherit them from; a\n * `glow` or a variant shadow then overrides it exactly as the `sx` does.\n *\n * `overflow` clips the paper's children to its corners, as MUI's own\n * `overflow-y: auto` does — except under `pulse` or `glow`, which have to paint\n * OUTSIDE the paper and are clipped by the view's own overflow on a device.\n */\nexport function dialogLook(theme: UiTheme, a: DialogLookArgs): DialogLook {\n const radius = dialogRadius(theme, a.borderRadius);\n\n const base: ViewStyle = {\n position: 'relative',\n borderRadius: radius,\n backgroundColor: theme.palette.background.paper,\n boxShadow: DIALOG_PAPER_SHADOW,\n width: '100%',\n maxWidth: DIALOG_MAX_WIDTH[a.size] ?? DIALOG_MAX_WIDTH.md,\n maxHeight: '100%',\n overflow: a.pulse || a.glow ? 'visible' : 'hidden',\n };\n\n return {\n radius,\n overlay: overlayFor(theme, a.variant),\n paper: {\n ...base,\n ...(a.glow\n ? { boxShadow: halo(DIALOG_GLOW.blurRadius, alpha(theme.palette.primary.main, DIALOG_GLOW.alpha)) }\n : null),\n ...variantPaper(theme, a, radius),\n },\n };\n}\n","import * as React from 'react';\nimport {\n Pressable,\n ScrollView,\n StyleSheet,\n Text as RNText,\n View,\n type TextStyle,\n type ViewStyle,\n} from 'react-native';\n\nimport {\n DIALOG_ACTIONS,\n DIALOG_CLOSE_BUTTON,\n DIALOG_CONTENT_PADDING_UNITS,\n DIALOG_HEADER_CHILDREN_PADDING_UNITS,\n DIALOG_TITLE,\n DIALOG_TITLED_BODY_PADDING_TOP_UNITS,\n} from './Dialog.metrics';\nimport type {\n DialogActionsAlignment,\n DialogActionsProps,\n DialogContentProps,\n DialogHeaderProps,\n} from './Dialog.types.native';\nimport { Icon } from '../../../icons/Icon.native';\nimport { childTestId, slotTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { renderTextChildren } from '../../../platform/text-children';\nimport { useUiTheme } from '../../../provider/use-ui-theme.native';\nimport { muiTypeStyle } from '../../../tokens/mui-type';\nimport type { UiTheme } from '../../../tokens/theme';\n\n/**\n * THE THREE SLOTS A DIALOG IS BUILT OUT OF, ON REACT NATIVE.\n *\n * The web overrides only padding, alignment and the dividers, so the rest of\n * the numbers are MUI's own and are restated in `Dialog.metrics.ts` rather than\n * re-invented.\n */\n\n/**\n * Is there a title above this body?\n *\n * The web asks the DOM — `.MuiDialogTitle-root + &` — because only the CSS knows\n * which `DialogContent` actually FOLLOWS the title. React Native has no sibling\n * selector, so `Dialog` says so through context instead; the difference is that\n * a second `DialogContent` further down gets the tighter top padding too.\n */\nexport const DialogTitledContext = React.createContext(false);\n\n/** The body text of a slot, in MUI's `body1`, so a bare string is not unstyled. */\nexport const dialogBodyTextStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'body1'),\n color: theme.palette.text.primary,\n});\n\n/** MUI's dialog title: `h6`, in the body ink. */\nexport const dialogTitleStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'h6'),\n color: theme.palette.text.primary,\n});\n\n/** The subtitle under it: `body2`, muted, one half-unit down. */\nexport const dialogSubtitleStyle = (theme: UiTheme): TextStyle => ({\n ...muiTypeStyle(theme, 'body2'),\n color: theme.palette.text.secondary,\n marginTop: theme.spacing(DIALOG_TITLE.subtitleGapUnits),\n});\n\nfunction CloseButton({ onPress, testID }: { onPress: () => void; testID: string }): React.JSX.Element {\n const theme = useUiTheme();\n return (\n <Pressable\n role=\"button\"\n aria-label=\"close\"\n testID={testID}\n onPress={onPress}\n style={({ pressed }) => [\n styles.close,\n pressed ? { backgroundColor: theme.palette.action.hover } : null,\n ]}\n >\n <Icon name=\"Close\" size={DIALOG_CLOSE_BUTTON.iconSize} color={theme.palette.text.secondary} />\n </Pressable>\n );\n}\n\nexport function DialogHeader({\n children,\n title,\n subtitle,\n showCloseButton = true,\n onClose,\n ...others\n}: DialogHeaderProps): React.JSX.Element {\n const theme = useUiTheme();\n const { style, ...viewProps } = withoutTestIdProps(others);\n\n if (children) {\n return (\n <View\n testID={slotTestId(others, 'header', 'dialog')}\n style={[\n {\n padding: theme.spacing(DIALOG_HEADER_CHILDREN_PADDING_UNITS),\n borderBottomWidth: 1,\n borderBottomColor: theme.palette.divider,\n borderStyle: 'solid',\n },\n style,\n ]}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </View>\n );\n }\n\n const paddingBottom = theme.spacing(\n subtitle ? DIALOG_TITLE.paddingBottomUnits.withSubtitle : DIALOG_TITLE.paddingBottomUnits.plain,\n );\n\n return (\n <View\n testID={slotTestId(others, 'title', 'dialog')}\n style={[styles.header, { paddingBottom }, style]}\n {...viewProps}\n >\n <View style={styles.headerText}>\n {/*\n MUI's `DialogTitle` renders `component: \"h2\"`, so the web dialog has a\n heading and the native one had none. `role`/`aria-level` are what\n react-native-web maps to an `<h2>`; a device reads the same pair\n through `accessibilityRole=\"header\"`, which RN derives from `role`.\n */}\n <RNText role=\"heading\" aria-level={2} style={dialogTitleStyle(theme)}>\n {title}\n </RNText>\n {subtitle ? <RNText style={dialogSubtitleStyle(theme)}>{subtitle}</RNText> : null}\n </View>\n {showCloseButton && onClose ? (\n <CloseButton onPress={onClose} testID={childTestId(others, 'close', 'dialog')} />\n ) : null}\n </View>\n );\n}\n\nDialogHeader.displayName = 'DialogHeader';\n\nexport function DialogContent({\n children,\n dividers = false,\n dense = false,\n ...others\n}: DialogContentProps): React.JSX.Element {\n const theme = useUiTheme();\n const titled = React.useContext(DialogTitledContext);\n const { style, ...viewProps } = withoutTestIdProps(others);\n const padding = theme.spacing(\n dense ? DIALOG_CONTENT_PADDING_UNITS.dense : DIALOG_CONTENT_PADDING_UNITS.normal,\n );\n const paddingTop = titled\n ? theme.spacing(DIALOG_TITLED_BODY_PADDING_TOP_UNITS)\n : padding;\n\n return (\n <ScrollView\n testID={slotTestId(others, 'content', 'dialog')}\n style={[\n styles.content,\n dividers\n ? {\n borderTopWidth: 1,\n borderBottomWidth: 1,\n borderStyle: 'solid',\n borderColor: theme.palette.divider,\n }\n : null,\n style,\n ]}\n contentContainerStyle={{ padding, paddingTop }}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </ScrollView>\n );\n}\n\nDialogContent.displayName = 'DialogContent';\n\nconst JUSTIFY: Record<DialogActionsAlignment, NonNullable<ViewStyle['justifyContent']>> = {\n left: 'flex-start',\n center: 'center',\n right: 'flex-end',\n 'space-between': 'space-between',\n};\n\nexport function DialogActions({\n children,\n alignment = 'right',\n spacing = DIALOG_ACTIONS.defaultSpacingUnits,\n ...others\n}: DialogActionsProps): React.JSX.Element {\n const theme = useUiTheme();\n const { style, ...viewProps } = withoutTestIdProps(others);\n\n return (\n <View\n testID={slotTestId(others, 'actions', 'dialog')}\n style={[\n styles.actions,\n {\n padding: theme.spacing(DIALOG_ACTIONS.paddingUnits),\n justifyContent: JUSTIFY[alignment],\n gap: theme.spacing(spacing),\n },\n style,\n ]}\n {...viewProps}\n >\n {renderTextChildren(children, dialogBodyTextStyle(theme))}\n </View>\n );\n}\n\nDialogActions.displayName = 'DialogActions';\n\nconst styles = StyleSheet.create({\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexGrow: 0,\n flexShrink: 0,\n paddingTop: DIALOG_TITLE.padding.top,\n paddingHorizontal: DIALOG_TITLE.padding.horizontal,\n },\n headerText: {\n flexShrink: 1,\n },\n close: {\n padding: DIALOG_CLOSE_BUTTON.padding,\n borderRadius: 9999,\n alignItems: 'center',\n justifyContent: 'center',\n },\n content: {\n flexGrow: 1,\n flexShrink: 1,\n },\n actions: {\n flexDirection: 'row',\n alignItems: 'center',\n flexGrow: 0,\n flexShrink: 0,\n },\n});\n","import * as React from 'react';\nimport {\n Keyboard,\n Platform,\n type KeyboardEvent,\n type KeyboardEventName,\n type LayoutChangeEvent,\n} from 'react-native';\n\n/**\n * HOW FAR THE SOFT KEYBOARD COVERS A FULL-WINDOW VIEW, AND NOTHING MORE.\n *\n * React Native's `Modal` asks Android for `SOFT_INPUT_ADJUST_RESIZE`, and under\n * edge-to-edge that asks for nothing: the window keeps its full height and the\n * keyboard is drawn over it. iOS never resized a modal at all. So an overlay\n * that fills the modal has its bottom under the keyboard, and whatever sits\n * there — a dialog's actions — is hidden until the keyboard goes away.\n *\n * This reads the keyboard's own top edge from the `Keyboard` events and the\n * view's own bottom edge from its layout, and answers the difference. Reading\n * the OVERLAP rather than the keyboard's height is what keeps it right when the\n * window DID resize (a host that is not edge-to-edge): the view then already\n * ends at the keyboard, the overlap is zero, and nothing is lifted twice. No\n * soft-input mode is set here, deliberately — a second one would fight the one\n * `Modal` already asks for.\n *\n * The view's layout `y` is relative to its parent, so the view is expected to\n * be the root of a window that starts at the top of the screen — which a\n * `transparent` modal is, on iOS and under Android edge-to-edge alike.\n */\nexport function keyboardOverlap(viewBottom: number | null, keyboardTop: number | null): number {\n if (viewBottom === null || keyboardTop === null) return 0;\n return Math.max(0, viewBottom - keyboardTop);\n}\n\n/** iOS announces the keyboard before it moves; Android only once it has. */\nconst KEYBOARD_EVENTS: { show: KeyboardEventName; hide: KeyboardEventName } =\n Platform.OS === 'ios'\n ? { show: 'keyboardWillShow', hide: 'keyboardWillHide' }\n : { show: 'keyboardDidShow', hide: 'keyboardDidHide' };\n\nexport interface KeyboardOverlap {\n /** How many dp of the view the keyboard covers; `0` while it is down. */\n overlap: number;\n /** Goes on the view being measured. */\n onLayout: (event: LayoutChangeEvent) => void;\n}\n\n/**\n * The keyboard's overlap with the view `onLayout` is put on. Android re-sends\n * `keyboardDidShow` when the keyboard changes height while it stays up (an\n * emoji panel, a suggestion strip), so the overlap follows those too.\n */\nexport function useKeyboardOverlap(): KeyboardOverlap {\n const [viewBottom, setViewBottom] = React.useState<number | null>(null);\n const [keyboardTop, setKeyboardTop] = React.useState<number | null>(null);\n\n React.useEffect(() => {\n const shown = Keyboard.addListener(KEYBOARD_EVENTS.show, (event: KeyboardEvent) => {\n setKeyboardTop(event.endCoordinates.screenY);\n });\n const hidden = Keyboard.addListener(KEYBOARD_EVENTS.hide, () => {\n setKeyboardTop(null);\n });\n return () => {\n shown.remove();\n hidden.remove();\n };\n }, []);\n\n const onLayout = React.useCallback((event: LayoutChangeEvent) => {\n const { y, height } = event.nativeEvent.layout;\n setViewBottom(y + height);\n }, []);\n\n return { overlap: keyboardOverlap(viewBottom, keyboardTop), onLayout };\n}\n"],"mappings":"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","names":["React","Pressable","ScrollView","StyleSheet","View","React","React","jsx","jsxs","styles","StyleSheet","ScrollView","View","Pressable"]}
@@ -29,6 +29,18 @@ export interface DialogLook {
29
29
  /** The paper's corner radius, for the pulse ring to follow. */
30
30
  radius: number;
31
31
  }
32
+ /**
33
+ * The overlay's bottom inset while the soft keyboard covers `overlap` dp of it
34
+ * (FUT-3021), or nothing while it covers none.
35
+ *
36
+ * The inset grows by exactly the overlap, on top of the margin the variant
37
+ * already keeps, so the space the paper may fill — its `maxHeight: '100%'`, or
38
+ * the `height: '100%'` of `fullscreen` and `drawer` — shrinks by the same
39
+ * amount and ends where the keyboard starts. A centred paper re-centres in what
40
+ * is left, and a body taller than that scrolls inside `DialogContent` with the
41
+ * actions still on screen.
42
+ */
43
+ export declare function dialogKeyboardLift(theme: UiTheme, variant: DialogVariant, overlap: number): ViewStyle | null;
32
44
  /**
33
45
  * Everything the dialog paints, decided once per render.
34
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.look.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.look.native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAanF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;;;;GAQG;AAEH,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAIxD;AAYD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AA0DD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,GAAG,UAAU,CAyBxE"}
1
+ {"version":3,"file":"Dialog.look.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.look.native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAanF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;;;;GAQG;AAEH,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/E;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAIxD;AAYD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AA0DD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAI5G;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,GAAG,UAAU,CAyBxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAiBzD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAwDlF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmE/D;yBAnEe,MAAM"}
1
+ {"version":3,"file":"Dialog.native.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/Dialog/Dialog.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAkBzD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAwDlF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA4E/D;yBA5Ee,MAAM"}
@@ -0,0 +1,36 @@
1
+ import { type LayoutChangeEvent } from 'react-native';
2
+ /**
3
+ * HOW FAR THE SOFT KEYBOARD COVERS A FULL-WINDOW VIEW, AND NOTHING MORE.
4
+ *
5
+ * React Native's `Modal` asks Android for `SOFT_INPUT_ADJUST_RESIZE`, and under
6
+ * edge-to-edge that asks for nothing: the window keeps its full height and the
7
+ * keyboard is drawn over it. iOS never resized a modal at all. So an overlay
8
+ * that fills the modal has its bottom under the keyboard, and whatever sits
9
+ * there — a dialog's actions — is hidden until the keyboard goes away.
10
+ *
11
+ * This reads the keyboard's own top edge from the `Keyboard` events and the
12
+ * view's own bottom edge from its layout, and answers the difference. Reading
13
+ * the OVERLAP rather than the keyboard's height is what keeps it right when the
14
+ * window DID resize (a host that is not edge-to-edge): the view then already
15
+ * ends at the keyboard, the overlap is zero, and nothing is lifted twice. No
16
+ * soft-input mode is set here, deliberately — a second one would fight the one
17
+ * `Modal` already asks for.
18
+ *
19
+ * The view's layout `y` is relative to its parent, so the view is expected to
20
+ * be the root of a window that starts at the top of the screen — which a
21
+ * `transparent` modal is, on iOS and under Android edge-to-edge alike.
22
+ */
23
+ export declare function keyboardOverlap(viewBottom: number | null, keyboardTop: number | null): number;
24
+ export interface KeyboardOverlap {
25
+ /** How many dp of the view the keyboard covers; `0` while it is down. */
26
+ overlap: number;
27
+ /** Goes on the view being measured. */
28
+ onLayout: (event: LayoutChangeEvent) => void;
29
+ }
30
+ /**
31
+ * The keyboard's overlap with the view `onLayout` is put on. Android re-sends
32
+ * `keyboardDidShow` when the keyboard changes height while it stays up (an
33
+ * emoji panel, a suggestion strip), so the overlap follows those too.
34
+ */
35
+ export declare function useKeyboardOverlap(): KeyboardOverlap;
36
+ //# sourceMappingURL=keyboard-overlap.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard-overlap.native.d.ts","sourceRoot":"","sources":["../../../src/platform/keyboard-overlap.native.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG7F;AAQD,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAuBpD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.52.0",
3
+ "version": "6.52.1",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -120,6 +120,23 @@ function overlayFor(theme: UiTheme, variant: DialogVariant): ViewStyle {
120
120
  return centredOverlay(theme);
121
121
  }
122
122
 
123
+ /**
124
+ * The overlay's bottom inset while the soft keyboard covers `overlap` dp of it
125
+ * (FUT-3021), or nothing while it covers none.
126
+ *
127
+ * The inset grows by exactly the overlap, on top of the margin the variant
128
+ * already keeps, so the space the paper may fill — its `maxHeight: '100%'`, or
129
+ * the `height: '100%'` of `fullscreen` and `drawer` — shrinks by the same
130
+ * amount and ends where the keyboard starts. A centred paper re-centres in what
131
+ * is left, and a body taller than that scrolls inside `DialogContent` with the
132
+ * actions still on screen.
133
+ */
134
+ export function dialogKeyboardLift(theme: UiTheme, variant: DialogVariant, overlap: number): ViewStyle | null {
135
+ if (overlap <= 0) return null;
136
+ const margin = variant === 'fullscreen' || variant === 'drawer' ? 0 : theme.spacing(DIALOG_MARGIN_UNITS);
137
+ return { paddingBottom: margin + overlap };
138
+ }
139
+
123
140
  /**
124
141
  * Everything the dialog paints, decided once per render.
125
142
  *
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import { Modal, Pressable, ScrollView, StyleSheet, View } from 'react-native';
3
3
 
4
4
  import { hasSpacingSlot, withDialogDefaults } from './Dialog.helpers';
5
- import { dialogBackdrop, dialogLook } from './Dialog.look.native';
5
+ import { dialogBackdrop, dialogKeyboardLift, dialogLook } from './Dialog.look.native';
6
6
  import { DIALOG_BODY_PADDING_UNITS, DIALOG_PULSE } from './Dialog.metrics';
7
7
  import type { DialogProps } from './Dialog.types.native';
8
8
  import {
@@ -12,6 +12,7 @@ import {
12
12
  DialogTitledContext,
13
13
  dialogBodyTextStyle,
14
14
  } from './DialogParts.native';
15
+ import { useKeyboardOverlap } from '../../../platform/keyboard-overlap.native';
15
16
  import { PulseRing } from '../../../platform/pulse-ring.native';
16
17
  import { resolveTestId, withoutTestIdProps } from '../../../platform/test-id';
17
18
  import { renderTextChildren } from '../../../platform/text-children';
@@ -106,9 +107,18 @@ export function Dialog(rawProps: DialogProps): React.JSX.Element {
106
107
  // backdrop and Escape (which `Modal` routes through `onRequestClose`).
107
108
  const dismiss = persistent ? undefined : onClose;
108
109
 
110
+ // Edge-to-edge defeats the `SOFT_INPUT_ADJUST_RESIZE` that `Modal` asks for,
111
+ // so the window never shrinks for the keyboard and nothing else moves the
112
+ // paper: the overlay measures how much of it the keyboard covers and takes
113
+ // that off the paper's room itself (FUT-3021).
114
+ const keyboard = useKeyboardOverlap();
115
+
109
116
  return (
110
117
  <Modal visible={open} transparent onRequestClose={dismiss} {...modalProps}>
111
- <View style={look.overlay}>
118
+ <View
119
+ style={[look.overlay, dialogKeyboardLift(theme, variant, keyboard.overlap)]}
120
+ onLayout={keyboard.onLayout}
121
+ >
112
122
  <Pressable
113
123
  aria-hidden
114
124
  tabIndex={-1}
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Keyboard,
4
+ Platform,
5
+ type KeyboardEvent,
6
+ type KeyboardEventName,
7
+ type LayoutChangeEvent,
8
+ } from 'react-native';
9
+
10
+ /**
11
+ * HOW FAR THE SOFT KEYBOARD COVERS A FULL-WINDOW VIEW, AND NOTHING MORE.
12
+ *
13
+ * React Native's `Modal` asks Android for `SOFT_INPUT_ADJUST_RESIZE`, and under
14
+ * edge-to-edge that asks for nothing: the window keeps its full height and the
15
+ * keyboard is drawn over it. iOS never resized a modal at all. So an overlay
16
+ * that fills the modal has its bottom under the keyboard, and whatever sits
17
+ * there — a dialog's actions — is hidden until the keyboard goes away.
18
+ *
19
+ * This reads the keyboard's own top edge from the `Keyboard` events and the
20
+ * view's own bottom edge from its layout, and answers the difference. Reading
21
+ * the OVERLAP rather than the keyboard's height is what keeps it right when the
22
+ * window DID resize (a host that is not edge-to-edge): the view then already
23
+ * ends at the keyboard, the overlap is zero, and nothing is lifted twice. No
24
+ * soft-input mode is set here, deliberately — a second one would fight the one
25
+ * `Modal` already asks for.
26
+ *
27
+ * The view's layout `y` is relative to its parent, so the view is expected to
28
+ * be the root of a window that starts at the top of the screen — which a
29
+ * `transparent` modal is, on iOS and under Android edge-to-edge alike.
30
+ */
31
+ export function keyboardOverlap(viewBottom: number | null, keyboardTop: number | null): number {
32
+ if (viewBottom === null || keyboardTop === null) return 0;
33
+ return Math.max(0, viewBottom - keyboardTop);
34
+ }
35
+
36
+ /** iOS announces the keyboard before it moves; Android only once it has. */
37
+ const KEYBOARD_EVENTS: { show: KeyboardEventName; hide: KeyboardEventName } =
38
+ Platform.OS === 'ios'
39
+ ? { show: 'keyboardWillShow', hide: 'keyboardWillHide' }
40
+ : { show: 'keyboardDidShow', hide: 'keyboardDidHide' };
41
+
42
+ export interface KeyboardOverlap {
43
+ /** How many dp of the view the keyboard covers; `0` while it is down. */
44
+ overlap: number;
45
+ /** Goes on the view being measured. */
46
+ onLayout: (event: LayoutChangeEvent) => void;
47
+ }
48
+
49
+ /**
50
+ * The keyboard's overlap with the view `onLayout` is put on. Android re-sends
51
+ * `keyboardDidShow` when the keyboard changes height while it stays up (an
52
+ * emoji panel, a suggestion strip), so the overlap follows those too.
53
+ */
54
+ export function useKeyboardOverlap(): KeyboardOverlap {
55
+ const [viewBottom, setViewBottom] = React.useState<number | null>(null);
56
+ const [keyboardTop, setKeyboardTop] = React.useState<number | null>(null);
57
+
58
+ React.useEffect(() => {
59
+ const shown = Keyboard.addListener(KEYBOARD_EVENTS.show, (event: KeyboardEvent) => {
60
+ setKeyboardTop(event.endCoordinates.screenY);
61
+ });
62
+ const hidden = Keyboard.addListener(KEYBOARD_EVENTS.hide, () => {
63
+ setKeyboardTop(null);
64
+ });
65
+ return () => {
66
+ shown.remove();
67
+ hidden.remove();
68
+ };
69
+ }, []);
70
+
71
+ const onLayout = React.useCallback((event: LayoutChangeEvent) => {
72
+ const { y, height } = event.nativeEvent.layout;
73
+ setViewBottom(y + height);
74
+ }, []);
75
+
76
+ return { overlap: keyboardOverlap(viewBottom, keyboardTop), onLayout };
77
+ }