@jsenv/navi 0.29.394 → 0.29.396

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/README.md CHANGED
@@ -1,63 +1,99 @@
1
1
  # @jsenv/navi
2
2
 
3
- A frontend framework for building modern web applications, focused on routing, async data, and UI components.
3
+ A Preact framework for building web applications: routing, async data, REST state, controls and forms, popups, gestures, layout and text. Every export can be used on its own, but they are designed to work together, and an app gets the most out of navi by reaching for its primitive whenever one exists.
4
+
5
+ This page names what navi provides, area by area. The reasoning behind each area lives in [docs/](./docs/), and [docs/AI_INSTRUCTIONS.md](./docs/AI_INSTRUCTIONS.md) lists every doc with the moment to read it.
4
6
 
5
7
  ## Routing
6
8
 
7
9
  Routing is signal-based, which means URL state — including search params — can be bound to signals with two-way synchronization. Change a signal, the URL updates. Navigate to a URL, the signals update. Search params can also be validated and typed, so you always work with the right shape of data.
8
10
 
9
- Routes are flexible: you can create route groups to share logic, state, or UI across multiple routes. Nested routing is supported, and the structure naturally maps to how your application is organized.
11
+ Routes are flexible: you can create route groups to share logic, state, or UI across multiple routes. Nested routing is supported, and the structure naturally maps to how your application is organized. `routeFallback()` names the not-found page.
12
+
13
+ `Link` knows whether it points where the user is, and `Nav` turns links into a tab row whose indicator travels from tab to tab. `Binder` draws tabs and the page they open as one shape. `RouteTravel` lets a thumb drag from one URL to the next without ever mounting a route that does not match. See [docs/navigation.md](./docs/navigation.md).
14
+
15
+ ## Transitions
16
+
17
+ A navigation can be animated as a relation the user reads as a map: `defineRouteTransition` declares how one page gives way to another (a slide, a cover), and the browser's back button plays it the other way. See [docs/route_transitions.md](./docs/route_transitions.md), and [docs/view_transitions.md](./docs/view_transitions.md) before starting a view transition of your own.
18
+
19
+ `UITransition` animates a change inside a box — one content replacing another, or a loading state giving way to the result — while the box resizes from one to the other.
20
+
21
+ ## State
22
+
23
+ `stateSignal` is a signal that knows more about its value: a type, a default (possibly another signal), constraints such as `oneOf` or `min`/`max`, and where it lives — memory, `localStorage` (`persists`), or the URL when a route binds it.
10
24
 
11
- How to declare routes, render them, and turn them into tabs — including `RouteTravel`, which lets a thumb drag from one URL to the next without ever mounting a route that does not match — is in [docs/navigation.md](./docs/navigation.md).
25
+ State navi shows is bound, not copied back by a callback: a control, a popup, a tab row take a `signal` and read and write it themselves. See [docs/state_binding.md](./docs/state_binding.md).
12
26
 
13
27
  ## Actions
14
28
 
15
- Actions are async operations with lifecycle management — pending, success, error. You can declare actions that run when navigating to a route, and any component can subscribe to them via `useAsyncData` to reflect what is happening: loading states, results, errors. No manual wiring.
29
+ Actions are async operations with lifecycle management — pending, success, error. You can declare actions that run when navigating to a route, and any component can subscribe to them via `useAsyncData` to reflect what is happening: loading states, results, errors. No manual wiring. See [docs/actions.md](./docs/actions.md), and [docs/data_states.md](./docs/data_states.md) for drawing a skeleton, a result and an error from the same action.
16
30
 
17
31
  Code loaded on demand is the same thing: a page's `import()` is a route action, started with the page's data on the url change and prefetched when a link to it is hovered or focused; a component inside a page that stays reads its own with `useAsyncData(() => import(…))`. One wait, one failure, no lazy-shaped API. See [docs/dynamic_import.md](./docs/dynamic_import.md).
18
32
 
33
+ Errors are kept in two kinds — a request refused versus a bug — each shown in its own place; `ErrorBoundary` and `Loading` are the boundaries. See [docs/error_handling.md](./docs/error_handling.md).
34
+
19
35
  ## REST state
20
36
 
21
37
  `resource()` turns a REST endpoint into a reactive store: one action per verb, a
22
38
  shared signal store every component reads from, and automatic invalidation
23
39
  between related actions. Parent/child relations are first-class — `.one`,
