@astryxdesign/lab 0.1.9-canary.d600202 → 0.1.9-canary.e00a241

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.
Files changed (47) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +95 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
  3. package/dist/BottomSheet/BottomSheet.js +150 -0
  4. package/dist/BottomSheet/index.d.ts +9 -0
  5. package/dist/BottomSheet/index.d.ts.map +1 -0
  6. package/dist/BottomSheet/index.js +10 -0
  7. package/dist/BottomSheet/useSheetGestures.d.ts +69 -0
  8. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
  9. package/dist/BottomSheet/useSheetGestures.js +163 -0
  10. package/dist/RichTextEditor/RichTextEditor.d.ts +67 -6
  11. package/dist/RichTextEditor/RichTextEditor.d.ts.map +1 -1
  12. package/dist/RichTextEditor/RichTextEditor.js +165 -12
  13. package/dist/RichTextEditor/editorTheme.d.ts.map +1 -1
  14. package/dist/RichTextEditor/editorTheme.js +33 -7
  15. package/dist/RichTextEditor/index.d.ts +1 -1
  16. package/dist/RichTextEditor/index.d.ts.map +1 -1
  17. package/dist/Stepper/Step.js +1 -1
  18. package/dist/Stepper/Stepper.d.ts +1 -1
  19. package/dist/Stepper/StepperContext.d.ts +1 -1
  20. package/dist/Stepper/StepperContext.js +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -0
  24. package/dist/lab.css +23 -0
  25. package/package.json +2 -2
  26. package/src/BottomSheet/BottomSheet.doc.mjs +105 -0
  27. package/src/BottomSheet/BottomSheet.test.tsx +233 -0
  28. package/src/BottomSheet/BottomSheet.tsx +226 -0
  29. package/src/BottomSheet/index.ts +11 -0
  30. package/src/BottomSheet/useSheetGestures.doc.mjs +31 -0
  31. package/src/BottomSheet/useSheetGestures.test.ts +160 -0
  32. package/src/BottomSheet/useSheetGestures.ts +226 -0
  33. package/src/ChatReasoning/ChatReasoning.doc.mjs +49 -2
  34. package/src/CodeEditor/CodeEditor.doc.mjs +67 -2
  35. package/src/LogStream/LogStream.doc.mjs +53 -2
  36. package/src/RichTextEditor/RichTextEditor.doc.mjs +19 -1
  37. package/src/RichTextEditor/RichTextEditor.test.tsx +441 -11
  38. package/src/RichTextEditor/RichTextEditor.tsx +289 -31
  39. package/src/RichTextEditor/editorTheme.ts +63 -3
  40. package/src/RichTextEditor/index.ts +2 -0
  41. package/src/Schedule/Schedule.doc.mjs +143 -3
  42. package/src/Stepper/Step.tsx +1 -1
  43. package/src/Stepper/Stepper.doc.mjs +6 -0
  44. package/src/Stepper/Stepper.tsx +1 -1
  45. package/src/Stepper/StepperContext.ts +1 -1
  46. package/src/__tests__/labApiContractDrift.test.tsx +57 -0
  47. package/src/index.ts +5 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @file BottomSheet.tsx
