@inglorious/ui 7.4.0 → 7.6.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.
package/README.md
CHANGED
|
@@ -46,17 +46,22 @@ The public CSS exports are:
|
|
|
46
46
|
|
|
47
47
|
## Safe-area utilities
|
|
48
48
|
|
|
49
|
-
The base styles also expose CSS variables and utility classes for
|
|
49
|
+
The base styles also expose CSS variables and utility classes for the system bars' insets: the notch, rounded corners, the home indicator and Android's status and navigation bars. They are in `base.css`, or on their own in `@inglorious/ui/safe-area.css`.
|
|
50
50
|
|
|
51
51
|
```css
|
|
52
52
|
:root {
|
|
53
|
-
--safe-area-top:
|
|
54
|
-
--safe-area-bottom:
|
|
55
|
-
--safe-area-left:
|
|
56
|
-
--safe-area-right:
|
|
53
|
+
--safe-area-top: …;
|
|
54
|
+
--safe-area-bottom: …;
|
|
55
|
+
--safe-area-left: …;
|
|
56
|
+
--safe-area-right: …;
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
They are `env(safe-area-inset-*)`, with two corrections:
|
|
61
|
+
|
|
62
|
+
- **In a Capacitor app on Android**, they use the `--safe-area-inset-*` variables Capacitor's SystemBars plugin injects. `env()` alone doubles the status bar on WebViews that Capacitor already pads natively.
|
|
63
|
+
- **In a browser tab, the top inset is 0.** The browser's chrome always sits between the page and the status bar, but browsers that embed a WebView can still report one (DuckDuckGo on Android reports 37px under its toolbar). The top inset applies in the installed PWA and in the native app, which `mount` from `@inglorious/web` marks with the `iw-native` class on `<html>`.
|
|
64
|
+
|
|
60
65
|
The following utility classes are available:
|
|
61
66
|
|
|
62
67
|
```css
|
|
@@ -73,5 +78,3 @@ The following utility classes are available:
|
|
|
73
78
|
padding-right: var(--safe-area-right);
|
|
74
79
|
}
|
|
75
80
|
```
|
|
76
|
-
|
|
77
|
-
These are useful for mobile layouts that need to respect notches, rounded corners, and the home indicator. They are intentionally generic utilities so apps can opt into them without having to define custom safe-area values repeatedly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ui",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"description": "A design system for Inglorious Web with render-function components and customizable themes",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./navigation.css": "./src/navigation/navigation.css",
|
|
36
36
|
"./surfaces.css": "./src/surfaces/surfaces.css",
|
|
37
37
|
"./tokens.css": "./src/styles/tokens.css",
|
|
38
|
+
"./safe-area.css": "./src/styles/safe-area.css",
|
|
38
39
|
"./themes/inglorious.css": "./src/styles/themes/inglorious.css",
|
|
39
40
|
"./themes/material.css": "./src/styles/themes/material.css",
|
|
40
41
|
"./themes/bootstrap.css": "./src/styles/themes/bootstrap.css",
|
|
@@ -305,10 +306,10 @@
|
|
|
305
306
|
"**/*.css"
|
|
306
307
|
],
|
|
307
308
|
"peerDependencies": {
|
|
308
|
-
"@inglorious/charts": "10.0.
|
|
309
|
+
"@inglorious/charts": "10.0.6",
|
|
309
310
|
"@inglorious/store": "11.0.3",
|
|
310
311
|
"@inglorious/utils": "5.0.0",
|
|
311
|
-
"@inglorious/web": "6.
|
|
312
|
+
"@inglorious/web": "6.6.0"
|
|
312
313
|
},
|
|
313
314
|
"devDependencies": {
|
|
314
315
|
"@fontsource/material-symbols-outlined": "^5.3.5",
|
|
@@ -317,7 +318,7 @@
|
|
|
317
318
|
"@inglorious/store": "11.0.3",
|
|
318
319
|
"@inglorious/vite-plugin-jsx": "2.0.3",
|
|
319
320
|
"@inglorious/vite-plugin-vue": "1.0.4",
|
|
320
|
-
"@inglorious/web": "6.
|
|
321
|
+
"@inglorious/web": "6.6.0",
|
|
321
322
|
"@storybook/addon-docs": "^10.6.0",
|
|
322
323
|
"@storybook/web-components-vite": "^10.6.0",
|
|
323
324
|
"happy-dom": "^20.14.0",
|
|
@@ -1,44 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import("../../../types/navigation/view-transitions").TransitionsStore} Store
|
|
3
3
|
* @typedef {import("../../../types/navigation/view-transitions").ViewTransitionType} ViewTransitionType
|
|
4
|
+
* @typedef {import("../../../types/navigation/view-transitions").TransitionsOptions} TransitionsOptions
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
/** The path the router matches against. Hashes are deliberately excluded: they
|
|
7
|
-
* change the fragment, not the page, so they must not animate one. */
|
|
8
|
-
function getPath() {
|
|
9
|
-
return window.location.pathname + window.location.search
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** The position of the current entry in the session history, stamped by the
|
|
13
|
-
* Router as it navigates. Comparing it to the previous one is what tells a push
|
|
14
|
-
* from a traversal, and in which direction. */
|
|
15
|
-
function getIndex() {
|
|
16
|
-
return window.history.state?.idx ?? 0
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function supportsViewTransitions() {
|
|
20
|
-
return typeof document.startViewTransition === "function"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** `types` landed after `startViewTransition` itself, so an engine can have the
|
|
24
|
-
* method and still throw when handed the object form of the argument. */
|
|
25
|
-
function supportsTransitionTypes() {
|
|
26
|
-
return (
|
|
27
|
-
typeof ViewTransition !== "undefined" && "types" in ViewTransition.prototype
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {number} index - The index of the entry now current.
|
|
33
|
-
* @param {number} previousIndex - The index of the entry before this render.
|
|
34
|
-
* @returns {ViewTransitionType}
|
|
35
|
-
*/
|
|
36
|
-
function getTransitionType(index, previousIndex) {
|
|
37
|
-
if (index < previousIndex) return "back"
|
|
38
|
-
if (index > previousIndex) return "forward"
|
|
39
|
-
return "replace"
|
|
40
|
-
}
|
|
41
|
-
|
|
42
7
|
/**
|
|
43
8
|
* Wraps a store so that the renders which follow a navigation are run inside a
|
|
44
9
|
* view transition, tagged with the direction they navigated in. Pair it with
|
|
@@ -57,11 +22,28 @@ function getTransitionType(index, previousIndex) {
|
|
|
57
22
|
*
|
|
58
23
|
* mount(withTransitions(store), renderApp, document.getElementById("root"))
|
|
59
24
|
*
|
|
25
|
+
* Pass `routes` to choose which navigations animate; the rest change page
|
|
26
|
+
* without a transition. A list of route names animates every navigation into
|
|
27
|
+
* or out of one of them. A function receives `{ type, from, to }`, where `type`
|
|
28
|
+
* is the direction read from the history and `from` / `to` carry the `path`,
|
|
29
|
+
* `index` and `route` of each side, and returns whether to animate.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Slide into and out of detail pages; switch tabs instantly.
|
|
33
|
+
* withTransitions(store, { routes: ["eventDetail", "userDetail"] })
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Slide only when going deeper or coming back up, never sideways.
|
|
37
|
+
* withTransitions(store, {
|
|
38
|
+
* routes: ({ from, to }) => from.path.startsWith(to.path) || to.path.startsWith(from.path),
|
|
39
|
+
* })
|
|
40
|
+
*
|
|
60
41
|
* @param {Store} store - The store to wrap.
|
|
42
|
+
* @param {TransitionsOptions} [options]
|
|
61
43
|
* @returns {Store} The same store, with a wrapped `subscribe`.
|
|
62
44
|
* @throws {Error} If the store has no `Router` type.
|
|
63
45
|
*/
|
|
64
|
-
export function withTransitions(store) {
|
|
46
|
+
export function withTransitions(store, { routes } = {}) {
|
|
65
47
|
if (!store.getType("Router")) {
|
|
66
48
|
throw new Error(
|
|
67
49
|
'[withTransitions] Requires the "Router" type from @inglorious/web. The ' +
|
|
@@ -71,38 +53,60 @@ export function withTransitions(store) {
|
|
|
71
53
|
}
|
|
72
54
|
|
|
73
55
|
const subscribe = store.subscribe
|
|
56
|
+
const shouldTransition = toPredicate(routes)
|
|
74
57
|
const listeners = new Set()
|
|
75
58
|
|
|
76
59
|
let currentPath = ""
|
|
77
60
|
let currentIndex = 0
|
|
61
|
+
let currentRoute
|
|
78
62
|
let unsubscribeStore = null
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
64
|
+
store.subscribe = (listener) => {
|
|
65
|
+
/* Taken here rather than when the store is wrapped, so that anything that
|
|
66
|
+
navigated in between — an initial redirect, a renderer mounting late — is
|
|
67
|
+
part of the baseline instead of a navigation in its own right. It also
|
|
68
|
+
keeps `window` untouched until something subscribes, so wrapping a store
|
|
69
|
+
during a server render is harmless. */
|
|
70
|
+
if (!unsubscribeStore) {
|
|
71
|
+
currentPath = getPath()
|
|
72
|
+
currentIndex = getIndex()
|
|
73
|
+
currentRoute = getRoute(store.getState())
|
|
74
|
+
unsubscribeStore = subscribe.call(store, onStateChange)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
listeners.add(listener)
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
listeners.delete(listener)
|
|
81
|
+
|
|
82
|
+
if (!listeners.size) {
|
|
83
|
+
unsubscribeStore?.()
|
|
84
|
+
unsubscribeStore = null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
function onStateChange() {
|
|
92
90
|
const path = getPath()
|
|
93
91
|
const index = getIndex()
|
|
92
|
+
const route = getRoute(store.getState())
|
|
94
93
|
/* Both are compared against the baseline before it moves forward,
|
|
95
94
|
* otherwise the comparison would always be against itself and every
|
|
96
95
|
* navigation would read as a replacement. */
|
|
97
96
|
const hasNavigated = path !== currentPath
|
|
98
|
-
const
|
|
97
|
+
const from = { path: currentPath, index: currentIndex, route: currentRoute }
|
|
98
|
+
const to = { path, index, route }
|
|
99
99
|
|
|
100
100
|
currentPath = path
|
|
101
101
|
currentIndex = index
|
|
102
|
+
currentRoute = route
|
|
102
103
|
|
|
103
104
|
if (!hasNavigated || !supportsViewTransitions()) return notify()
|
|
104
105
|
|
|
105
|
-
const
|
|
106
|
+
const type = getTransitionType(to.index, from.index)
|
|
107
|
+
if (!shouldTransition({ type, from, to })) return notify()
|
|
108
|
+
|
|
109
|
+
const types = [type]
|
|
106
110
|
|
|
107
111
|
if (supportsTransitionTypes()) {
|
|
108
112
|
document.startViewTransition({ update: notify, types })
|
|
@@ -113,29 +117,67 @@ export function withTransitions(store) {
|
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
120
|
+
/* The update callback of a view transition does not run synchronously: the
|
|
121
|
+
* browser captures the old state first and calls it on a later task. Reading
|
|
122
|
+
* the state here rather than in the subscription keeps a listener from
|
|
123
|
+
* replaying whatever the store held when the transition started. */
|
|
124
|
+
function notify() {
|
|
125
|
+
const state = store.getState()
|
|
126
|
+
// Copied, so that a listener subscribing during this pass waits for the
|
|
127
|
+
// next one instead of being notified by the pass that added it.
|
|
128
|
+
;[...listeners].forEach((listener) => listener(state))
|
|
129
|
+
}
|
|
127
130
|
|
|
128
|
-
|
|
131
|
+
return store
|
|
132
|
+
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
/** `routes` as a predicate over a navigation. Without it every navigation
|
|
135
|
+
* animates. */
|
|
136
|
+
function toPredicate(routes) {
|
|
137
|
+
if (!routes) return () => true
|
|
138
|
+
if (typeof routes === "function") return routes
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
140
|
+
const names = new Set(routes)
|
|
141
|
+
return ({ from, to }) => names.has(from.route) || names.has(to.route)
|
|
142
|
+
}
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
/** The path the router matches against. Hashes are deliberately excluded: they
|
|
145
|
+
* change the fragment, not the page, so they must not animate one. */
|
|
146
|
+
function getPath() {
|
|
147
|
+
return window.location.pathname + window.location.search
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The position of the current entry in the session history, stamped by the
|
|
151
|
+
* Router as it navigates. Comparing it to the previous one is what tells a push
|
|
152
|
+
* from a traversal, and in which direction. */
|
|
153
|
+
function getIndex() {
|
|
154
|
+
return window.history.state?.idx ?? 0
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The route the Router entity resolved, whatever the entity is called. */
|
|
158
|
+
function getRoute(state) {
|
|
159
|
+
return Object.values(state).find((entity) => entity?.type === "Router")?.route
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function supportsViewTransitions() {
|
|
163
|
+
return typeof document.startViewTransition === "function"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {number} index - The index of the entry now current.
|
|
168
|
+
* @param {number} previousIndex - The index of the entry before this render.
|
|
169
|
+
* @returns {ViewTransitionType}
|
|
170
|
+
*/
|
|
171
|
+
function getTransitionType(index, previousIndex) {
|
|
172
|
+
if (index < previousIndex) return "back"
|
|
173
|
+
if (index > previousIndex) return "forward"
|
|
174
|
+
return "replace"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** `types` landed after `startViewTransition` itself, so an engine can have the
|
|
178
|
+
* method and still throw when handed the object form of the argument. */
|
|
179
|
+
function supportsTransitionTypes() {
|
|
180
|
+
return (
|
|
181
|
+
typeof ViewTransition !== "undefined" && "types" in ViewTransition.prototype
|
|
182
|
+
)
|
|
141
183
|
}
|
package/src/styles/safe-area.css
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
|
+
/* The system bars' insets.
|
|
2
|
+
|
|
3
|
+
In a native app on Android, Capacitor's SystemBars plugin injects
|
|
4
|
+
--safe-area-inset-* with the right value in every case: 0 where it pads the
|
|
5
|
+
WebView natively (WebView < 140), the real inset where the app is
|
|
6
|
+
edge-to-edge. env() is only right on the newer WebViews, and adding it on top
|
|
7
|
+
of that native padding doubles the status bar. Everywhere else there is no
|
|
8
|
+
injected variable, and env() is the fallback.
|
|
9
|
+
|
|
10
|
+
The top inset is the exception: in a browser tab the browser's own chrome
|
|
11
|
+
always sits between the page and the status bar, so it is 0 there even when
|
|
12
|
+
the browser reports otherwise. Browsers that embed a WebView can (DuckDuckGo
|
|
13
|
+
on Android says 37px with its toolbar in the way). It only applies where the
|
|
14
|
+
page can reach the status bar: the installed PWA, and the native app, which
|
|
15
|
+
`mount` from @inglorious/web marks with `iw-native`. */
|
|
1
16
|
:root {
|
|
2
|
-
--safe-area-top:
|
|
3
|
-
--safe-area-bottom:
|
|
4
|
-
|
|
5
|
-
|
|
17
|
+
--safe-area-top: var(--safe-area-inset-top, 0px);
|
|
18
|
+
--safe-area-bottom: var(
|
|
19
|
+
--safe-area-inset-bottom,
|
|
20
|
+
env(safe-area-inset-bottom, 0px)
|
|
21
|
+
);
|
|
22
|
+
--safe-area-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
|
|
23
|
+
--safe-area-right: var(
|
|
24
|
+
--safe-area-inset-right,
|
|
25
|
+
env(safe-area-inset-right, 0px)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (display-mode: standalone), (display-mode: fullscreen) {
|
|
30
|
+
:root {
|
|
31
|
+
--safe-area-top: env(safe-area-inset-top, 0px);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:root.iw-native {
|
|
36
|
+
--safe-area-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
|
|
6
37
|
}
|
|
7
38
|
|
|
8
39
|
.pt-safe {
|
|
@@ -10,8 +10,33 @@ export interface TransitionsStore<S = unknown> {
|
|
|
10
10
|
getType(name: string): unknown
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** One side of a navigation. */
|
|
14
|
+
export interface NavigationSide {
|
|
15
|
+
path: string
|
|
16
|
+
/** The history index the Router stamped on the entry. */
|
|
17
|
+
index: number
|
|
18
|
+
/** The route the Router resolved, if any. */
|
|
19
|
+
route?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Navigation {
|
|
23
|
+
/** The direction read from the history. */
|
|
24
|
+
type: ViewTransitionType
|
|
25
|
+
from: NavigationSide
|
|
26
|
+
to: NavigationSide
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TransitionsOptions {
|
|
30
|
+
/** Which navigations animate: those into or out of the named routes, or
|
|
31
|
+
* those the function accepts. Omit it to animate every navigation. */
|
|
32
|
+
routes?: string[] | ((navigation: Navigation) => boolean)
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
export interface WithTransitions {
|
|
14
|
-
<S>(
|
|
36
|
+
<S>(
|
|
37
|
+
store: TransitionsStore<S>,
|
|
38
|
+
options?: TransitionsOptions,
|
|
39
|
+
): TransitionsStore<S>
|
|
15
40
|
}
|
|
16
41
|
|
|
17
42
|
export declare const withTransitions: WithTransitions
|