24
40
  `.many`, `.scopedOne`, `.scopedMany` model a backend sub-resource such as
25
- `/games/:id/candidates` rather than leaving you to hand-roll it. See
26
- [docs/resource.md](./docs/resource.md).
41
+ `/games/:id/candidates` rather than leaving you to hand-roll it. With `persist`,
42
+ the last answer is drawn again right after a reload, before the network
43
+ answers. See [docs/resource.md](./docs/resource.md).
27
44
 
28
- ## Layout & Typography
45
+ `setNetworkPolicy` decides whether a request may go out at all — offline, or
46
+ any reason the app has: writes are held, reads answered from the store or let
47
+ through, and the controls bound to them say why. See
48
+ [docs/network_policy.md](./docs/network_policy.md).
29
49
 
30
- **`Box`** is the main layout primitive. It wraps CSS Flexbox with a friendlier API: `flex` for horizontal layout, `flex="y"` for vertical (the axis things line up on, rather than guessing what `flex-direction: column` does visually). Supports `grid`, `inline`, alignment via `alignX`/`alignY`, and spacing props.
50
+ ## Fields & Forms
31
51
 
32
- **`createSlot`** renders content declared in one place at another: a toolbar, a status, or one `SidePanel` for a whole board bound to the state that names what it shows (see [docs/popup_open.md](./docs/popup_open.md)).
52
+ Field components (`Input` of every type, checkbox and radio included, `Select`, `Textarea`, `Picker`, …) hold their own value. Inside a `Form` they need nothing wired: the form reads them when it sends, through the same action system, and sends nothing when nothing changed. When the app needs a value, a `signal` binds it both ways. An `action` runs what a change must cause when that can fail or take time; it is one option, not the way a field is wired. See [docs/control_value.md](./docs/control_value.md) and [docs/form_changed.md](./docs/form_changed.md).
33
53
 
34
- **`Text`** and related components (`Title`, `Paragraph`, `Code`, `Caption`) handle typography consistently across the app — including the parts of a line that are not text: icons, counts, truncation (`maxLines`), skeletons, and where a line may break. See [docs/typography.md](./docs/typography.md).
54
+ `Picker` is a control whose value is chosen in a popup: a list of options, a confirmation, or several controls composing one object value. Its trigger wears the wait and the error of what the choice runs. See [docs/control_object.md](./docs/control_object.md).
35
55
 
36
- ## Texts & i18n
56
+ Validation goes beyond native browser constraints: custom rules, better error positioning, real-time feedback, and a UX that doesn't punish users before they've finished typing. See [docs/field_validation.md](./docs/field_validation.md).
37
57
 
38
- `interpolateText`/`Interpolate` keep a sentence containing values readable as one string, and `createI18n` gives the app a single place holding its wording — worth it even with one language. `naviI18n` is where navi's own texts (validation messages, button labels, relative time) can be overridden or translated. See [docs/i18n.md](./docs/i18n.md).
58
+ Some values are moved rather than typed: `Spin` puts the way back and the way on around a value one step at a time, and `SpinGroup` makes several of them read as one — `TimeSpin` ("07h30") and `TimeRangeSpin` ("de 7h à 21h", end after start) are built that way. `Wheel` is the scroll picker a phone shows for the same job, with `TimeWheel` and `TimeRangeWheel` built on it.
39
59
 
40
- ## Icons
60
+ `Group` makes several controls read as one framed object, `Editable` edits a value in place, exactly where it is written.
41
61
 
42
- Icons are a piece that is often missing or painful in web projects. The `Icon` component makes icons behave like text — they scale with font size, inherit color, and align naturally in any layout. No sizing hacks, no SVG wrangling.
62
+ ## List
43
63
 
44
- ## Fields & Forms
64
+ `List` shows a collection: rows that can be selected, searched (`createSearch`), grouped and separated, with a skeleton while the first answer is out and the error in its place when it fails. Thousands of rows render through `<List.Items>` and a render window, and a list remembers where it was scrolled when the user comes back. See [docs/scroll.md](./docs/scroll.md), [docs/list_refresh.md](./docs/list_refresh.md) and [docs/list_action.md](./docs/list_action.md).
65
+
66
+ ## Table
45
67
 