3
+ * @input Uses React, StyleX, theme tokens, Drawer, useSheetGestures, themeProps
4
+ * @output Exports BottomSheet component and BottomSheetProps
5
+ * @position Lab implementation; consumed by index.ts, tested by BottomSheet.test.tsx, demonstrated in Storybook
6
+ *
7
+ * A mobile touch surface that rises from the bottom edge with a grab handle
8
+ * and swipe-to-dismiss. Built ON TOP OF the lab Drawer's native `<dialog>`
9
+ * engine (`side="bottom"`) rather than forking it — Drawer owns
10
+ * showModal/show, the open/close slide animation, focus trap + restore,
11
+ * scroll lock, the Escape/scrim contract, and the LIFO overlay registry.
12
+ * BottomSheet layers the grab handle and the drag-to-dismiss gesture on top,
13
+ * inside the dialog.
14
+ *
15
+ * The gesture translate is applied to an inner sheet wrapper, not the
16
+ * `<dialog>` itself, so Drawer owns the open/close slide and the drag
17
+ * translates within it without the two transforms fighting.
18
+ *
19
+ * SYNC: When modified, update these files to stay in sync:
20
+ * - /packages/lab/src/BottomSheet/BottomSheet.doc.mjs (props table, features, usage)
21
+ * - /packages/lab/src/BottomSheet/BottomSheet.test.tsx (tests for new/changed behavior)
22
+ * - /packages/lab/src/BottomSheet/index.ts (exports if types change)
23
+ * - /apps/storybook/stories/BottomSheet.stories.tsx (examples and visual coverage)
24
+ */
25
+ import { type ReactNode } from 'react';
26
+ import type { BaseProps } from '@astryxdesign/core';
27
+ /**
28
+ * Height budget for each named size. `medium`/`tall` track the viewport so
29
+ * the sheet scales with the device; `short` is a fixed peek height. `auto`
30
+ * is handled separately (fits content, capped at the `tall` budget).
31
+ */
32
+ declare const HEIGHT_BUDGETS: {
33
+ readonly short: "240px";
34
+ readonly medium: "50dvh";
35
+ readonly tall: "90dvh";
36
+ readonly auto: "90dvh";
37
+ };
38
+ export type BottomSheetHeight = keyof typeof HEIGHT_BUDGETS;
39
+ export interface BottomSheetProps extends BaseProps<HTMLDialogElement> {
40
+ /** Ref forwarded to the underlying <dialog> element. */
41
+ ref?: React.Ref<HTMLDialogElement>;
42
+ /** Whether the sheet is open. Fully controlled — pair with `onOpenChange`. */
43
+ isOpen: boolean;
44
+ /**
45
+ * Called when the sheet opens or closes. The boolean is the requested
46
+ * next state (`false` on Escape, scrim click, or a swipe past the dismiss
47
+ * threshold). The caller owns the open state.
48
+ */
49
+ onOpenChange: (isOpen: boolean) => void;
50
+ /**
51
+ * Accessible label for the sheet (required — the sheet has no built-in
52
+ * heading to derive a name from).
53
+ */
54
+ label: string;
55
+ /** Sheet content, rendered below the grab handle in a scrollable area. */
56
+ children: ReactNode;
57
+ /**
58
+ * How tall the sheet is:
59
+ * - `'short'` — a fixed peek height
60
+ * - `'medium'` — half the viewport
61
+ * - `'tall'` — nearly the full viewport
62
+ * - `'auto'` — fits its content, capped at the `'tall'` budget
63
+ *
64
+ * On viewports shorter than the budget the sheet fills the available
65
+ * height.
66
+ * @default 'medium'
67
+ */
68
+ height?: BottomSheetHeight;
69
+ /** Test ID for the root element. */
70
+ 'data-testid'?: string;
71
+ }
72
+ /**
73
+ * A mobile touch sheet that rises from the bottom edge, with a grab handle
74
+ * and swipe-to-dismiss. Built on the lab Drawer's `<dialog>` engine
75
+ * (`side="bottom"`): Drawer owns the open/close slide, focus trap + restore,
76
+ * scroll lock, and the Escape/scrim contract, while BottomSheet layers the
77
+ * grab handle and drag-to-dismiss on top.
78
+ *
79
+ * @example
80
+ * ```
81
+ * const [isOpen, setIsOpen] = useState(false);
82
+ * <BottomSheet
83
+ * isOpen={isOpen}
84
+ * onOpenChange={setIsOpen}
85
+ * label="Filters">
86
+ * <FilterControls />
87
+ * </BottomSheet>
88
+ * ```
89
+ */
90
+ export declare function BottomSheet({ isOpen, onOpenChange, label, children, height, xstyle, ...props }: BottomSheetProps): import("react").JSX.Element;
91
+ export declare namespace BottomSheet {
92
+ var displayName: string;
93
+ }
94
+ export {};
95
+ //# sourceMappingURL=BottomSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAUlD;;;;GAIG;AACH,QAAA,MAAM,cAAc;;;;;CAKV,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,cAAc,CAAC;AAoE5D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACpE,wDAAwD;IACxD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAE3B,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,MAAiB,EACjB,MAAM,EACN,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAoClB;yBA5Ce,WAAW"}
@@ -0,0 +1,150 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file BottomSheet.tsx
7
+ * @input Uses React, StyleX, theme tokens, Drawer, useSheetGestures, themeProps
8
+ * @output Exports BottomSheet component and BottomSheetProps
9
+ * @position Lab implementation; consumed by index.ts, tested by BottomSheet.test.tsx, demonstrated in Storybook
10
+ *
11
+ * A mobile touch surface that rises from the bottom edge with a grab handle
12
+ * and swipe-to-dismiss. Built ON TOP OF the lab Drawer's native `<dialog>`
13
+ * engine (`side="bottom"`) rather than forking it — Drawer owns
14
+ * showModal/show, the open/close slide animation, focus trap + restore,
15
+ * scroll lock, the Escape/scrim contract, and the LIFO overlay registry.
16
+ * BottomSheet layers the grab handle and the drag-to-dismiss gesture on top,
17
+ * inside the dialog.
18
+ *
19
+ * The gesture translate is applied to an inner sheet wrapper, not the
20
+ * `<dialog>` itself, so Drawer owns the open/close slide and the drag
21
+ * translates within it without the two transforms fighting.
22
+ *
23
+ * SYNC: When modified, update these files to stay in sync:
24
+ * - /packages/lab/src/BottomSheet/BottomSheet.doc.mjs (props table, features, usage)
25
+ * - /packages/lab/src/BottomSheet/BottomSheet.test.tsx (tests for new/changed behavior)
26
+ * - /packages/lab/src/BottomSheet/index.ts (exports if types change)
27
+ * - /apps/storybook/stories/BottomSheet.stories.tsx (examples and visual coverage)
28
+ */
29
+ import * as stylex from '@stylexjs/stylex';
30
+ import '@astryxdesign/core/theme/tokens.stylex';
31
+ import { colorVars, radiusVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';
32
+ import { mergeProps, themeProps } from '@astryxdesign/core/utils';
33
+ import { Drawer } from "../Drawer/index.js";
34
+ import { useSheetGestures } from "./useSheetGestures.js";
35
+
36
+ /**
37
+ * Height budget for each named size. `medium`/`tall` track the viewport so
38
+ * the sheet scales with the device; `short` is a fixed peek height. `auto`
39
+ * is handled separately (fits content, capped at the `tall` budget).
40
+ */
41
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
42
+ const HEIGHT_BUDGETS = {
43
+ short: '240px',
44
+ medium: '50dvh',
45
+ tall: '90dvh',
46
+ auto: '90dvh'
47
+ };
48
+ // Sheets on wide touch devices (tablets) shouldn't stretch edge to edge;
49
+ // cap and center them. Matches the `sm` layout breakpoint. On phones the
50
+ // viewport is narrower than this, so the sheet stays full-width.
51
+ const MAX_SHEET_WIDTH = 640;
52
+ const styles = {
53
+ sheet: {
54
+ k1xSpc: "x78zum5",
55
+ kXwgrk: "xdt5ytf",
56
+ kAzted: "x2lwn1j",
57
+ kZKoxP: "x5yr21d",
58
+ kzqmXN: "xh8yej3",
59
+ ks0D6T: "x11gisft",
60
+ kUOVxO: "xvueqy4",
61
+ kWkggS: "x10xzikg",
62
+ krdFHd: "x183tx6i",
63
+ kfmiAY: "x663v1",
64
+ kVQacm: "xb3r6kr",
65
+ kGO01o: "x1a148e8",
66
+ k6sLGO: "x1so62im",
67
+ $$css: true
68
+ },
69
+ dialogSurface: {
70
+ kWkggS: "xjbqb8w",
71
+ kGVxlE: "x1gnnqk1",
72
+ kEafiO: "x972fbf",
73
+ $$css: true
74
+ },
75
+ autoHeight: {
76
+ kZKoxP: "xg7h5cd",
77
+ kskxy: "x1ismmbu",
78
+ $$css: true
79
+ }
80
+ };
81
+ /**
82
+ * A mobile touch sheet that rises from the bottom edge, with a grab handle
83
+ * and swipe-to-dismiss. Built on the lab Drawer's `<dialog>` engine
84
+ * (`side="bottom"`): Drawer owns the open/close slide, focus trap + restore,
85
+ * scroll lock, and the Escape/scrim contract, while BottomSheet layers the
86
+ * grab handle and drag-to-dismiss on top.
87
+ *
88
+ * @example
89
+ * ```
90
+ * const [isOpen, setIsOpen] = useState(false);
91
+ * <BottomSheet
92
+ * isOpen={isOpen}
93
+ * onOpenChange={setIsOpen}
94
+ * label="Filters">
95
+ * <FilterControls />
96
+ * </BottomSheet>
97
+ * ```
98
+ */
99
+ export function BottomSheet({
100
+ isOpen,
101
+ onOpenChange,
102
+ label,
103
+ children,
104
+ height = 'medium',
105
+ xstyle,
106
+ ...props
107
+ }) {
108
+ const close = () => onOpenChange(false);
109
+ const {
110
+ contentProps,
111
+ handleProps
112
+ } = useSheetGestures({
113
+ isOpen,
114
+ onDismiss: close
115
+ });
116
+ return /*#__PURE__*/_jsx(Drawer, {
117
+ isOpen: isOpen,
118
+ onClose: close,
119
+ side: "bottom",
120
+ size: HEIGHT_BUDGETS[height],
121
+ label: label,
122
+ hasScrim: true,
123
+ hasCloseButton: false,
124
+ xstyle: [styles.dialogSurface, height === 'auto' && styles.autoHeight],
125
+ ...props,
126
+ children: /*#__PURE__*/_jsxs("div", {
127
+ "data-astryx-sheet": "",
128
+ ...mergeProps(themeProps('bottom-sheet'), stylex.props(styles.sheet, xstyle), contentProps.style),
129
+ children: [/*#__PURE__*/_jsx("div", {
130
+ "data-astryx-sheet-handle": "",
131
+ ...{
132
+ className: "x2lah0s x78zum5 x6s0dn4 xl56j7k xce4md1 x5ve5x3 x1jm3nie"
133
+ },
134
+ ...handleProps,
135
+ "aria-hidden": "true",
136
+ children: /*#__PURE__*/_jsx("div", {
137
+ ...{
138
+ className: "x14qfxbe xqu0tyb xjspbzw x1m4xfpy"
139
+ }
140
+ })
141
+ }), /*#__PURE__*/_jsx("div", {
142
+ ...{
143
+ className: "x1iyjqo2 x2lwn1j x1odjw0f xish69e"
144
+ },
145
+ children: children
146
+ })]
147
+ })
148
+ });
149
+ }
150
+ BottomSheet.displayName = 'BottomSheet';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @file index.ts
3
+ * @input BottomSheet.tsx
4
+ * @output Re-exports BottomSheet component and BottomSheetProps type
5
+ * @position Lab entry point for the BottomSheet directory
6
+ */
7
+ export { BottomSheet } from './BottomSheet';
8
+ export type { BottomSheetProps } from './BottomSheet';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC"}
@@ -0,0 +1,10 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file index.ts
5
+ * @input BottomSheet.tsx
6
+ * @output Re-exports BottomSheet component and BottomSheetProps type
7
+ * @position Lab entry point for the BottomSheet directory
8
+ */
9
+
10
+ export { BottomSheet } from "./BottomSheet.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file useSheetGestures.ts
3
+ * @input Uses React (useCallback, useEffect, useMemo, useRef, useState)
4
+ * @output Exports useSheetGestures hook and its option/result types
5
+ * @position Internal to BottomSheet; not exported from the lab entry point
6
+ *
7
+ * Drag-to-dismiss machinery for the bottom sheet. Tracks a pointer drag
8
+ * down the block axis, translates the sliding surface live, and on release
9
+ * either dismisses (past a distance or velocity threshold) or springs back
10
+ * to fully open. Pointer-events based so one code path covers mouse + touch.
11
+ *
12
+ * Kept private to BottomSheet: the behavior is sheet-specific (a dismiss
13
+ * edge on a bottom-anchored surface). It is not a general primitive and is
14
+ * intentionally not exported.
15
+ *
16
+ * SSR-safe: no window/document access at module scope; all measurement
17
+ * happens inside handlers. Respects prefers-reduced-motion by skipping the
18
+ * settle transition.
19
+ *
20
+ * SYNC: When modified, update these files to stay in sync:
21
+ * - /packages/lab/src/BottomSheet/useSheetGestures.doc.mjs
22
+ * - /packages/lab/src/BottomSheet/useSheetGestures.test.ts
23
+ */
24
+ import { type CSSProperties, type PointerEvent as ReactPointerEvent } from 'react';
25
+ export interface UseSheetGesturesOptions {
26
+ /** Whether the owning sheet is open. Drag state resets when it closes. */
27
+ isOpen: boolean;
28
+ /** Called when a drag crosses the dismiss threshold (distance or flick). */
29
+ onDismiss: () => void;
30
+ }
31
+ export interface SheetContentProps {
32
+ style: CSSProperties;
33
+ }
34
+ export interface SheetHandleProps {
35
+ style: CSSProperties;
36
+ onPointerDown: (event: ReactPointerEvent) => void;
37
+ onPointerMove: (event: ReactPointerEvent) => void;
38
+ onPointerUp: (event: ReactPointerEvent) => void;
39
+ onPointerCancel: (event: ReactPointerEvent) => void;
40
+ }
41
+ export interface UseSheetGesturesResult {
42
+ /** Spread on the sliding surface: live translate + touch-action guard. */
43
+ contentProps: SheetContentProps;
44
+ /** Spread on the grab-handle element: pointer drag handlers. */
45
+ handleProps: SheetHandleProps;
46
+ /** Current live drag offset in px (positive = dragged toward the edge). */
47
+ dragOffset: number;
48
+ /** Whether a drag is currently in progress. */
49
+ isDragging: boolean;
50
+ }
51
+ /**
52
+ * Drag-to-dismiss machinery for the bottom sheet. Returns props to spread on
53
+ * the grab handle and the sliding surface, plus live drag state for callers
54
+ * that want to drive their own animation.
55
+ *
56
+ * @example
57
+ * ```
58
+ * const {contentProps, handleProps} = useSheetGestures({
59
+ * isOpen,
60
+ * onDismiss: () => onOpenChange(false),
61
+ * });
62
+ * <div {...contentProps}>
63
+ * <div {...handleProps} />
64
+ * {children}
65
+ * </div>
66
+ * ```
67
+ */
68
+ export declare function useSheetGestures({ isOpen, onDismiss, }: UseSheetGesturesOptions): UseSheetGesturesResult;
69
+ //# sourceMappingURL=useSheetGestures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,YAAY,IAAI,iBAAiB,EACvC,MAAM,OAAO,CAAC;AAQf,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,YAAY,EAAE,iBAAiB,CAAC;IAChC,gEAAgE;IAChE,WAAW,EAAE,gBAAgB,CAAC;IAC9B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,SAAS,GACV,EAAE,uBAAuB,GAAG,sBAAsB,CA4HlD"}
@@ -0,0 +1,163 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useSheetGestures.ts
7
+ * @input Uses React (useCallback, useEffect, useMemo, useRef, useState)
8
+ * @output Exports useSheetGestures hook and its option/result types
9
+ * @position Internal to BottomSheet; not exported from the lab entry point
10
+ *
11
+ * Drag-to-dismiss machinery for the bottom sheet. Tracks a pointer drag
12
+ * down the block axis, translates the sliding surface live, and on release
13
+ * either dismisses (past a distance or velocity threshold) or springs back
14
+ * to fully open. Pointer-events based so one code path covers mouse + touch.
15
+ *
16
+ * Kept private to BottomSheet: the behavior is sheet-specific (a dismiss
17
+ * edge on a bottom-anchored surface). It is not a general primitive and is
18
+ * intentionally not exported.
19
+ *
20
+ * SSR-safe: no window/document access at module scope; all measurement
21
+ * happens inside handlers. Respects prefers-reduced-motion by skipping the
22
+ * settle transition.
23
+ *
24
+ * SYNC: When modified, update these files to stay in sync:
25
+ * - /packages/lab/src/BottomSheet/useSheetGestures.doc.mjs
26
+ * - /packages/lab/src/BottomSheet/useSheetGestures.test.ts
27
+ */
28
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
29
+
30
+ // Fraction of the measured sheet height a drag must cross (with low
31
+ // velocity) before release dismisses instead of springing back.
32
+ const DISMISS_DISTANCE_RATIO = 0.25;
33
+ // A quick flick past this speed (px/ms) dismisses regardless of distance.
34
+ const DISMISS_VELOCITY = 0.5;
35
+ function prefersReducedMotion() {
36
+ if (typeof window === 'undefined' || !window.matchMedia) {
37
+ return false;
38
+ }
39
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
40
+ }
41
+
42
+ /**
43
+ * Drag-to-dismiss machinery for the bottom sheet. Returns props to spread on
44
+ * the grab handle and the sliding surface, plus live drag state for callers
45
+ * that want to drive their own animation.
46
+ *
47
+ * @example
48
+ * ```
49
+ * const {contentProps, handleProps} = useSheetGestures({
50
+ * isOpen,
51
+ * onDismiss: () => onOpenChange(false),
52
+ * });
53
+ * <div {...contentProps}>
54
+ * <div {...handleProps} />
55
+ * {children}
56
+ * </div>
57
+ * ```
58
+ */
59
+ export function useSheetGestures({
60
+ isOpen,
61
+ onDismiss
62
+ }) {
63
+ const [dragOffset, setDragOffset] = useState(0);
64
+ const [isDragging, setIsDragging] = useState(false);
65
+
66
+ // Latest callback ref so pointer handlers stay stable across renders.
67
+ const onDismissRef = useRef(onDismiss);
68
+ useEffect(() => {
69
+ onDismissRef.current = onDismiss;
70
+ });
71
+
72
+ // Live drag bookkeeping (refs so pointermove doesn't churn renders).
73
+ const dragStateRef = useRef(null);
74
+
75
+ // Reset drag state each time the sheet re-opens.
76
+ useEffect(() => {
77
+ if (isOpen) {
78
+ setDragOffset(0);
79
+ setIsDragging(false);
80
+ }
81
+ }, [isOpen]);
82
+ const measureHeight = useCallback(el => {
83
+ const sheet = el?.closest('[data-astryx-sheet]') ?? el;
84
+ return sheet?.getBoundingClientRect().height ?? 0;
85
+ }, []);
86
+ const settleFromDrag = useCallback((offset, velocity, height) => {
87
+ const dismiss = offset > height * DISMISS_DISTANCE_RATIO || velocity > DISMISS_VELOCITY;
88
+ if (dismiss) {
89
+ onDismissRef.current();
90
+ return;
91
+ }
92
+ // Below threshold: spring back to fully open.
93
+ setDragOffset(0);
94
+ }, []);
95
+ const handlePointerDown = useCallback(event => {
96
+ const target = event.currentTarget;
97
+ target.setPointerCapture?.(event.pointerId);
98
+ dragStateRef.current = {
99
+ pointerId: event.pointerId,
100
+ startCoord: event.clientY,
101
+ lastCoord: event.clientY,
102
+ lastTime: event.timeStamp,
103
+ velocity: 0,
104
+ height: measureHeight(target)
105
+ };
106
+ setIsDragging(true);
107
+ }, [measureHeight]);
108
+ const handlePointerMove = useCallback(event => {
109
+ const state = dragStateRef.current;
110
+ if (!state || state.pointerId !== event.pointerId) {
111
+ return;
112
+ }
113
+ // Dragging DOWN (increasing Y) collapses/dismisses. Only allow drag in
114
+ // the closing direction past the fully-open position (no overscroll).
115
+ const delta = event.clientY - state.startCoord;
116
+ const dt = event.timeStamp - state.lastTime;
117
+ if (dt > 0) {
118
+ state.velocity = (event.clientY - state.lastCoord) / dt;
119
+ state.lastCoord = event.clientY;
120
+ state.lastTime = event.timeStamp;
121
+ }
122
+ setDragOffset(Math.max(0, delta));
123
+ }, []);
124
+ const endDrag = useCallback(event => {
125
+ const state = dragStateRef.current;
126
+ if (!state || state.pointerId !== event.pointerId) {
127
+ return;
128
+ }
129
+ const target = event.currentTarget;
130
+ target.releasePointerCapture?.(event.pointerId);
131
+ const offset = Math.max(0, event.clientY - state.startCoord);
132
+ dragStateRef.current = null;
133
+ setIsDragging(false);
134
+ settleFromDrag(offset, state.velocity, state.height || 1);
135
+ }, [settleFromDrag]);
136
+
137
+ // Reduced motion skips the settle transition (snaps instantly).
138
+ const reducedMotion = useMemo(prefersReducedMotion, [isOpen]);
139
+ const contentProps = useMemo(() => ({
140
+ style: {
141
+ transform: dragOffset !== 0 ? `translateY(${dragOffset}px)` : undefined,
142
+ transition: isDragging || reducedMotion ? 'none' : undefined,
143
+ touchAction: 'none',
144
+ overscrollBehavior: 'contain'
145
+ }
146
+ }), [dragOffset, isDragging, reducedMotion]);
147
+ const handleProps = useMemo(() => ({
148
+ style: {
149
+ touchAction: 'none',
150
+ cursor: 'grab'
151
+ },
152
+ onPointerDown: handlePointerDown,
153
+ onPointerMove: handlePointerMove,
154
+ onPointerUp: endDrag,
155
+ onPointerCancel: endDrag
156
+ }), [handlePointerDown, handlePointerMove, endDrag]);
157
+ return {
158
+ contentProps,
159
+ handleProps,
160
+ dragOffset,
161
+ isDragging
162
+ };
163
+ }
@@ -22,6 +22,8 @@
22
22
  import { type ReactNode } from 'react';
23
23
  import type { BaseProps } from '@astryxdesign/core';
24
24
  import type { SizeValue } from '@astryxdesign/core/utils';
25
+ import { type Transformer } from '@lexical/markdown';
26
+ export type { Transformer } from '@lexical/markdown';
25
27
  import type { EditorState, Klass, LexicalEditor, LexicalNode } from 'lexical';
26
28
  export type RichTextEditorStatusType = 'warning' | 'error' | 'success';
27
29
  export type RichTextEditorSize = 'sm' | 'md' | 'lg';
@@ -31,6 +33,36 @@ export interface RichTextEditorStatus {
31
33
  /** Optional message to display below the editor. */
32
34
  message?: string;
33
35
  }
36
+ /**
37
+ * Imperative handle exposed via `ref`. Lets callers focus, clear, and read the
38
+ * editor without wiring a custom plugin. Available after mount.
39
+ */
40
+ export interface RichTextEditorRef {
41
+ /**
42
+ * Move focus into the editor's editable surface. No-op when the editor is
43
+ * read-only or disabled.
44
+ */
45
+ focus: () => void;
46
+ /**
47
+ * Remove all content, resetting the editor to a single empty paragraph.
48
+ * No-op when the editor is read-only or disabled.
49
+ */
50
+ clear: () => void;
51
+ /** Read the current `EditorState`. Serialize with `.toJSON()` to persist. */
52
+ getEditorState: () => EditorState;
53
+ /**
54
+ * Serialize the current content to a Markdown string, using the same
55
+ * `transformers` the editor is configured with (so custom transformers
56
+ * layered in via the `transformers` prop are honored). Equivalent to
57
+ * `$convertToMarkdownString` run in a read context.
58
+ */
59
+ getMarkdown: () => string;
60
+ /**
61
+ * Access the underlying `LexicalEditor` instance for advanced use cases
62
+ * (custom commands, listeners, node transforms).
63
+ */
64
+ getEditor: () => LexicalEditor;
65
+ }
34
66
  export interface RichTextEditorProps extends Omit<BaseProps, 'onChange' | 'defaultValue'> {
35
67
  /** Label text for the editor (always rendered for accessibility). */
36
68
  label: string;
@@ -106,12 +138,39 @@ export interface RichTextEditorProps extends Omit<BaseProps, 'onChange' | 'defau
106
138
  plugins?: ReactNode;
107
139
  /**
108
140
  * Whether to enable Markdown shortcut typing (e.g. `# ` for a heading,
109
- * `- ` for a list). Uses the default `@lexical/markdown` transformers.
141
+ * `- ` for a list). Uses the `transformers` prop (defaults to the standard
142
+ * `@lexical/markdown` transformers).
110
143
  * @default true
111
144
  */
112
145
  hasMarkdownShortcuts?: boolean;
146
+ /**
147
+ * Markdown transformers — the single source of truth for markdown behaviour.
148
+ * Defaults to the standard `@lexical/markdown` `TRANSFORMERS`.
149
+ *
150
+ * The same array drives all three markdown operations in Lexical (see the
151
+ * lexical-playground reference, where one `PLAYGROUND_TRANSFORMERS` array
152
+ * feeds each):
153
+ * - shortcut typing — `registerMarkdownShortcuts` (wired here today)
154
+ * - markdown -> state — `$convertFromMarkdownString` (future import API)
155
+ * - state -> markdown — `$convertToMarkdownString` (future `getMarkdown`)
156
+ *
157
+ * Pass a custom array to support additional node types (e.g. custom
158
+ * transformers layered in via the `nodes` extension point) consistently
159
+ * across all three. Shortcut typing is only applied when
160
+ * `hasMarkdownShortcuts` is true; the array is still the intended input for
161
+ * the serialization APIs added in later phases.
162
+ */
163
+ transformers?: ReadonlyArray<Transformer>;
113
164
  /** Whether to automatically focus the editor on mount. @default false */
114
165
  hasAutoFocus?: boolean;
166
+ /**
167
+ * Maximum number of characters. When set, a character counter
168
+ * (current/max) is displayed below the editor. Like TextArea, this does
169
+ * NOT enforce the limit natively — the counter shows error styling when the
170
+ * plain-text length exceeds the limit. Count is the editor's plain-text
171
+ * content length.
172
+ */
173
+ maxLength?: number;
115
174
  /**
116
175
  * The Lexical composer namespace, used for editor identity.
117
176
  * @default 'astryx-editor'
@@ -128,18 +187,20 @@ export interface RichTextEditorProps extends Omit<BaseProps, 'onChange' | 'defau
128
187
  * `plugins` to layer richer behaviour (toolbars, mentions, hover cards) on top
129
188
  * without forking.
130
189
  *
190
+ * The forwarded `RichTextEditorRef` exposes imperative `focus()` and `clear()`
191
+ * methods for callers that manage the editor from outside.
192
+ *
131
193
  * @example
132
194
  * ```
133
- * import {RichTextEditor} from '@astryxdesign/lab';
195
+ * import {RichTextEditor, type RichTextEditorRef} from '@astryxdesign/lab';
196
+ * const ref = useRef<RichTextEditorRef>(null);
134
197
  * <RichTextEditor
198
+ * ref={ref}
135
199
  * label="Notes"
136
200
  * placeholder="Write something..."
137
201
  * onChange={state => save(JSON.stringify(state.toJSON()))}
138
202
  * />
139
203
  * ```
140
204
  */
141
- export declare function RichTextEditor({ label, isLabelHidden, description, isOptional, isRequired, defaultValue, onChange, placeholder, isReadOnly, isDisabled, status, width, labelTooltip, size: sizeProp, nodes, plugins, hasMarkdownShortcuts, hasAutoFocus, namespace, xstyle, className, style, ...rest }: RichTextEditorProps): import("react").JSX.Element;
142
- export declare namespace RichTextEditor {
143
- var displayName: string;
144
- }
205
+ export declare const RichTextEditor: import("react").ForwardRefExoticComponent<RichTextEditorProps & import("react").RefAttributes<RichTextEditorRef>>;
145
206
  //# sourceMappingURL=RichTextEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../src/RichTextEditor/RichTextEditor.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAgB/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAuBxD,OAAO,KAAK,EACV,WAAW,EACX,KAAK,EACL,aAAa,EACb,WAAW,EAEZ,MAAM,SAAS,CAAC;AAiEjB,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,SAAS,EACT,UAAU,GAAG,cAAc,CAC5B;IACC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACrE,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,MAAM,EACN,KAAK,EACL,YAAY,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EACL,OAAO,EACP,oBAA2B,EAC3B,YAAoB,EACpB,SAA2B,EAC3B,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAkHrB;yBA1Ie,cAAc"}
1
+ {"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../src/RichTextEditor/RichTextEditor.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAQL,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAgBf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAkBxD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAKnD,OAAO,KAAK,EACV,WAAW,EACX,KAAK,EACL,aAAa,EACb,WAAW,EAEZ,MAAM,SAAS,CAAC;AAoHjB,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,WAAW,CAAC;IAClC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B;;;OAGG;IACH,SAAS,EAAE,MAAM,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,SAAS,EACT,UAAU,GAAG,cAAc,CAC5B;IACC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACrE,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC1C,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,cAAc,mHAuLzB,CAAC"}