@solidrt/components 0.0.70 → 0.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -4
- package/README.md +10 -6
- package/docs/context-menu.md +1 -1
- package/docs/modal.md +1 -1
- package/docs/scroll-view.md +1 -1
- package/docs/select.md +1 -1
- package/docs/text-input.md +6 -2
- package/package.json +5 -2
- package/src/card.tsx +5 -2
- package/src/carousel.tsx +4 -1
- package/src/context-menu.tsx +16 -2
- package/src/dismissible.tsx +5 -2
- package/src/editor-field.tsx +28 -2
- package/src/image.tsx +2 -2
- package/src/modal.tsx +20 -6
- package/src/pressable.tsx +2 -2
- package/src/scroll-view.tsx +6 -10
- package/src/select.tsx +16 -2
- package/src/text-input.tsx +8 -0
- package/src/text.tsx +2 -2
- package/src/view.tsx +2 -2
- package/src/window.tsx +2 -2
package/AGENTS.md
CHANGED
|
@@ -73,7 +73,7 @@ One bullet per module, generated from the first paragraph of its docs/ file.
|
|
|
73
73
|
- `Text` - Themed text in a layout box. `variant` picks a typography role from the theme's type scale (`caption`/`label`/`body`/`title`/`heading`, default `body`); `color` picks a semantic theme color (`text`, `textMuted`, `primary`, `onPrimary`, `danger`, default `text`), with `muted` as sugar for `color="textMuted"`. Font fields go in `layout` (they affect measurement) and individually override the role; `style.color` still wins over `color`.
|
|
74
74
|
- `Image` - Loads and displays an image from a URL or raw bytes (`src: string | Uint8Array`). URL loads are shared runtime-wide: mounts of the same URL reuse one fetch and one texture, and the bytes are cached on disk (fetched with `cache: "force-cache"` - no freshness check, so use versioned URLs for content that changes). Concurrent asset fetches are kept polite with a per-host limit; a failed load rejects the mounts sharing it and a later remount retries.
|
|
75
75
|
- `SafeArea` - Wraps its children in a view padded clear of system UI intrusions (status bars, home indicators, notches). Top and bottom insets are applied by default; pass `false` to opt out of an edge, or a number to apply the inset with that minimum padding.
|
|
76
|
-
- `TextInput` - Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only). Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
76
|
+
- `TextInput` - Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only), `onCancel` on Escape or the platform's back, each followed by the blur. Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
77
77
|
- `RichTextEditor` - Edits a rich text `Document` (styled runs, paragraph attributes) in the same field as `TextInput`: always multiline, same caret, keys, wrapping, scrolling and sizing, and the same font fields on `layout` (the base font the document's runs style from). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue` (start from `plainDocument("")`). Formatting is driven through `editorRef`, which hands you the document buffer - the component ships no toolbar; the app renders its own controls.
|
|
78
78
|
- `Document model` - The value model behind `RichTextEditor`: a `Document` is `{ text, runs, blocks }` - plain text plus attributed runs (inline formatting) and per-paragraph blocks. `plainDocument(text)` builds one from a string; `createDocumentBuffer(doc)` wraps one in the editing API (`format`, `formatBlock`, `insertAtom`, `attributes`, selection and edits) that `editorRef` hands out. `ATOM` is the inline-atom placeholder character (U+FFFC) for embedded objects.
|
|
79
79
|
- `ScrollView` - A scrollable region; vertical by default, `horizontal` to flip. Both the wheel and dragging scroll the content: the drag activates after a small movement threshold along the scroll axis, also when it starts on a pressable (the press is cancelled and its feedback retracts), and keeps scrolling when the pointer leaves the box. Scrolling glides: the offset springs to each new target (250 ms, critically damped), so a wheel notch never jumps and a burst of notches reads as one motion; a dragging finger is tracked exactly, without the spring. A lift at speed flings: the content keeps moving from the finger's release speed and decays (iOS's deceleration), as one runtime-side animation to a projected destination, clamped to the range (it slows into an edge, no bounce). A finger landing on a moving list holds it where it is, a tap included.
|
|
@@ -95,11 +95,11 @@ One bullet per module, generated from the first paragraph of its docs/ file.
|
|
|
95
95
|
- `Spinner` - An indeterminate spinner: a 270-degree arc that rotates continuously, driven by core `onFrame`, so it participates in demand-driven rendering and stops when unmounted. `size` (diameter, default 24), `thickness` (default 3), `speed` (revolutions per second, default 1). Color comes from the theme `primary`; override via `style.color`.
|
|
96
96
|
- `ProgressBar` - A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill), and box the track with `borderColor`/`borderWidth`. The ends follow `radius.full` through `pill`, and the fill glows with the theme's accent glow.
|
|
97
97
|
- `Portal` - Renders its child somewhere other than its lexical position: by default at the window root, so overlays (modals, menus, tooltips) escape the clipping and stacking of their surrounding layout; `mount` targets another node captured from a `ref` instead. A thin JSX wrapper over core `createPortal`. The child should be a single element with `position="absolute"`, since it is inserted into the window's flex root. Portals cannot mount during the app's initial render, so gate them behind a signal that starts false.
|
|
98
|
-
- `Modal` - A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop
|
|
98
|
+
- `Modal` - A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop, Escape and the platform's back call `onClose` (unless `dismissable` is false, which keeps the modal up and still stops a back from reaching the screen beneath), pressing the content does not, and while mounted the modal traps `createFocusNav` inside itself.
|
|
99
99
|
- `Tooltip` - A hover-only affordance: under the `desktop`/`hybrid` interaction policies, resting a mouse pointer on the wrapped content shows a bubble near it after `delay` (default 500ms). Under the `touch` policy it never shows, so tooltip content must stay non-essential. The bubble is portal-mounted at the window root, clamped to the window edges, takes no pointer events, fades in and out, hides on leave and on press, and glows with the theme's overlay glow. A string/number `content` renders as themed body text; anything else as-is. `placement` picks the side (`"top"`, the default, or `"bottom"`).
|
|
100
|
-
- `Select` - A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside
|
|
100
|
+
- `Select` - A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside, Escape and the platform's back close without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
|
|
101
101
|
- `SegmentedControl` - A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`, and box the control with `borderColor`/`borderWidth`. The indicator glows with the theme's accent glow.
|
|
102
|
-
- `ContextMenu` - Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside
|
|
102
|
+
- `ContextMenu` - Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside, Escape and the platform's back close without selecting.
|
|
103
103
|
- `NavShell` - An app shell that arranges primary navigation around the content per `policy.navigation`: bottom tabs under it (`bottomTabs`), a narrow rail (`rail`), or a wide sidebar (`sidebar`) beside it. The content is a single stable node; switching arrangement only flips the shell's flex direction and remounts the stateless nav strip, so page state survives a resize across a breakpoint. `items` is a `NavItem[]` (`{ value, label, icon? }`; the icon renders above the label in tabs/rail, beside it in the sidebar); controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Safe areas are the caller's concern: wrap the shell in `SafeArea`. With `@solidrt/router` the shell is the tabs route's component, `value`/`onChange` wired to `useTabs()` (`active()`/`select(path)`) and `<Outlet />` as the content; the router then keeps a stack per tab and the tabs mounted.
|
|
104
104
|
- `SplitView` - A list-detail container driven by `policy.layout`: `twoPane` shows the `list` pane (width `listWidth`, default `theme.size.splitViewList`) beside the `detail` pane, `singlePane` shows one at a time per `showDetail`. Keep pane state (selection, scroll) in the app, not in the panes: crossing a breakpoint re-arranges and can remount them. It draws no chrome and adds no padding; a back affordance in the single-pane detail is the app's to render (fork on `policy.layout`).
|
|
105
105
|
- `QrCode` - Renders a QR code for `data` out of primitives: same-color modules in a row collapse into one box, drawn on a light quiet-zone panel; the grid recomputes only when `data` or `level` changes. It paints black on white by default (not the theme) so it stays scannable through a theme switch; override `color`/`background` only if the contrast still holds. `moduleSize` (default 6) is pixels per module, `margin` (default 16) the quiet zone (keep it non-zero), `level` the error correction (`L`/`M`/`Q`/`H`, default `M`: higher tolerates more damage but caps data length sooner), `radius` the panel's corner radius.
|
package/README.md
CHANGED
|
@@ -276,7 +276,7 @@ API: `SafeArea` - typed and commented in [src/safe-area.tsx](./src/safe-area.tsx
|
|
|
276
276
|
|
|
277
277
|
### TextInput
|
|
278
278
|
|
|
279
|
-
Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only). Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
279
|
+
Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only), `onCancel` on Escape or the platform's back, each followed by the blur. Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
280
280
|
|
|
281
281
|
```jsx
|
|
282
282
|
import { TextInput } from "@solidrt/components"
|
|
@@ -296,7 +296,11 @@ function NameField() {
|
|
|
296
296
|
}
|
|
297
297
|
```
|
|
298
298
|
|
|
299
|
-
`style` overrides the themed colors, border, and radius (`borderWidth: 0` draws no border at all, and no focus ring). The mouse cursor is the I-beam over the field. `autoFocus` focuses on mount (the on-screen keyboard still waits for a tap). A tap anywhere outside the field blurs it
|
|
299
|
+
`style` overrides the themed colors, border, and radius (`borderWidth: 0` draws no border at all, and no focus ring). The mouse cursor is the I-beam over the field. `autoFocus` focuses on mount (the on-screen keyboard still waits for a tap). A tap anywhere outside the field blurs it.
|
|
300
|
+
|
|
301
|
+
Leaving the field: Enter submits (`onSubmit` with the value, then the blur); Escape and the platform's back (the Android button, a pad's back button) cancel (`onCancel`, then the blur). `onCancel` only reports: the value stays as it is, and reverting it is the app's call. `onBlur` runs after either, and on a tap outside.
|
|
302
|
+
|
|
303
|
+
Keys the focused field keeps for itself, which never reach a parent `onKeyDown` or the window's shortcuts: Backspace, Delete, Left and Right, Home and End, Enter (and a remote's center key), Escape, Ctrl/Cmd+A and Ctrl/Cmd+V, Ctrl/Cmd+C and Ctrl/Cmd+X when text is selected, and every printable key while typing. A multiline field also keeps Up and Down. Everything else bubbles on (Tab, Up and Down in a single-line field, PageUp, function keys, Ctrl+S, ...), so a chat field that wants Up for its history wraps the field in a `View` with an `onKeyDown`.
|
|
300
304
|
|
|
301
305
|
A multiline field sizes like a flex item. Unconstrained, it grows with its content, up to `maxRows` rows and then scrolls. Sized by its layout it is a fixed box that scrolls to the caret: an explicit `height`, or `flexGrow: 1` in a parent with a height (the field fills what is left and scrolls once the text outgrows it), or a parent too small for the content (the field shrinks to fit rather than overflowing).
|
|
302
306
|
|
|
@@ -374,7 +378,7 @@ createEffect(
|
|
|
374
378
|
<ScrollView scrollRef={setScroll}>...</ScrollView>
|
|
375
379
|
```
|
|
376
380
|
|
|
377
|
-
The range changes whenever the content or the viewport changes size. The first fill (nothing was scrollable before it, whether it mounted with the view or arrived a second later) lands instantly, as a chat opens at its end; after that the view follows the end only if it was at the previous end, and the spring makes that follow a glide. The handle arrives
|
|
381
|
+
The range changes whenever the content or the viewport changes size. The first fill (nothing was scrollable before it, whether it mounted with the view or arrived a second later) lands instantly, as a chat opens at its end; after that the view follows the end only if it was at the previous end, and the spring makes that follow a glide. The handle arrives during mount, outside any reactive scope, so a signal setter can be passed as the ref; holding it in a signal lets the effect track its arrival. The offset is read untracked: the policy reacts to the range, not to every scroll.
|
|
378
382
|
|
|
379
383
|
A `scrollX`/`scrollY` entry in `transition` replaces the default spring: `transition={{ scrollY: { duration: 400, bounce: 0.2 } }}` (keep it a spring rather than a tween, because the wheel retargets mid-flight). The other entries animate the box itself and its background/border as on any component.
|
|
380
384
|
|
|
@@ -705,7 +709,7 @@ API: `Portal`, `PortalProps` - typed and commented in [src/portal.tsx](./src/por
|
|
|
705
709
|
|
|
706
710
|
### Modal
|
|
707
711
|
|
|
708
|
-
A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop
|
|
712
|
+
A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop, Escape and the platform's back call `onClose` (unless `dismissable` is false, which keeps the modal up and still stops a back from reaching the screen beneath), pressing the content does not, and while mounted the modal traps `createFocusNav` inside itself.
|
|
709
713
|
|
|
710
714
|
```jsx
|
|
711
715
|
import { Modal, Card, Button } from "@solidrt/components"
|
|
@@ -737,7 +741,7 @@ API: `Tooltip`, `TooltipProps` - typed and commented in [src/tooltip.tsx](./src/
|
|
|
737
741
|
|
|
738
742
|
### Select
|
|
739
743
|
|
|
740
|
-
A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside
|
|
744
|
+
A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside, Escape and the platform's back close without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
|
|
741
745
|
|
|
742
746
|
```jsx
|
|
743
747
|
import { Select } from "@solidrt/components"
|
|
@@ -771,7 +775,7 @@ API: `SegmentedControl`, `SegmentedControlProps` - typed and commented in [src/s
|
|
|
771
775
|
|
|
772
776
|
### ContextMenu
|
|
773
777
|
|
|
774
|
-
Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside
|
|
778
|
+
Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside, Escape and the platform's back close without selecting.
|
|
775
779
|
|
|
776
780
|
```jsx
|
|
777
781
|
import { ContextMenu } from "@solidrt/components"
|
package/docs/context-menu.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ContextMenu
|
|
2
2
|
|
|
3
|
-
Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside
|
|
3
|
+
Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside, Escape and the platform's back close without selecting.
|
|
4
4
|
|
|
5
5
|
```jsx
|
|
6
6
|
import { ContextMenu } from "@solidrt/components"
|
package/docs/modal.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Modal
|
|
2
2
|
|
|
3
|
-
A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop
|
|
3
|
+
A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop, Escape and the platform's back call `onClose` (unless `dismissable` is false, which keeps the modal up and still stops a back from reaching the screen beneath), pressing the content does not, and while mounted the modal traps `createFocusNav` inside itself.
|
|
4
4
|
|
|
5
5
|
```jsx
|
|
6
6
|
import { Modal, Card, Button } from "@solidrt/components"
|
package/docs/scroll-view.md
CHANGED
|
@@ -28,7 +28,7 @@ createEffect(
|
|
|
28
28
|
<ScrollView scrollRef={setScroll}>...</ScrollView>
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The range changes whenever the content or the viewport changes size. The first fill (nothing was scrollable before it, whether it mounted with the view or arrived a second later) lands instantly, as a chat opens at its end; after that the view follows the end only if it was at the previous end, and the spring makes that follow a glide. The handle arrives
|
|
31
|
+
The range changes whenever the content or the viewport changes size. The first fill (nothing was scrollable before it, whether it mounted with the view or arrived a second later) lands instantly, as a chat opens at its end; after that the view follows the end only if it was at the previous end, and the spring makes that follow a glide. The handle arrives during mount, outside any reactive scope, so a signal setter can be passed as the ref; holding it in a signal lets the effect track its arrival. The offset is read untracked: the policy reacts to the range, not to every scroll.
|
|
32
32
|
|
|
33
33
|
A `scrollX`/`scrollY` entry in `transition` replaces the default spring: `transition={{ scrollY: { duration: 400, bounce: 0.2 } }}` (keep it a spring rather than a tween, because the wheel retargets mid-flight). The other entries animate the box itself and its background/border as on any component.
|
|
34
34
|
|
package/docs/select.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Select
|
|
2
2
|
|
|
3
|
-
A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside
|
|
3
|
+
A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside, Escape and the platform's back close without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
|
|
4
4
|
|
|
5
5
|
```jsx
|
|
6
6
|
import { Select } from "@solidrt/components"
|
package/docs/text-input.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TextInput
|
|
2
2
|
|
|
3
|
-
Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only). Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
3
|
+
Text input, single-line by default; `multiline` wraps at the field's width and edits across lines (Enter inserts a newline, Up/Down move by line; grows with content up to `maxRows` unless its layout sizes the box, and scrolls to the caret). Controlled via `value`/`onInput`, or uncontrolled via `defaultValue`; `onSubmit` fires on Enter (single-line only), `onCancel` on Escape or the platform's back, each followed by the blur. Also `placeholder`, `maxLength`, `autoFocus`, `disabled`, `onFocus`/`onBlur`, and `hints` for IME behavior (keyboard type, capitalization, autocorrect - identifier-like fields want `{ capitalize: "none", autocorrect: false }`). The font fields of `layout` (`fontSize`, `fontFamily`, `lineHeight`, `fontStyle`, `fontWeight`) shape the text as on `Text`, with the rows, caret and scrolling following.
|
|
4
4
|
|
|
5
5
|
```jsx
|
|
6
6
|
import { TextInput } from "@solidrt/components"
|
|
@@ -20,7 +20,11 @@ function NameField() {
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`style` overrides the themed colors, border, and radius (`borderWidth: 0` draws no border at all, and no focus ring). The mouse cursor is the I-beam over the field. `autoFocus` focuses on mount (the on-screen keyboard still waits for a tap). A tap anywhere outside the field blurs it
|
|
23
|
+
`style` overrides the themed colors, border, and radius (`borderWidth: 0` draws no border at all, and no focus ring). The mouse cursor is the I-beam over the field. `autoFocus` focuses on mount (the on-screen keyboard still waits for a tap). A tap anywhere outside the field blurs it.
|
|
24
|
+
|
|
25
|
+
Leaving the field: Enter submits (`onSubmit` with the value, then the blur); Escape and the platform's back (the Android button, a pad's back button) cancel (`onCancel`, then the blur). `onCancel` only reports: the value stays as it is, and reverting it is the app's call. `onBlur` runs after either, and on a tap outside.
|
|
26
|
+
|
|
27
|
+
Keys the focused field keeps for itself, which never reach a parent `onKeyDown` or the window's shortcuts: Backspace, Delete, Left and Right, Home and End, Enter (and a remote's center key), Escape, Ctrl/Cmd+A and Ctrl/Cmd+V, Ctrl/Cmd+C and Ctrl/Cmd+X when text is selected, and every printable key while typing. A multiline field also keeps Up and Down. Everything else bubbles on (Tab, Up and Down in a single-line field, PageUp, function keys, Ctrl+S, ...), so a chat field that wants Up for its history wraps the field in a `View` with an `onKeyDown`.
|
|
24
28
|
|
|
25
29
|
A multiline field sizes like a flex item. Unconstrained, it grows with its content, up to `maxRows` rows and then scrolls. Sized by its layout it is a fixed box that scrolls to the caret: an explicit `height`, or `flexGrow: 1` in a parent with a height (the field fills what is left and scrolls once the text outgrows it), or a parent too small for the content (the field shrinks to fit rather than overflowing).
|
|
26
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.72",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"qrcode-generator": "^2.0.4"
|
|
21
21
|
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@solidrt/test": "0.0.72"
|
|
24
|
+
},
|
|
22
25
|
"peerDependencies": {
|
|
23
26
|
"@solidjs/signals": "2.0.0-rc.13",
|
|
24
|
-
"@solidrt/core": "0.0.
|
|
27
|
+
"@solidrt/core": "0.0.72"
|
|
25
28
|
}
|
|
26
29
|
}
|
package/src/card.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Show, createRadialGradient, createSignal, getLayoutBox, withAlpha } from "@solidrt/core"
|
|
1
|
+
import { Show, createRadialGradient, createSignal, getLayoutBox, untrack, withAlpha } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps, PointerEvent } from "@solidrt/core"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
4
|
import { policy } from "./policy"
|
|
@@ -78,7 +78,10 @@ export function Card(props: CardProps) {
|
|
|
78
78
|
onTransitionEnd={transitionEndFor("root", props.onTransitionEnd)}
|
|
79
79
|
ref={(n: { id: number }) => {
|
|
80
80
|
node = n
|
|
81
|
-
|
|
81
|
+
// A ref callback runs in the element's owned scope, where a prop
|
|
82
|
+
// read warns in dev (STRICT_READ_UNTRACKED); the caller's ref is a
|
|
83
|
+
// one-shot, read untracked.
|
|
84
|
+
untrack(() => props.ref)?.(n)
|
|
82
85
|
}}
|
|
83
86
|
repaintBoundary
|
|
84
87
|
flexDirection="column"
|
package/src/carousel.tsx
CHANGED
|
@@ -82,7 +82,10 @@ export function Carousel(props: CarouselProps) {
|
|
|
82
82
|
onTransitionEnd={transitionEndFor("root", props.onTransitionEnd)}
|
|
83
83
|
ref={(n: { id: number }) => {
|
|
84
84
|
node = n
|
|
85
|
-
|
|
85
|
+
// A ref callback runs in the element's owned scope, where a prop
|
|
86
|
+
// read warns in dev (STRICT_READ_UNTRACKED); the caller's ref is a
|
|
87
|
+
// one-shot, read untracked.
|
|
88
|
+
untrack(() => props.ref)?.(n)
|
|
86
89
|
}}
|
|
87
90
|
overflow="hidden"
|
|
88
91
|
{...props.layout}
|
package/src/context-menu.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, createLongPress, createPortal, onLayout, getBoundingBox, Show, For, env } from "@solidrt/core"
|
|
1
|
+
import { createEffect, createSignal, createLongPress, createPortal, onBack, onLayout, getBoundingBox, Show, For, env } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps, PointerEvent } from "@solidrt/core"
|
|
3
3
|
import { createPress } from "./press"
|
|
4
4
|
import { theme } from "./theme"
|
|
@@ -33,7 +33,8 @@ let margin = () => theme.spacing.sm
|
|
|
33
33
|
* pressable inside retracts and never fires, and a scroll that started
|
|
34
34
|
* first keeps it). The presentation forks on the interaction policy: touch
|
|
35
35
|
* gets a bottom sheet over a scrim, desktop/hybrid an anchored menu at the
|
|
36
|
-
* pointer. Pressing outside
|
|
36
|
+
* pointer. Pressing outside, Escape and the user's back close without
|
|
37
|
+
* selecting.
|
|
37
38
|
*/
|
|
38
39
|
export function ContextMenu(props: ContextMenuProps) {
|
|
39
40
|
let [open, setOpen] = createSignal(false)
|
|
@@ -55,6 +56,19 @@ export function ContextMenu(props: ContextMenuProps) {
|
|
|
55
56
|
item.onSelect?.()
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// Open, the menu is one step of core's back stack: Escape and the user's
|
|
60
|
+
// back close it as a press outside does, selecting nothing.
|
|
61
|
+
createEffect(
|
|
62
|
+
() => open(),
|
|
63
|
+
(isOpen) => {
|
|
64
|
+
if (!isOpen) return
|
|
65
|
+
return onBack((e) => {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
setOpen(false)
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
58
72
|
let bodyText = (color: string) => ({ ...typeStyle("body"), color, maxLines: 1 })
|
|
59
73
|
|
|
60
74
|
let ItemRow = (p: { item: ContextMenuItem; padY: number }) => {
|
package/src/dismissible.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, createSwipe, getLayoutBox } from "@solidrt/core"
|
|
1
|
+
import { createSignal, createSwipe, getLayoutBox, untrack } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps, SwipeDirection, TransitionEndEvent } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps, TransitionProps, TransitionStyleProp, TransitionViewProp } from "./types"
|
|
4
4
|
import { splitTransition, transitionEndFor } from "./types"
|
|
@@ -98,7 +98,10 @@ export function Dismissible(props: DismissibleProps) {
|
|
|
98
98
|
onTransitionEnd={transitionEndFor("root", props.onTransitionEnd)}
|
|
99
99
|
ref={(n: { id: number }) => {
|
|
100
100
|
node = n
|
|
101
|
-
|
|
101
|
+
// A ref callback runs in the element's owned scope, where a prop
|
|
102
|
+
// read warns in dev (STRICT_READ_UNTRACKED); the caller's ref is a
|
|
103
|
+
// one-shot, read untracked.
|
|
104
|
+
untrack(() => props.ref)?.(n)
|
|
102
105
|
}}
|
|
103
106
|
overflow="hidden"
|
|
104
107
|
{...props.layout}
|
package/src/editor-field.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
createSignal,
|
|
13
13
|
onCleanup,
|
|
14
14
|
focusedNode,
|
|
15
|
+
onBack,
|
|
15
16
|
setFocus,
|
|
16
17
|
startTextInput,
|
|
17
18
|
textInputActive,
|
|
@@ -63,6 +64,8 @@ export interface EditorFieldProps extends TransitionProps {
|
|
|
63
64
|
renderLine: (r: LineRender) => Element
|
|
64
65
|
|
|
65
66
|
onSubmit?: (value: string) => void
|
|
67
|
+
/** The user backed out of the field (Escape, or the platform's back): fires, then the field blurs. The value is left as it is. */
|
|
68
|
+
onCancel?: () => void
|
|
66
69
|
onFocus?: () => void
|
|
67
70
|
onBlur?: () => void
|
|
68
71
|
placeholder?: string
|
|
@@ -91,7 +94,8 @@ export interface EditorFieldProps extends TransitionProps {
|
|
|
91
94
|
// range replace it (the buffer's own behavior). Ctrl/Cmd+C/X/V go through
|
|
92
95
|
// navigator.clipboard (copy and cut need a range; single-line paste flattens
|
|
93
96
|
// line breaks). A tap outside the field blurs it (core's outside-tap blur in
|
|
94
|
-
// window.ts)
|
|
97
|
+
// window.ts). Escape and the user's back (core's onBack stack: the Android
|
|
98
|
+
// button, the pad's back) cancel: onCancel fires, then the field blurs.
|
|
95
99
|
// Font options compared field by field: the same keys with the same values.
|
|
96
100
|
function sameFont(a: MeasureTextOptions, b: MeasureTextOptions): boolean {
|
|
97
101
|
let ka = Object.keys(a) as (keyof MeasureTextOptions)[]
|
|
@@ -324,7 +328,7 @@ export function EditorField(props: EditorFieldProps) {
|
|
|
324
328
|
// The remote center key's `key` is "Unidentified"; match its code.
|
|
325
329
|
activateField()
|
|
326
330
|
} else if (e.key === "Escape") {
|
|
327
|
-
|
|
331
|
+
cancel()
|
|
328
332
|
} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && textInputActive()) {
|
|
329
333
|
// A printable key is this field's text (it arrives as a text-input
|
|
330
334
|
// event); consumed here so a typed Space never reaches the window as
|
|
@@ -371,6 +375,28 @@ export function EditorField(props: EditorFieldProps) {
|
|
|
371
375
|
}
|
|
372
376
|
}
|
|
373
377
|
|
|
378
|
+
// Cancel mirrors submit: the callback, then the blur. Escape reaches it
|
|
379
|
+
// through the key handler (the focused field consumes the key, so a window
|
|
380
|
+
// shortcut on Escape never fires on the same press); the user's back
|
|
381
|
+
// through the back stack, where the field is a step while focused. Focus
|
|
382
|
+
// comes after any enclosing overlay mounted, so the field tops the stack
|
|
383
|
+
// and a back leaves the field before it closes the overlay.
|
|
384
|
+
let cancel = () => {
|
|
385
|
+
props.onCancel?.()
|
|
386
|
+
setFocus(null)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
createEffect(
|
|
390
|
+
() => focused(),
|
|
391
|
+
(isFocused) => {
|
|
392
|
+
if (!isFocused) return
|
|
393
|
+
return onBack((e) => {
|
|
394
|
+
e.preventDefault()
|
|
395
|
+
cancel()
|
|
396
|
+
})
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
|
|
374
400
|
let unregisterNav: (() => void) | null = null
|
|
375
401
|
|
|
376
402
|
onCleanup(() => {
|
package/src/image.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createImage, createEffect, Loading, Errored, pct } from "@solidrt/core"
|
|
2
|
-
import type { ImageSource, LayoutProps, Pct,
|
|
2
|
+
import type { ImageSource, InputProps, LayoutProps, Pct, TextureProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps, TransitionProps } from "./types"
|
|
4
4
|
import { splitTransition, transitionEndFor } from "./types"
|
|
5
5
|
|
|
6
|
-
export interface ImageProps extends
|
|
6
|
+
export interface ImageProps extends InputProps, TransitionProps {
|
|
7
7
|
src: string | Uint8Array
|
|
8
8
|
/**
|
|
9
9
|
* How the image maps into the Image's box (CSS object-fit): "fill"
|
package/src/modal.tsx
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { createPortal, onCleanup } from "@solidrt/core"
|
|
2
|
-
import type { Color
|
|
1
|
+
import { createPortal, onBack, onCleanup } from "@solidrt/core"
|
|
2
|
+
import type { Color } from "@solidrt/core"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
4
|
import { pushNavScope } from "./focus-nav"
|
|
5
5
|
import { colorFade, popupFade } from "./motion"
|
|
6
6
|
|
|
7
7
|
export interface ModalProps {
|
|
8
|
-
// Called when the backdrop (the area around the content) is pressed,
|
|
9
|
-
// `dismissable` is false.
|
|
8
|
+
// Called when the backdrop (the area around the content) is pressed, or on
|
|
9
|
+
// Escape or the platform's back, unless `dismissable` is false.
|
|
10
10
|
onClose?: () => void
|
|
11
11
|
// The modal content, centered over the backdrop.
|
|
12
12
|
children?: any
|
|
13
13
|
// Scrim color behind the content. Defaults to the theme scrim; pass
|
|
14
14
|
// "transparent" for no dim.
|
|
15
15
|
backdropColor?: Color
|
|
16
|
-
// Whether
|
|
16
|
+
// Whether a backdrop press, Escape or back calls onClose. Defaults to true.
|
|
17
|
+
// False keeps the modal up until the app removes it; back still stops at
|
|
18
|
+
// the modal, never reaching the screen beneath.
|
|
17
19
|
dismissable?: boolean
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -30,12 +32,24 @@ export interface ModalProps {
|
|
|
30
32
|
* works because pointer events dispatch to the whole hit path with no
|
|
31
33
|
* stopPropagation, so the content is kept a sibling of the backdrop (not a
|
|
32
34
|
* child): a press on the content never has the backdrop on its path.
|
|
35
|
+
*
|
|
36
|
+
* While mounted the modal is one step of core's back stack: Escape and the
|
|
37
|
+
* user's back close it as the backdrop does. Registered at mount, so a field
|
|
38
|
+
* focused inside it (which registers at focus) sits above it and a back
|
|
39
|
+
* leaves the field first, the modal next.
|
|
33
40
|
*/
|
|
34
41
|
export function Modal(props: ModalProps) {
|
|
35
|
-
let dismiss = (
|
|
42
|
+
let dismiss = () => {
|
|
36
43
|
if (props.dismissable !== false) props.onClose?.()
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
// A modal that is not dismissable still takes the step and does nothing:
|
|
47
|
+
// it wants an answer, and the screen beneath must not get the back.
|
|
48
|
+
onBack((e) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
dismiss()
|
|
51
|
+
})
|
|
52
|
+
|
|
39
53
|
// While mounted, the modal is a focus-navigation trap: its container tops
|
|
40
54
|
// the nav scope stack, so createFocusNav only reaches controls inside it.
|
|
41
55
|
let popNavScope: (() => void) | null = null
|
package/src/pressable.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { children, untrack } from "@solidrt/core"
|
|
2
|
-
import type { Element,
|
|
2
|
+
import type { Element, InputProps, LayoutProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps, TransitionProps } from "./types"
|
|
4
4
|
import { splitTransition, transitionEndFor, withTransitionDefaults } from "./types"
|
|
5
5
|
import { createPress, type PressState } from "./press"
|
|
@@ -7,7 +7,7 @@ import { colorFade, scaleFeedback } from "./motion"
|
|
|
7
7
|
|
|
8
8
|
export type { PressState } from "./press"
|
|
9
9
|
|
|
10
|
-
export interface PressableProps extends
|
|
10
|
+
export interface PressableProps extends InputProps, TransitionProps {
|
|
11
11
|
// children and style may be functions of the press state, so a caller can
|
|
12
12
|
// restyle on press/hover without wiring their own signals. The state is live
|
|
13
13
|
// (getters, not a snapshot): read it inside a prop or child expression, never
|
package/src/scroll-view.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createPan, createScroll, createSignal, getBoundingBoxViewport, getLayoutBox
|
|
2
|
-
import type { LayoutProps, PointerEvent,
|
|
1
|
+
import { callRef, createPan, createScroll, createSignal, getBoundingBoxViewport, getLayoutBox } from "@solidrt/core"
|
|
2
|
+
import type { InputProps, LayoutProps, PointerEvent, Scroll, TransitionCurve, TransitionEndEvent, WheelEvent } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps, TransitionProps, TransitionScrollProp, TransitionStyleProp, TransitionViewProp } from "./types"
|
|
4
4
|
import { splitTransition, transitionEndFor } from "./types"
|
|
5
5
|
|
|
6
6
|
export interface ScrollViewProps
|
|
7
|
-
extends
|
|
7
|
+
extends InputProps,
|
|
8
8
|
TransitionProps<TransitionViewProp | TransitionStyleProp | TransitionScrollProp> {
|
|
9
9
|
children?: any
|
|
10
10
|
ref?: (node: { id: number }) => void
|
|
@@ -14,8 +14,8 @@ export interface ScrollViewProps
|
|
|
14
14
|
horizontal?: boolean
|
|
15
15
|
/** Receives the scroll handle (offset, range, scrollTo) for driving the view
|
|
16
16
|
* from app code; scroll policies such as following a growing log are written
|
|
17
|
-
* against it. Called
|
|
18
|
-
*
|
|
17
|
+
* against it. Called during mount, outside any reactive scope, so a signal
|
|
18
|
+
* setter can be passed directly. */
|
|
19
19
|
scrollRef?: (scroll: Scroll) => void
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -72,11 +72,7 @@ export function ScrollView(props: ScrollViewProps) {
|
|
|
72
72
|
() => content,
|
|
73
73
|
{ axis: props.horizontal ? "horizontal" : "vertical" },
|
|
74
74
|
)
|
|
75
|
-
|
|
76
|
-
// scope, where a signal write (an app passing its setter) is refused.
|
|
77
|
-
onSettled(() => {
|
|
78
|
-
untrack(() => props.scrollRef)?.(scroll)
|
|
79
|
-
})
|
|
75
|
+
callRef(() => props.scrollRef, scroll)
|
|
80
76
|
|
|
81
77
|
// Content follows the finger: it moves opposite to scroll offsets, which
|
|
82
78
|
// grow toward the bottom/right. The lift's velocity (parent-frame px/s,
|
package/src/select.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, createPortal, onLayout, getBoundingBox, Show, For, env } from "@solidrt/core"
|
|
1
|
+
import { createEffect, createSignal, createPortal, onBack, onLayout, getBoundingBox, Show, For, env } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
3
3
|
import { createPress } from "./press"
|
|
4
4
|
import { theme } from "./theme"
|
|
@@ -33,7 +33,8 @@ let margin = () => theme.spacing.sm
|
|
|
33
33
|
* A single-choice picker whose presentation forks on the interaction policy:
|
|
34
34
|
* desktop/hybrid opens an anchored dropdown under the trigger (flipping above
|
|
35
35
|
* when there is no room), touch opens a bottom sheet over a scrim. Same
|
|
36
|
-
* value/onChange contract either way; pressing outside
|
|
36
|
+
* value/onChange contract either way; pressing outside, Escape and the user's
|
|
37
|
+
* back close without a change.
|
|
37
38
|
* The option list is not scrollable yet, so keep it short.
|
|
38
39
|
*/
|
|
39
40
|
export function Select<T>(props: SelectProps<T>) {
|
|
@@ -51,6 +52,19 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
51
52
|
props.onChange?.(v)
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// Open, the picker is one step of core's back stack: Escape and the user's
|
|
56
|
+
// back close it as a press outside does, without a change.
|
|
57
|
+
createEffect(
|
|
58
|
+
() => open(),
|
|
59
|
+
(isOpen) => {
|
|
60
|
+
if (!isOpen) return
|
|
61
|
+
return onBack((e) => {
|
|
62
|
+
e.preventDefault()
|
|
63
|
+
setOpen(false)
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
54
68
|
let bodyText = (color: string) => ({ ...typeStyle("body"), color, maxLines: 1 })
|
|
55
69
|
|
|
56
70
|
// One option row, shared by both presentations; only the vertical padding
|
package/src/text-input.tsx
CHANGED
|
@@ -10,6 +10,13 @@ export interface TextInputProps extends TransitionProps {
|
|
|
10
10
|
defaultValue?: string
|
|
11
11
|
onInput?: (value: string) => void
|
|
12
12
|
onSubmit?: (value: string) => void
|
|
13
|
+
/**
|
|
14
|
+
* The user backed out of the field: Escape, or the platform's back (the
|
|
15
|
+
* Android button, a pad's back). Fires, then the field blurs, as `onSubmit`
|
|
16
|
+
* does on Enter; `onBlur` follows either. Reports only: the value stays as
|
|
17
|
+
* it is, reverting it is the app's call.
|
|
18
|
+
*/
|
|
19
|
+
onCancel?: () => void
|
|
13
20
|
onFocus?: () => void
|
|
14
21
|
onBlur?: () => void
|
|
15
22
|
|
|
@@ -75,6 +82,7 @@ export function TextInput(props: TextInputProps) {
|
|
|
75
82
|
</d-text>
|
|
76
83
|
)}
|
|
77
84
|
onSubmit={props.onSubmit}
|
|
85
|
+
onCancel={props.onCancel}
|
|
78
86
|
onFocus={props.onFocus}
|
|
79
87
|
onBlur={props.onBlur}
|
|
80
88
|
placeholder={props.placeholder}
|
package/src/text.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createMemo } from "@solidrt/core"
|
|
2
|
-
import type {
|
|
2
|
+
import type { InputProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps, TextLayoutProps, TransitionProps, TransitionViewProp } from "./types"
|
|
4
4
|
import { splitTextLayout, splitTransition, transitionEndFor, withTransitionDefaults } from "./types"
|
|
5
5
|
import { theme, type TextVariant } from "./theme"
|
|
@@ -10,7 +10,7 @@ import { colorFade } from "./motion"
|
|
|
10
10
|
// make sense as a text fill; style.color takes raw values for anything else.
|
|
11
11
|
export type TextColor = "text" | "textMuted" | "primary" | "onPrimary" | "danger"
|
|
12
12
|
|
|
13
|
-
export interface TextProps extends
|
|
13
|
+
export interface TextProps extends InputProps, TransitionProps<TransitionViewProp | "color"> {
|
|
14
14
|
children?: any
|
|
15
15
|
// Typography role from the theme's type scale; defaults to "body". Explicit
|
|
16
16
|
// layout font props override the role's fields individually. fontSize
|
package/src/view.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InputProps, LayoutProps } from "@solidrt/core"
|
|
2
2
|
import type { StyleProps, TransitionProps } from "./types"
|
|
3
3
|
import { splitTransition, transitionEndFor, withTransitionDefaults } from "./types"
|
|
4
4
|
import { colorFade } from "./motion"
|
|
5
5
|
|
|
6
|
-
export interface ViewProps extends
|
|
6
|
+
export interface ViewProps extends InputProps, TransitionProps {
|
|
7
7
|
children?: any
|
|
8
8
|
ref?: (node: { id: number }) => void
|
|
9
9
|
layout?: LayoutProps
|
package/src/window.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createEffect, displayScale, warmText } from "@solidrt/core"
|
|
2
|
-
import type {
|
|
2
|
+
import type { InputProps, LayoutProps, WindowShaderProps } from "@solidrt/core"
|
|
3
3
|
import type { StyleProps } from "./types"
|
|
4
4
|
import { colorFade } from "./motion"
|
|
5
5
|
import { theme } from "./theme"
|
|
6
6
|
import { typeScaleStyles } from "./typography"
|
|
7
7
|
|
|
8
|
-
export interface WindowProps extends
|
|
8
|
+
export interface WindowProps extends InputProps {
|
|
9
9
|
children?: any
|
|
10
10
|
title?: string
|
|
11
11
|
fullscreen?: boolean
|