46
- Field components (`Input` of every type, checkbox and radio included, `Select`, `Textarea`, `Picker`, …) hold their own value. Inside a `Form` they need nothing wired: the form reads them when it sends, through the same action system. When the app needs a value, a `signal` binds it both ways. An `action` runs what a change must cause when that can fail or take time; it is one option, not the way a field is wired. See [docs/control_value.md](./docs/control_value.md).
68
+ A `Table` for spreadsheet-like screens: columns reordered by drag, columns and rows resized, sticky leading rows and columns, and cell, row and column selection with keyboard shortcuts.
47
69
 
48
- Validation goes beyond native browser constraints: custom rules, better error positioning, real-time feedback, and a UX that doesn't punish users before they've finished typing. See [docs/field_validation.md](./docs/field_validation.md).
70
+ ## Popups
49
71
 
50
- Some values are moved rather than typed: `Spin` puts the way back and the way on around a value one step at a time, and `SpinGroup` makes several of them read as one — `TimeSpin` ("07h30") and `TimeRangeSpin` ("de 7h à 21h", end after start) are built that way.
72
+ `Dialog`, `Popover` and `SidePanel` own whether they are open; what varies is the trigger — a button (`command` + `commandFor`), a gesture, a piece of application state. A popup can lift the card that was pressed to the front (`animation="lifting"`), dock to an edge on a small touch screen, and choose how far the page behind it withdraws. `Expandable` opens in place, pushing the content below. `createSlot` renders content declared in one place at another: one `SidePanel` for a whole board, bound to the state that names what it shows. See [docs/popup_open.md](./docs/popup_open.md), [docs/popup_backdrop.md](./docs/popup_backdrop.md), [docs/popup_lift.md](./docs/popup_lift.md) and [docs/dialog_shape.md](./docs/dialog_shape.md).
51
73
 
52
- ## Table
74
+ A callout (`openCallout`) points at an element to say something about it — a validation message, a refused press.
53
75
 
54
- A `Table` for spreadsheet-like screens: columns reordered by drag, columns and rows resized, sticky leading rows and columns, and cell, row and column selection with keyboard shortcuts.
76
+ ## Gestures
77
+
78
+ A gesture is named, not read by hand: the `interactions` prop, available on every component, answers swipes, holds, double clicks and keyboard shortcuts with an action. Elements are carried with `move`, `reorder`, `land`, `toss` and `leave`, and a surface is panned and zoomed with `pan`/`zoom`. Touch, mouse and pen share one model of who owns a press. See [docs/interactions.md](./docs/interactions.md), [docs/drag_interactions.md](./docs/drag_interactions.md), [docs/pan_zoom.md](./docs/pan_zoom.md) and [docs/drag_to_travel.md](./docs/drag_to_travel.md).
79
+
80
+ Who holds the keyboard, and whether a focus ring shows, is decided in one place for every component. See [docs/autofocus.md](./docs/autofocus.md).
55
81
 
56
- ## Other
82
+ ## Layout
57
83
 
58
- Dialogs, badges, details/collapsible, separators, keyboard shortcuts, popovers, copy-to-clipboard, and other utilities.
84
+ **`Box`** is the main layout primitive. It wraps CSS Flexbox with a friendlier API: `flex` for horizontal layout, `flex="y"` for vertical (the axis things line up on, rather than guessing what `flex-direction: column` does visually). Supports `grid`, `inline`, alignment via `alignX`/`alignY`, and spacing props.
85
+
86
+ `FixedBar` pins a strip to an edge of the window and gives the space back to the content, clearing the notch and the virtual keyboard (see [docs/safe_area.md](./docs/safe_area.md)). `SlideContainer` holds slides that replace one another in one box, dragged or bound to the URL. `CardLayout`, `StepList` and `Separator` cover the rest of a screen's usual shapes.
87
+
88
+ Components are styled from `@layer navi` and `--navi-*` tokens, so an app theme and a plain CSS rule win without a fight. See [docs/css_architecture.md](./docs/css_architecture.md).
89
+
90
+ ## Text & i18n
91
+
92
+ **`Text`** and related components (`Title`, `Paragraph`, `Code`, `Caption`) handle typography consistently across the app — including the parts of a line that are not text: icons, counts, truncation (`maxLines`), skeletons, and where a line may break. Values have components of their own: `Time`, `TimeRange`, `Quantity`, `Unit`, `Meter`, `Badge`, `BadgeList`. See [docs/typography.md](./docs/typography.md).
93
+
94
+ Icons are a piece that is often missing or painful in web projects. The `Icon` component makes icons behave like text — they scale with font size, inherit color, and align naturally in any layout. No sizing hacks, no SVG wrangling.
59
95
 
