@inkra/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +86 -4
  2. package/dist/alert-dialog/index.d.ts +1 -1
  3. package/dist/alert-dialog/index.js +4 -2
  4. package/dist/command/index.d.ts +24 -2
  5. package/dist/command/index.js +2 -2
  6. package/dist/context-menu/index.d.ts +2 -2
  7. package/dist/context-menu/index.js +3 -2
  8. package/dist/drawer/index.d.ts +1 -1
  9. package/dist/drawer/index.js +6 -4
  10. package/dist/dropdown-menu/index.d.ts +1 -1
  11. package/dist/dropdown-menu/index.js +3 -2
  12. package/dist/form/EditablePencilText.js +34 -0
  13. package/dist/form/Input.js +1 -1
  14. package/dist/input-number/index.d.ts +6 -0
  15. package/dist/input-number/index.js +126 -15
  16. package/dist/input-number/model.d.ts +13 -0
  17. package/dist/input-number/model.js +50 -0
  18. package/dist/number-ticker/index.d.ts +11 -9
  19. package/dist/number-ticker/index.js +14 -11
  20. package/dist/number-ticker/model.d.ts +38 -5
  21. package/dist/number-ticker/model.js +139 -10
  22. package/dist/number-ticker/precision.d.ts +7 -0
  23. package/dist/number-ticker/precision.js +134 -0
  24. package/dist/overlay/Overlay.d.ts +1 -1
  25. package/dist/overlay/Overlay.js +4 -2
  26. package/dist/overlay/Popover.d.ts +1 -1
  27. package/dist/overlay/Popover.js +3 -2
  28. package/dist/overlay/themeBridge.d.ts +12 -1
  29. package/dist/overlay/themeBridge.js +27 -1
  30. package/dist/radar/index.d.ts +2 -0
  31. package/dist/radar/index.js +2 -2
  32. package/dist/sortable/index.js +9 -4
  33. package/dist/styles.css +147 -22
  34. package/dist/tooltip/Tooltip.d.ts +1 -1
  35. package/dist/tooltip/Tooltip.js +3 -2
  36. package/dist/virtual-list/index.js +3 -2
  37. package/package.json +4 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Inkra
2
2
 
3
- Inkra is a pencil-styled React component library. It includes action, form, overlay, navigation, content and feedback components, plus Calendar, DayAgenda, JourneyMap and deterministic drawing primitives. Version `0.1.0` is published; the component expansion described below is available only in the current, unpublished source candidate.
3
+ Inkra is a pencil-styled React component library. It includes action, form, overlay, navigation, content and feedback components, plus Calendar, DayAgenda, JourneyMap and deterministic drawing primitives. This document describes the APIs shipped with this package; see the repository changelog and npm registry for release status.
4
4
 