60
- A `Dialog`/`Popover` owns whether it is open; what varies is the trigger — a button, a gesture, a piece of application state. Which one to use, and what each costs, is in [docs/popup_open.md](./docs/popup_open.md).
96
+ `interpolateText`/`Interpolate` keep a sentence containing values readable as one string, and `createI18n` gives the app a single place holding its wording — worth it even with one language. `naviI18n` is where navi's own texts (validation messages, button labels, relative time) can be overridden or translated. The `format*` helpers write dates, times, durations and numbers for the user's language. See [docs/i18n.md](./docs/i18n.md).
61
97
 
62
98
  ---
63
99
 
@@ -6837,10 +6837,6 @@ const css$13 = /* css */ `
6837
6837
  border: none;
6838
6838
  outline: none; /* programmatic focus may land here briefly before being redirected to close button */
6839
6839
  opacity: 0;
6840
- /* opacity only: the placement lives in the translate property, which
6841
- applyNewPosition (visible_rect.js) owns and animates itself through the
6842
- Web Animations API, same mechanism Popover/Dialog use. */
6843
- transition: opacity 0.2s ease-in-out;
6844
6840
  cursor: initial; /* Do not inherit element cursor, inside the element but should use regular cursor */
6845
6841
  pointer-events: auto; /* Must be interactive to be closabled (overrid list item pointer-events none for instance) */
6846
6842
  overflow: visible;
@@ -6849,6 +6845,15 @@ const css$13 = /* css */ `
6849
6845
  position: absolute;
6850
6846
  }
6851
6847
 
6848
+ /* Armed after the first placement: the first show answers a tap or a
6849
+ click and appears at once, the fade is for the anchor scrolling out of
6850
+ view and back. Opacity only: the placement lives in the translate
6851
+ property, which applyNewPosition (visible_rect.js) owns and animates
6852
+ itself through the Web Animations API, same mechanism Popover/Dialog use. */
6853
+ &[data-placed] {
6854
+ transition: opacity 0.12s ease-out;
6855
+ }
6856
+
6852
6857
  &[data-status="success"] {
6853
6858
  --x-callout-status-color: var(--callout-success-color);
6854
6859
  }
@@ -8290,6 +8295,13 @@ const positionCallout = (
8290
8295
  calloutFrameElement.innerHTML = generateSvgWithoutArrow(width, height);
8291
8296
  calloutElement.style.opacity = 1;
8292
8297
  }
8298
+ if (!calloutElement.hasAttribute("data-placed")) {
8299
+ // Style flush between the first opacity write and arming the
8300
+ // transition, so that first write lands unanimated in the same frame.
8301
+ // eslint-disable-next-line no-unused-expressions
8302
+ getComputedStyle(calloutElement).opacity;
8303
+ calloutElement.setAttribute("data-placed", "");
8304
+ }
8293
8305
  applyNewPosition(calloutElement, position);
8294
8306
  },
8295
8307
  );
@@ -42108,7 +42120,15 @@ const useControlProps = (props, {
42108
42120
  if (actsOnMouseDown) {
42109
42121
  return {
42110
42122
  name: "mousedown",
42111
- allowed: () => onButtonInteractionAllowed(e)
42123
+ allowed: () => onButtonInteractionAllowed(e),
42124
+ // The press is answered here, refusal included: the click it
42125
+ // leaves behind would otherwise follow the href of a
42126
+ // <Button route>. Not asked about again — a second refusal
42127
+ // reads as "got it" and dismisses the callout just shown.
42128
+ prevented: () => {
42129
+ const clickSuppressionIsOver = suppressClickAfterGesture();
42130
+ clickSuppressionIsOver();
42131
+ }
42112
42132
  };
42113
42133
  }
42114
42134
  return null;
@@ -42119,7 +42139,11 @@ const useControlProps = (props, {
42119
42139
  }
42120
42140
  return {
42121
42141
  name: "click",
42122
- allowed: () => onButtonInteractionAllowed(e)
42142
+ allowed: () => onButtonInteractionAllowed(e),
42143
+ // A refused press keeps nothing of the click's default: the href
42144
+ // of a <Button route> (rendered as <a>), a form submit, a
42145
+ // popovertarget.
42146
+ prevented: () => e.preventDefault()
42123
42147
  };
42124
42148
  }
42125
42149
  };