5
5
  Source and maintenance: [inkrajs/inkra](https://github.com/inkrajs/inkra).
6
6
 
@@ -80,6 +80,18 @@ All standard `button` props, including `disabled`, `name`, `value`, `form`, `ari
80
80
 
81
81
  `InputNumber` places decrease and increase buttons on either side of a centered numeric input, with no clear button. Keyboard deletion produces `null`; decimal stepping and out-of-range validation remain native. `Slider` supports single/range values, horizontal/vertical layout and `standard` or `inset` appearance.
82
82
 
83
+ `InputNumber` supports Shift-click and Shift-arrow increments of 10 by default.
84
+ Use `stepModifiers={[{ shiftKey: true, step: 10 }, { altKey: true, step: 0.1 }]}`
85
+ to configure exact modifier chords with absolute increments, or `[]` to disable
86
+ overrides. The first valid matching rule wins; unspecified modifiers must be
87
+ released. Values remain bounded by `min`/`max`; `step="any"` disables stepping.
88
+ `pressAndHold` defaults to true, with `repeatDelay={400}` and
89
+ `repeatInterval={80}` in milliseconds. Release, cancellation, blur, backgrounding,
90
+ disabling and value boundaries stop repetition. Delay/interval are bounded to
91
+ 150-60000/32-60000 ms. Numeric values use pencil lettering at rest and during
92
+ button stepping. Direct text entry deliberately stays native until blur, keeping
93
+ browser-owned incomplete drafts such as `-`, `1.` and `1e` readable.
94
+
83
95
  `Select.Root` uses string values for single selection and string arrays with `multiple`. Multi-select remains open while toggling items and submits repeated values under the same field name. `Select.Clear` is a separate button; never nest it inside the trigger.
84
96
 
85
97
  `Select.Trigger` supports `startIcon`, `endIcon`, `hideValue`, `hideIndicator`, `iconOnly`, `fullWidth` and `textAlign`. The existing `icon` prop replaces the dropdown indicator. Icon-only mode keeps a square hit target of at least 44px and retains selected text as an accessible description. Provide a Field/ARIA name and Tooltip for icon-only triggers. Decorative slots must not contain interactive controls.
@@ -195,10 +207,9 @@ Content layout options preserve existing defaults:
195
207
  - `Avatar.Root.radius` clamps to 0-8px, including pencil and image corners; `bordered={false}` removes the outline. `Avatar.Image.fit` accepts `cover | contain` (default cover).
196
208
  - `Table.Root.striped`, `hoverable` and `stickyHeader` default to false. Constrain `Table.Container` with `style={{ maxHeight: 240 }}` for sticky scrolling. `captionSide` accepts `top | bottom`. `Head.align` and `Cell.align` accept logical `start | center | end`, retaining native alignment values for compatibility. Hover feedback does not imply row actions.
197
209
 
198
- ## Component Expansion (Unreleased)
210
+ ## Additional Components
199
211
 
200
- These exports are not present in the published 0.1.0 archive. Build, pack and
201
- install the local candidate to use them. The documentation playground includes
212
+ These exports extend the original core component set. The documentation playground includes
202
213
  working examples, live property tabs and per-component API/accessibility notes.
203
214
 
204
215
  | Component | Contract |
@@ -239,6 +250,77 @@ motion and SSR show deterministic final values. Intermediate frames are visual;
239
250
  the polite announcement updates only at the final value. Use `getValueLabel`
240
251
  when units or custom rendering need a complete accessible phrase.
241
252
 
253
+ Set `compact` to follow `locale` (Chinese units for `zh`, English units otherwise),
254
+ or use `compact="en"` / `compact="zh"` to select a unit table explicitly.
255
+ English uses this library's short-scale suffix convention: K, M, B, T, Qa, Qi,
256
+ Sx, Sp, Oc, No, Dc (10^3 through 10^33, increasing by 10^3).
257
+ Chinese uses 千 (10^3), then 万, 亿, 兆, 京, 垓, 秭, 穰, 沟, 涧, 正, 载, 极,
258
+ 恒河沙, 阿僧祇, 那由他, 不可思议, 无量大数 (10^4 through 10^68, increasing by 10^4).
259
+ `numberTickerUnits` exports both tables. A space separates numbers and units by default.
260
+ The default is `false`, preserving existing `formatOptions` behavior. Compact mode
261
+ uses at most one fractional digit by default; customize precision with
262
+ `formatOptions`. Explicit scientific/engineering notation takes priority over compact.
263
+ Currency/percent affixes are preserved. By default rounding can promote the unit
264
+ (999,950 becomes 1 M). Deltas use the same unit
265
+ system with their own precision; announcements and `getValueLabel` receive the
266
+ unabbreviated formatted value. `renderValue` receives the abbreviated value in
267
+ `details.formattedValue`. NumberTicker never changes the supplied numeric value.
268
+
269
+ `compactOptions` supports:
270
+
271
+ - `minUnit` / `maxUnit`: allowed unit labels; equal bounds fix the unit. An empty
272
+ label denotes the unitless base. For example `maxUnit: '万'` keeps 10^8 as `10,000 万`.
273
+ - `separator`: defaults to one space; `''` removes it.
274
+ - `promotionThreshold`: positive multiplier on each unit's factor (default 1).
275
+ Set 10 to wait until 10,000 before entering K. `promoteAfterRounding: false`
276
+ compares raw magnitudes instead of rounded current-unit values.
277
+ - `units`: replace the table with `{ factor, label, threshold? }` entries.
278
+ `threshold` overrides the multiplier with an absolute display-magnitude trigger;
279
+ e.g. `{ factor: 10000, label: '万', threshold: 100000 }`. Binary factors work too.
280
+ Entries are sorted without mutation; labels/factors must be unique, factors > 1,
281
+ and thresholds positive, finite and strictly increasing. Invalid configurations
282
+ and unknown/reversed bounds throw RangeError. The unitless base is implicit.
283
+ - `overflow`: `unit` keeps the largest permitted unit; `scientific` switches at
284
+ the next unit's threshold (or extrapolates the last step at the table end).
285
+ Explicit `maxUnit` defaults to `unit`; otherwise the default is `scientific`.
286
+
287
+ Use `formatOptions.notation: 'scientific' | 'engineering'` for exponent notation.
288
+ `maximumFractionDigits` controls decimals, `minimumFractionDigits` pads zeros,
289
+ and `roundingMode: 'trunc'` truncates instead of rounding (`halfExpand` is default).
290
+ Other Intl rounding modes are supported when provided by the runtime; unsupported
291
+ nondefault rounding modes throw rather than silently round differently.
292
+ `value`, unit `factor` / `threshold`, and `promotionThreshold` accept
293
+ `number | bigint | string`. Use bigint for exact integers and decimal strings for
294
+ exact fractions or exponents beyond JavaScript's numeric range. Already-rounded
295
+ `number` inputs cannot regain lost digits. Decimal arithmetic uses an isolated
296
+ `decimal.js` context without modifying the application's Decimal settings.
297
+
298
+ ```tsx
299
+ <NumberTicker value={9007199254740993n} showDelta />
300
+ <NumberTicker value="12345678901234567890.123456" formatOptions={{ maximumFractionDigits: 6 }} />
301
+ <NumberTicker value="1.23456789e1000" formatOptions={{ notation: 'scientific' }} />
302
+ ```
303
+
304
+ Strings accept signs, decimal points, e/E exponents and outer whitespace, but
305
+ not grouping separators, hex, empty strings or non-finite spellings. Inputs are
306
+ bounded to 10,000 coefficient digits and written/normalized exponents within
307
+ -10,000..10,000; invalid or oversized exact inputs throw `RangeError`. Decimal
308
+ fractions require exact Intl decimal-string support in the runtime; unsupported
309
+ engines throw rather than silently lose precision. Native number NaN/Infinity
310
+ formatting is unchanged.
311
+
312
+ `renderValue` and `getValueLabel` retain the input type; prefer the supplied
313
+ formatted text when custom rendering. Deltas between bigints are bigint, between
314
+ numbers are number, and mixed/string deltas are exact decimal strings. Bigint
315
+ animation frames stay integral. Input-type changes or exact arithmetic spanning
316
+ more than 512 digits settle immediately, as do reduced-motion updates.
317
+ `createNumberFormatter` remains a native Intl formatter; use
318
+ `formatTickerValue(formatter, value)` for precise numeric-string formatting.
319
+
320
+ `prefix` and `suffix` accept icons, text or fragments. Pass `PencilIcon` to keep
321
+ the library material, and `getValueLabel` to describe icon meanings to assistive
322
+ technology; decorative affixes are not announced twice.
323
+
242
324
  `Log` renders caller-ordered records with stable ids, optional level filters and
243
325
  pause/follow controls. It follows only append updates while the viewport remains
244
326
  near the bottom. The named scroll region supports Home/End and intentionally is
@@ -11,7 +11,7 @@ export declare function AlertDialogRoot({ defaultOpen, onOpenChange, open, pendi
11
11
  export type AlertDialogTriggerProps = DialogPrimitive.DialogTriggerProps;
12
12
  export declare const AlertDialogTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
13
13
  export type AlertDialogPortalProps = DialogPrimitive.DialogPortalProps;
14
- export declare function AlertDialogPortal({ children, ...props }: AlertDialogPortalProps): import("react").JSX.Element;
14
+ export declare function AlertDialogPortal({ children, ...props }: AlertDialogPortalProps): ReactNode;
15
15
  export type AlertDialogBackdropProps = DialogPrimitive.DialogOverlayProps;
16
16
  export declare const AlertDialogBackdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
17
17
  export interface AlertDialogContentProps extends DialogPrimitive.DialogContentProps {
@@ -5,7 +5,7 @@ import { Button } from '../Button.js';
5
5
  import { PencilLine } from '../PencilLine.js';
6
6
  import { PencilSurface } from '../PencilSurface.js';
7
7
  import { PencilText } from '../PencilText.js';
8
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
8
+ import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef, } from '../overlay/themeBridge.js';
9
9
  const classes = (...values) => values.filter(Boolean).join(' ');
10
10
  function assignRef(ref, value) {
11
11
  if (typeof ref === 'function')
@@ -32,15 +32,17 @@ export const AlertDialogTrigger = forwardRef(function AlertDialogTrigger(props,
32
32
  return _jsx(DialogPrimitive.Trigger, { ...props, ref: usePortalTriggerRef(forwardedRef) });
33
33
  });
34
34
  export function AlertDialogPortal({ children, ...props }) {
35
- return (_jsx(DialogPrimitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) }));
35
+ return mapPortalChildren(children, child => _jsx(DialogPrimitive.Portal, { ...props, children: child }));
36
36
  }
37
37
  export const AlertDialogBackdrop = forwardRef(function AlertDialogBackdrop({ className, ...props }, ref) {
38
+ props = usePortalThemeProps(props);
38
39
  return (_jsx(DialogPrimitive.Overlay, { ...props, className: classes('inkra-alert-dialog__backdrop', className), ref: ref }));
39
40
  });
40
41
  export const AlertDialogContent = forwardRef(function AlertDialogContent({ children, className, onEscapeKeyDown, onInteractOutside, onOpenAutoFocus, onPointerDownOutside, seed, ...props }, forwardedRef) {
41
42
  const context = useContext(AlertDialogContext);
42
43
  const contentRef = useRef(null);
43
44
  const id = useId();
45
+ props = usePortalThemeProps(props);
44
46
  return (_jsx(DialogPrimitive.Content, { ...props, role: "alertdialog", "aria-busy": context?.pending || undefined, className: classes('inkra-alert-dialog__content', className), "data-pending": context?.pending || undefined, ref: node => {
45
47
  contentRef.current = node;
46
48
  assignRef(forwardedRef, node);
@@ -1,6 +1,9 @@
1
1
  import { Command as Primitive } from 'cmdk';
2
2
  import type { ComponentPropsWithoutRef, HTMLAttributes, ReactNode } from 'react';
3
- export type CommandRootProps = Omit<ComponentPropsWithoutRef<typeof Primitive>, 'asChild'>;
3
+ export type CommandRootProps = Omit<ComponentPropsWithoutRef<typeof Primitive>, 'asChild'> & {
4
+ /** Disable when a parent dialog already supplies the paper and outline. */
5
+ surface?: boolean;
6
+ };
4
7
  export type CommandInputProps = Omit<ComponentPropsWithoutRef<typeof Primitive.Input>, 'asChild'>;
5
8
  export type CommandListProps = Omit<ComponentPropsWithoutRef<typeof Primitive.List>, 'asChild'>;
6
9
  export interface CommandItemProps extends Omit<ComponentPropsWithoutRef<typeof Primitive.Item>, 'asChild'> {
@@ -12,7 +15,26 @@ export type CommandEmptyProps = Omit<ComponentPropsWithoutRef<typeof Primitive.E
12
15
  export type CommandSeparatorProps = Omit<ComponentPropsWithoutRef<typeof Primitive.Separator>, 'asChild' | 'children'>;
13
16
  export type CommandShortcutProps = HTMLAttributes<HTMLElement>;
14
17
  export declare const Command: {
15
- Root: import("react").ForwardRefExoticComponent<CommandRootProps & import("react").RefAttributes<HTMLDivElement>>;
18
+ Root: import("react").ForwardRefExoticComponent<Omit<Omit<{
19
+ children?: React.ReactNode;
20
+ } & Pick<Pick<import("react").DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<HTMLDivElement>> & {
21
+ ref?: React.Ref<HTMLDivElement>;
22
+ } & {
23
+ asChild?: boolean;
24
+ }, "key" | keyof HTMLAttributes<HTMLDivElement> | "asChild"> & {
25
+ label?: string;
26
+ shouldFilter?: boolean;
27
+ filter?: (value: string, search: string, keywords?: string[]) => number;
28
+ defaultValue?: string;
29
+ value?: string;
30
+ onValueChange?: (value: string) => void;
31
+ loop?: boolean;
32
+ disablePointerSelection?: boolean;
33
+ vimBindings?: boolean;
34
+ } & import("react").RefAttributes<HTMLDivElement>, "ref">, "asChild"> & {
35
+ /** Disable when a parent dialog already supplies the paper and outline. */
36
+ surface?: boolean;
37
+ } & import("react").RefAttributes<HTMLDivElement>>;
16
38
  Input: import("react").ForwardRefExoticComponent<CommandInputProps & import("react").RefAttributes<HTMLInputElement>>;
17
39
  List: import("react").ForwardRefExoticComponent<CommandListProps & import("react").RefAttributes<HTMLDivElement>>;
18
40
  Group: import("react").ForwardRefExoticComponent<CommandGroupProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -6,14 +6,14 @@ import { PencilText } from '../PencilText.js';
6
6
  import { PencilLine } from '../PencilLine.js';
7
7
  function classes(base, extra) { return [base, extra].filter(Boolean).join(' '); }
8
8
  function copy(value) { return typeof value === 'string' ? _jsx(PencilText, { children: value }) : value; }
9
- const Root = forwardRef(function Root({ className, children, loop = true, onKeyDown, onCompositionStartCapture, onCompositionEndCapture, ...props }, ref) {
9
+ const Root = forwardRef(function Root({ className, children, loop = true, surface = true, onKeyDown, onCompositionStartCapture, onCompositionEndCapture, ...props }, ref) {
10
10
  const composing = useRef(false);
11
11
  return _jsx(Primitive, { ...props, ref: ref, loop: loop, className: classes('inkra-command', className), onCompositionStartCapture: event => { composing.current = true; onCompositionStartCapture?.(event); }, onCompositionEndCapture: event => { composing.current = false; onCompositionEndCapture?.(event); }, onKeyDown: event => {
12
12
  // Capture composition state as well as native flags before cmdk handles Enter.
13
13
  if (event.key === 'Enter' && (composing.current || event.nativeEvent.isComposing || event.keyCode === 229))
14
14
  event.preventDefault();
15
15
  onKeyDown?.(event);
16
- }, children: _jsx(PencilSurface, { variant: "paper", radius: 8, className: "inkra-command__paper", children: children }) });
16
+ }, children: surface ? _jsx(PencilSurface, { variant: "paper", radius: 8, className: "inkra-command__paper", children: children }) : children });
17
17
  });
18
18
  const Input = forwardRef(function Input({ className, ...props }, ref) {
19
19
  return _jsxs("div", { className: "inkra-command__input-wrap", children: [_jsx(Primitive.Input, { ...props, ref: ref, className: classes('inkra-command__input', className) }), _jsx(PencilLine, { className: "inkra-command__input-line" })] });
@@ -31,7 +31,7 @@ export interface ContextMenuTriggerProps extends Omit<HTMLAttributes<HTMLElement
31
31
  }
32
32
  export declare const ContextMenuTrigger: import("react").ForwardRefExoticComponent<ContextMenuTriggerProps & import("react").RefAttributes<HTMLElement>>;
33
33
  export type ContextMenuPortalProps = ComponentPropsWithoutRef<typeof DropdownMenu.Portal>;
34
- export declare const ContextMenuPortal: ({ children, ...props }: import("../dropdown-menu/index.js").DropdownMenuPortalProps) => import("react").JSX.Element;
34
+ export declare const ContextMenuPortal: ({ children, ...props }: import("../dropdown-menu/index.js").DropdownMenuPortalProps) => ReactNode;
35
35
  export type ContextMenuContentProps = DropdownMenuContentProps;
36
36
  export declare const ContextMenuContent: import("react").ForwardRefExoticComponent<DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>>;
37
37
  export type ContextMenuItemProps = ComponentPropsWithoutRef<typeof DropdownMenu.Item>;
@@ -61,7 +61,7 @@ export declare const ContextMenuSubContent: import("react").ForwardRefExoticComp
61
61
  export declare const ContextMenu: Readonly<{
62
62
  Root: typeof ContextMenuRoot;
63
63
  Trigger: import("react").ForwardRefExoticComponent<ContextMenuTriggerProps & import("react").RefAttributes<HTMLElement>>;
64
- Portal: ({ children, ...props }: import("../dropdown-menu/index.js").DropdownMenuPortalProps) => import("react").JSX.Element;
64
+ Portal: ({ children, ...props }: import("../dropdown-menu/index.js").DropdownMenuPortalProps) => ReactNode;
65
65
  Content: import("react").ForwardRefExoticComponent<DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>>;
66
66
  Item: import("react").ForwardRefExoticComponent<import("../dropdown-menu/index.js").DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>>;
67
67
  CheckboxItem: import("react").ForwardRefExoticComponent<import("../dropdown-menu/index.js").DropdownMenuCheckboxItemProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -5,7 +5,7 @@ import { DropdownMenu } from '../dropdown-menu/index.js';
5
5
  import { PencilIcon } from '../PencilIcon.js';
6
6
  import { PencilSurface } from '../PencilSurface.js';
7
7
  import { PencilText } from '../PencilText.js';
8
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
8
+ import { usePortalThemeProps, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
9
9
  const DEFAULT_EXCLUDE = '[data-context-menu-native]';
10
10
  const DEFAULT_LONG_PRESS_DELAY = 600;
11
11
  const DEFAULT_LONG_PRESS_TOLERANCE = 10;
@@ -220,7 +220,8 @@ export const ContextMenuSubTrigger = forwardRef(function ContextMenuSubTrigger({
220
220
  });
221
221
  export const ContextMenuSubContent = forwardRef(function ContextMenuSubContent({ children, className, collisionPadding = 8, seed, sideOffset = 4, ...props }, ref) {
222
222
  const id = useId();
223
- return (_jsx(Primitive.Portal, { children: _jsx(PortalThemeBridge, { children: _jsx(Primitive.SubContent, { ...props, className: classes('inkra-menu', 'inkra-context-menu__sub-content', className), collisionPadding: collisionPadding, ref: ref, sideOffset: sideOffset, children: _jsx(PencilSurface, { className: "inkra-menu__paper", radius: 8, seed: seed ?? `context-submenu-${id}`, variant: "paper", children: children }) }) }) }));
223
+ props = usePortalThemeProps(props);
224
+ return (_jsx(Primitive.Portal, { children: _jsx(Primitive.SubContent, { ...props, className: classes('inkra-menu', 'inkra-context-menu__sub-content', className), collisionPadding: collisionPadding, ref: ref, sideOffset: sideOffset, children: _jsx(PencilSurface, { className: "inkra-menu__paper", radius: 8, seed: seed ?? `context-submenu-${id}`, variant: "paper", children: children }) }) }));
224
225
  });
225
226
  export const ContextMenu = Object.freeze({
226
227
  Root: ContextMenuRoot,
@@ -8,7 +8,7 @@ export declare function DrawerRoot(props: DrawerRootProps): import("react").JSX.
8
8
  export type DrawerTriggerProps = DialogPrimitive.DialogTriggerProps;
9
9
  export declare const DrawerTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
10
10
  export type DrawerPortalProps = DialogPrimitive.DialogPortalProps;
11
- export declare function DrawerPortal({ children, ...props }: DrawerPortalProps): import("react").JSX.Element;
11
+ export declare function DrawerPortal({ children, ...props }: DrawerPortalProps): ReactNode;
12
12
  export type DrawerBackdropProps = DialogPrimitive.DialogOverlayProps;
13
13
  export declare const DrawerBackdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
14
14
  export interface DrawerContentProps extends DialogPrimitive.DialogContentProps {
@@ -8,7 +8,7 @@ import { PencilLine } from '../PencilLine.js';
8
8
  import { PencilSurface } from '../PencilSurface.js';
9
9
  import { PencilText } from '../PencilText.js';
10
10
  import { Tooltip } from '../tooltip/Tooltip.js';
11
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
11
+ import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef, } from '../overlay/themeBridge.js';
12
12
  const classes = (...values) => values.filter(Boolean).join(' ');
13
13
  const closePaths = closeIcon.node.map(([, attributes]) => ({ d: attributes.d }));
14
14
  function drawerSize(size) {
@@ -29,16 +29,18 @@ export const DrawerTrigger = forwardRef(function DrawerTrigger(props, forwardedR
29
29
  return _jsx(DialogPrimitive.Trigger, { ...props, ref: usePortalTriggerRef(forwardedRef) });
30
30
  });
31
31
  export function DrawerPortal({ children, ...props }) {
32
- return _jsx(DialogPrimitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) });
32
+ return mapPortalChildren(children, child => _jsx(DialogPrimitive.Portal, { ...props, children: child }));
33
33
  }
34
34
  export const DrawerBackdrop = forwardRef(function DrawerBackdrop({ className, ...props }, ref) {
35
+ props = usePortalThemeProps(props);
35
36
  return _jsx(DialogPrimitive.Overlay, { ...props, className: classes('inkra-drawer__backdrop', className), ref: ref });
36
37
  });
37
38
  export const DrawerContent = forwardRef(function DrawerContent({ children, className, direction = 'right', seed, size = 'md', style, ...props }, ref) {
38
39
  const id = useId();
39
- const mergedStyle = { ...style, '--inkra-drawer-size': drawerSize(size) };
40
+ const themedProps = usePortalThemeProps({ ...props, style });
41
+ const mergedStyle = { ...themedProps.style, '--inkra-drawer-size': drawerSize(size) };
40
42
  const edgeOrientation = direction === 'left' || direction === 'right' ? 'vertical' : 'horizontal';
41
- return (_jsx(DialogPrimitive.Content, { ...props, className: classes('inkra-drawer__content', className), "data-direction": direction, ref: ref, style: mergedStyle, children: _jsxs(PencilSurface, { className: "inkra-drawer__surface", radius: 0, seed: seed ?? `inkra-drawer-${id}`, variant: "full", children: [_jsx(PencilLine, { className: "inkra-drawer__edge", orientation: edgeOrientation, seed: `drawer-edge-${id}` }), _jsx("div", { className: "inkra-drawer__layout", children: children })] }) }));
43
+ return (_jsx(DialogPrimitive.Content, { ...themedProps, className: classes('inkra-drawer__content', className), "data-direction": direction, ref: ref, style: mergedStyle, children: _jsxs(PencilSurface, { className: "inkra-drawer__surface", radius: 0, seed: seed ?? `inkra-drawer-${id}`, variant: "full", children: [_jsx(PencilLine, { className: "inkra-drawer__edge", orientation: edgeOrientation, seed: `drawer-edge-${id}` }), _jsx("div", { className: "inkra-drawer__layout", children: children })] }) }));
42
44
  });
43
45
  export const DrawerTitle = forwardRef(function DrawerTitle({ children, className, seed, ...props }, ref) {
44
46
  const id = useId();
@@ -4,7 +4,7 @@ export type DropdownMenuProps = Primitive.DropdownMenuProps;
4
4
  declare function Root(props: DropdownMenuProps): import("react").JSX.Element;
5
5
  export type DropdownMenuTriggerProps = Primitive.DropdownMenuTriggerProps;
6
6
  export type DropdownMenuPortalProps = Primitive.DropdownMenuPortalProps;
7
- declare function Portal({ children, ...props }: DropdownMenuPortalProps): import("react").JSX.Element;
7
+ declare function Portal({ children, ...props }: DropdownMenuPortalProps): ReactNode;
8
8
  export interface DropdownMenuContentProps extends Omit<Primitive.DropdownMenuContentProps, 'asChild'> {
9
9
  seed?: string | number;
10
10
  }
@@ -5,16 +5,17 @@ import { PencilSurface } from '../PencilSurface.js';
5
5
  import { PencilText } from '../PencilText.js';
6
6
  import { PencilIcon } from '../PencilIcon.js';
7
7
  import { PencilLine } from '../PencilLine.js';
8
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef } from '../overlay/themeBridge.js';
8
+ import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef } from '../overlay/themeBridge.js';
9
9
  const classes = (...values) => values.filter(Boolean).join(' ');
10
10
  const copy = (children) => typeof children === 'string' ? _jsx(PencilText, { children: children }) : children;
11
11
  function Root(props) { return _jsx(PortalThemeProvider, { children: _jsx(Primitive.Root, { ...props }) }); }
12
12
  const Trigger = forwardRef(function Trigger(props, ref) {
13
13
  return _jsx(Primitive.Trigger, { ...props, ref: usePortalTriggerRef(ref) });
14
14
  });
15
- function Portal({ children, ...props }) { return _jsx(Primitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) }); }
15
+ function Portal({ children, ...props }) { return mapPortalChildren(children, child => _jsx(Primitive.Portal, { ...props, children: child })); }
16
16
  const Content = forwardRef(function Content({ className, children, seed, sideOffset = 6, collisionPadding = 8, ...props }, ref) {
17
17
  const id = useId();
18
+ props = usePortalThemeProps(props);
18
19
  return _jsx(Primitive.Content, { ...props, ref: ref, sideOffset: sideOffset, collisionPadding: collisionPadding, className: classes('inkra-menu', className), children: _jsx(PencilSurface, { variant: "paper", radius: 8, seed: seed ?? `menu-${id}`, className: "inkra-menu__paper", children: children }) });
19
20
  });
20
21
  function ItemBody({ children, icon }) {
@@ -213,6 +213,8 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
213
213
  let glyphs = new BoundedCache(128, 4 * 1024 * 1024);
214
214
  let disposed = false;
215
215
  let lastAppearance = '';
216
+ const numericControl = control instanceof HTMLInputElement && control.type === 'number';
217
+ let nativeNumberEditing = false;
216
218
  const appearanceKey = () => {
217
219
  const style = getComputedStyle(control);
218
220
  return JSON.stringify([
@@ -230,6 +232,12 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
230
232
  const paint = () => {
231
233
  if (disposed)
232
234
  return;
235
+ // Number inputs expose a sanitized value, not incomplete drafts such as "-" or "1.".
236
+ if (numericControl && (nativeNumberEditing || control.validity.badInput || composingRef.current)) {
237
+ root?.removeAttribute('data-pencil-editable-ready');
238
+ lastAppearance = '';
239
+ return;
240
+ }
233
241
  const key = appearanceKey();
234
242
  if (key === lastAppearance && root?.hasAttribute('data-pencil-editable-ready'))
235
243
  return;
@@ -249,7 +257,25 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
249
257
  }
250
258
  };
251
259
  scheduleRef.current = paint;
260
+ const beforeInput = (event) => {
261
+ if (!numericControl || event.defaultPrevented)
262
+ return;
263
+ nativeNumberEditing = true;
264
+ root?.setAttribute('data-pencil-number-editing', 'true');
265
+ paint();
266
+ };
267
+ const finishNumberEditing = () => {
268
+ if (!numericControl)
269
+ return;
270
+ nativeNumberEditing = false;
271
+ root?.removeAttribute('data-pencil-number-editing');
272
+ paint();
273
+ };
252
274
  const startComposition = () => {
275
+ if (numericControl) {
276
+ nativeNumberEditing = true;
277
+ root?.setAttribute('data-pencil-number-editing', 'true');
278
+ }
253
279
  composingRef.current = {
254
280
  value: control.value,
255
281
  start: control.selectionStart ?? control.value.length,
@@ -273,6 +299,8 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
273
299
  return;
274
300
  composingRef.current = null;
275
301
  root?.removeAttribute('data-composing');
302
+ nativeNumberEditing = false;
303
+ root?.removeAttribute('data-pencil-number-editing');
276
304
  refreshFonts();
277
305
  });
278
306
  const form = control.form;
@@ -284,6 +312,8 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
284
312
  }
285
313
  const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
286
314
  colorScheme.addEventListener('change', paint);
315
+ control.addEventListener('beforeinput', beforeInput);
316
+ control.addEventListener('blur', finishNumberEditing);
287
317
  control.addEventListener('input', paint);
288
318
  control.addEventListener('scroll', paint, { passive: true });
289
319
  control.addEventListener('compositionstart', startComposition);
@@ -301,6 +331,8 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
301
331
  resize?.disconnect();
302
332
  appearance.disconnect();
303
333
  colorScheme.removeEventListener('change', paint);
334
+ control.removeEventListener('beforeinput', beforeInput);
335
+ control.removeEventListener('blur', finishNumberEditing);
304
336
  control.removeEventListener('input', paint);
305
337
  control.removeEventListener('scroll', paint);
306
338
  control.removeEventListener('compositionstart', startComposition);
@@ -310,9 +342,11 @@ export function EditablePencilText({ controlRef, defaultValue, multiline = false
310
342
  document.fonts?.removeEventListener('loadingerror', refreshFonts);
311
343
  root?.removeAttribute('data-pencil-editable-ready');
312
344
  root?.removeAttribute('data-composing');
345
+ root?.removeAttribute('data-pencil-number-editing');
313
346
  composingRef.current = null;
314
347
  glyphs = new BoundedCache(0, 0);
315
348
  buffer.width = buffer.height = 0;
349
+ canvas.width = canvas.height = 0;
316
350
  scheduleRef.current = () => { };
317
351
  };
318
352
  }, [controlRef, multiline, seed]);
@@ -7,7 +7,7 @@ import { PencilText } from '../PencilText.js';
7
7
  import { InputAction } from './InputAction.js';
8
8
  import { useResolvedControlProps } from './controlProps.js';
9
9
  import { EditablePencilText } from './EditablePencilText.js';
10
- const editablePencilInputTypes = new Set(['text', 'search', 'email', 'url', 'tel']);
10
+ const editablePencilInputTypes = new Set(['text', 'search', 'email', 'url', 'tel', 'number']);
11
11
  const clearableInputTypes = new Set(['text', 'search', 'email', 'url', 'tel', 'password', 'number']);
12
12
  // Lucide X / LoaderCircle ISC geometry, rendered by the shared pencil engine.
13
13
  const clearPaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
@@ -1,4 +1,6 @@
1
1
  import type { InputProps } from '../form/Input.js';
2
+ import { type InputNumberStepModifier } from './model.js';
3
+ export type { InputNumberStepModifier } from './model.js';
2
4
  export interface InputNumberValueChangeDetails {
3
5
  reason: 'input' | 'increment' | 'decrement';
4
6
  }
@@ -9,6 +11,10 @@ export interface InputNumberProps extends Omit<InputProps, 'type' | 'value' | 'd
9
11
  min?: number;
10
12
  max?: number;
11
13
  step?: number | 'any';
14
+ stepModifiers?: readonly InputNumberStepModifier[];
15
+ pressAndHold?: boolean;
16
+ repeatDelay?: number;
17
+ repeatInterval?: number;
12
18
  controls?: boolean;
13
19
  incrementButtonLabel?: string;
14
20
  decrementButtonLabel?: string;