@jsenv/navi 0.29.183 → 0.29.184
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.
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { getVirtualKeyboardOverlayHeight, subscribeWindowResizeSettled, subscribeVisualViewportResizeSettled, setVirtualKeyboardOverlaysContent, setPlacementViewportInsets } from "@jsenv/dom";
|
|
2
|
+
import { signal, computed, effect } from "@preact/signals";
|
|
3
|
+
|
|
4
|
+
const installImportMetaCssBuild = (importMeta) => {
|
|
5
|
+
const IMPORT_META_CSS_BUILD = "jsenv_import_meta_css_build";
|
|
6
|
+
|
|
7
|
+
if (importMeta.css === IMPORT_META_CSS_BUILD) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const stylesheetMap = new Map();
|
|
12
|
+
const adopt = (url, value) => {
|
|
13
|
+
const stylesheet = new CSSStyleSheet({ baseUrl: importMeta.url });
|
|
14
|
+
stylesheet.replaceSync(value);
|
|
15
|
+
stylesheetMap.set(url, stylesheet);
|
|
16
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
|
|
17
|
+
};
|
|
18
|
+
const update = (url, value) => {
|
|
19
|
+
stylesheetMap.get(url).replaceSync(value);
|
|
20
|
+
};
|
|
21
|
+
const remove = (url) => {
|
|
22
|
+
const stylesheet = stylesheetMap.get(url);
|
|
23
|
+
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
|
|
24
|
+
(s) => s !== stylesheet,
|
|
25
|
+
);
|
|
26
|
+
stylesheetMap.delete(url);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const currentCssSourceMap = new Map();
|
|
30
|
+
Object.defineProperty(importMeta, "css", {
|
|
31
|
+
configurable: true,
|
|
32
|
+
get() {
|
|
33
|
+
return IMPORT_META_CSS_BUILD;
|
|
34
|
+
},
|
|
35
|
+
set([value, url]) {
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
if (stylesheetMap.has(url)) {
|
|
38
|
+
remove(url);
|
|
39
|
+
currentCssSourceMap.delete(url);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!stylesheetMap.has(url)) {
|
|
44
|
+
adopt(url, value);
|
|
45
|
+
currentCssSourceMap.set(url, value);
|
|
46
|
+
} else if (currentCssSourceMap.get(url) !== value) {
|
|
47
|
+
update(url, value);
|
|
48
|
+
currentCssSourceMap.set(url, value);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const windowWidthSignal = signal(window.innerWidth);
|
|
55
|
+
const windowHeightSignal = signal(window.innerHeight);
|
|
56
|
+
|
|
57
|
+
// Debounced (not a raw "resize" listener) — see window_size.js's own
|
|
58
|
+
// module comment: mobile fires a transient "resize" when the browser's own
|
|
59
|
+
// UI chrome (address bar, etc.) briefly shows/hides, and this needs to
|
|
60
|
+
// settle on the exact same tick as visualViewport's own debounced resize
|
|
61
|
+
// below and Popover/Dialog's own repositioning, or one flickers a moment
|
|
62
|
+
// out of sync with the others.
|
|
63
|
+
subscribeWindowResizeSettled(() => {
|
|
64
|
+
windowWidthSignal.value = window.innerWidth;
|
|
65
|
+
windowHeightSignal.value = window.innerHeight;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Visual viewport dimensions — update when the virtual keyboard opens/closes or
|
|
69
|
+
// when the browser UI (address bar) shows/hides.
|
|
70
|
+
// When visualViewport is not available, derived from window signals so they
|
|
71
|
+
// stay live without any extra listeners.
|
|
72
|
+
const vv = window.visualViewport;
|
|
73
|
+
const visualViewportWidthSignal = vv
|
|
74
|
+
? signal(vv.width)
|
|
75
|
+
: computed(() => windowWidthSignal.value);
|
|
76
|
+
const visualViewportHeightSignal = vv
|
|
77
|
+
? signal(vv.height)
|
|
78
|
+
: computed(() => windowHeightSignal.value);
|
|
79
|
+
|
|
80
|
+
if (vv) {
|
|
81
|
+
const update = () => {
|
|
82
|
+
visualViewportWidthSignal.value = vv.width;
|
|
83
|
+
visualViewportHeightSignal.value = vv.height;
|
|
84
|
+
};
|
|
85
|
+
subscribeVisualViewportResizeSettled(update);
|
|
86
|
+
vv.addEventListener("scroll", update);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The app's own screen — the visual viewport, unless the app declared a
|
|
90
|
+
// narrower one with --navi-app-max-width/--navi-app-height (see
|
|
91
|
+
// navi_css_vars.js, which derives --navi-app-width/--navi-app-height from
|
|
92
|
+
// these in CSS). Anything escaping normal flow is sized against this rather
|
|
93
|
+
// than the viewport: an app that simulates a handheld screen keeps that width
|
|
94
|
+
// even for what paints on top of it.
|
|
95
|
+
//
|
|
96
|
+
// The declaration stays in CSS and is read back from there rather than handed
|
|
97
|
+
// to navi a second time in JS — a JS copy would be the one that goes stale.
|
|
98
|
+
// Read on the spot rather than cached in a signal: the only caller is a popup
|
|
99
|
+
// resolving its own margin as it places itself, which already reads far more
|
|
100
|
+
// of the DOM than this, and nothing then has to be invalidated when the value
|
|
101
|
+
// changes.
|
|
102
|
+
const unresolvableWarned = new Set();
|
|
103
|
+
const readAppMax = (propertyName) => {
|
|
104
|
+
const declared = getComputedStyle(document.documentElement)
|
|
105
|
+
.getPropertyValue(propertyName)
|
|
106
|
+
.trim();
|
|
107
|
+
if (!declared) {
|
|
108
|
+
return Infinity;
|
|
109
|
+
}
|
|
110
|
+
// A custom property computes to a token stream, not to a length: "40rem"
|
|
111
|
+
// arrives here as the string "40rem", and parseFloat would read it as 40
|
|
112
|
+
// pixels. Only px is accepted — an app declaring the screen it simulates has
|
|
113
|
+
// a pixel number to give, and resolving arbitrary lengths would mean laying
|
|
114
|
+
// out a probe element on every read.
|
|
115
|
+
const inPixels = /^([0-9.]+)px$/.exec(declared);
|
|
116
|
+
if (!inPixels) {
|
|
117
|
+
if (!unresolvableWarned.has(propertyName)) {
|
|
118
|
+
unresolvableWarned.add(propertyName);
|
|
119
|
+
// Not silently wrong, just partially applied: CSS still caps the popup's
|
|
120
|
+
// size with the declared length, only the margin it keeps with the edges
|
|
121
|
+
// falls back to a share of the viewport (the pre-token behavior).
|
|
122
|
+
console.warn(
|
|
123
|
+
`${propertyName}="${declared}" must be a length in pixels ("600px"). Until then popups keep viewport-sized margins.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return Infinity;
|
|
127
|
+
}
|
|
128
|
+
return parseFloat(inPixels[1]);
|
|
129
|
+
};
|
|
130
|
+
const getAppWidth = () =>
|
|
131
|
+
Math.min(visualViewportWidthSignal.value, readAppMax("--navi-app-max-width"));
|
|
132
|
+
// The JS reading of --navi-app-inset-* (see safe_area.js): the centered bands
|
|
133
|
+
// between the window's edges and the app's own rectangle. Handed to
|
|
134
|
+
// @jsenv/dom (setPlacementViewportInsets, wired in navi_css_vars.js) so
|
|
135
|
+
// placement keeps to the same rectangle the CSS size caps describe. The
|
|
136
|
+
// keyboard is deliberately absent, unlike in the CSS twin: the placement
|
|
137
|
+
// viewport already subtracts the keyboard overlay itself (see
|
|
138
|
+
// getVisibleViewportRect in @jsenv/dom's visible_rect.js), so carrying it
|
|
139
|
+
// here too would count it twice.
|
|
140
|
+
const getAppInsets = () => {
|
|
141
|
+
const vvWidth = visualViewportWidthSignal.value;
|
|
142
|
+
const vvHeight = visualViewportHeightSignal.value;
|
|
143
|
+
const appMaxWidth = readAppMax("--navi-app-max-width");
|
|
144
|
+
const appMaxHeight = readAppMax("--navi-app-max-height");
|
|
145
|
+
const bandX = appMaxWidth < vvWidth ? (vvWidth - appMaxWidth) / 2 : 0;
|
|
146
|
+
const bandY = appMaxHeight < vvHeight ? (vvHeight - appMaxHeight) / 2 : 0;
|
|
147
|
+
return { left: bandX, top: bandY, right: bandX, bottom: bandY };
|
|
148
|
+
};
|
|
149
|
+
// Minus what the keyboard covers, so this stays the JS reading of the very
|
|
150
|
+
// same rectangle --navi-app-height describes in CSS (see safe_area.js's own
|
|
151
|
+
// --navi-keyboard-inset-bottom). Zero unless the app opted into the keyboard
|
|
152
|
+
// overlaying its content — otherwise the shrinking visual viewport above has
|
|
153
|
+
// already accounted for it, and subtracting again would count it twice.
|
|
154
|
+
const getAppHeight = () =>
|
|
155
|
+
Math.max(
|
|
156
|
+
0,
|
|
157
|
+
Math.min(
|
|
158
|
+
visualViewportHeightSignal.value,
|
|
159
|
+
readAppMax("--navi-app-max-height"),
|
|
160
|
+
) - getVirtualKeyboardOverlayHeight(),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Whether the primary input is a finger rather than a mouse. A pointer type is
|
|
164
|
+
// not a size: a narrow desktop window is still a mouse, and a large tablet is
|
|
165
|
+
// still a finger — so anything sized for the on-screen keyboard must key off
|
|
166
|
+
// this, never off windowWidthSignal. Thumb reach needs both, which is what
|
|
167
|
+
// smallTouchScreenSignal below answers.
|
|
168
|
+
const coarsePointerQuery = window.matchMedia
|
|
169
|
+
? window.matchMedia("(pointer: coarse)")
|
|
170
|
+
: null;
|
|
171
|
+
const coarsePointerSignal = signal(
|
|
172
|
+
coarsePointerQuery ? coarsePointerQuery.matches : false,
|
|
173
|
+
);
|
|
174
|
+
if (coarsePointerQuery) {
|
|
175
|
+
coarsePointerQuery.addEventListener("change", () => {
|
|
176
|
+
coarsePointerSignal.value = coarsePointerQuery.matches;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Whether the screen is one a bottom sheet actually suits: a finger *and* a
|
|
181
|
+
// screen whose bottom edge stays where the thumb already is. Touch alone is not
|
|
182
|
+
// enough — a tall touch screen (a tablet, a kiosk panel) docks a sheet a whole
|
|
183
|
+
// screen away from where the finger just tapped, which is worse than the
|
|
184
|
+
// centered box it replaced.
|
|
185
|
+
//
|
|
186
|
+
// A phone is recognized by its SHAPE, not by a box of maximum dimensions: what
|
|
187
|
+
// makes the bottom edge reachable is holding a narrow slab, and phones keep
|
|
188
|
+
// growing along their long side (20:9, 21:9) while staying just as narrow. So
|
|
189
|
+
// each orientation is answered on the short side plus the elongation:
|
|
190
|
+
// - upright: narrow enough to be held in one hand, and taller than it is wide.
|
|
191
|
+
// Its height is deliberately unbounded — a very tall narrow screen is the
|
|
192
|
+
// case a bottom sheet is most for, not the case to exclude.
|
|
193
|
+
// - on its side: short enough that the bottom edge is a thumb away whatever the
|
|
194
|
+
// width, and wider than it is tall.
|
|
195
|
+
// A tablet fails both: it is too wide upright, and too tall on its side — the
|
|
196
|
+
// smallest one already starts around 740 CSS px on its short side, and the
|
|
197
|
+
// bound below leaves that gap deliberately wide rather than cutting close to
|
|
198
|
+
// the largest phone.
|
|
199
|
+
//
|
|
200
|
+
// Read off window, not visualViewport: the virtual keyboard shrinks the visual
|
|
201
|
+
// viewport while the user types, and a dialog must not undock mid-interaction
|
|
202
|
+
// because a keyboard opened under it.
|
|
203
|
+
const HANDHELD_MAX_SHORT_SIDE = 600;
|
|
204
|
+
// Enough elongation to tell a slab from a square-ish panel; a phone is well
|
|
205
|
+
// past it (1.7 and up) in either orientation.
|
|
206
|
+
const HANDHELD_MIN_RATIO = 1.2;
|
|
207
|
+
const smallTouchScreenSignal = computed(() => {
|
|
208
|
+
if (!coarsePointerSignal.value) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const width = windowWidthSignal.value;
|
|
212
|
+
const height = windowHeightSignal.value;
|
|
213
|
+
if (width <= HANDHELD_MAX_SHORT_SIDE) {
|
|
214
|
+
return height >= width * HANDHELD_MIN_RATIO;
|
|
215
|
+
}
|
|
216
|
+
if (height <= HANDHELD_MAX_SHORT_SIDE) {
|
|
217
|
+
return width >= height * HANDHELD_MIN_RATIO;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
installImportMetaCssBuild(import.meta);/**
|
|
223
|
+
* The part of the window an app actually has, in two levels.
|
|
224
|
+
*
|
|
225
|
+
* Two, and not one, for a reason worth stating up front: a fixed bar is one of
|
|
226
|
+
* the things that reduce the free region, so it cannot ALSO be placed against
|
|
227
|
+
* that region — it would push itself off the edge it is pinned to. What is
|
|
228
|
+
* anchored and what is anchored-inside are two different rectangles.
|
|
229
|
+
*
|
|
230
|
+
* 1. `--navi-app-inset-{top,right,bottom,left}` — from the window's edges to
|
|
231
|
+
* the app's own rectangle. Whatever is pinned to an edge (a fixed bar, a
|
|
232
|
+
* side panel, a popup aimed at a corner) is pinned to THAT, so an app
|
|
233
|
+
* pretending to be a 600px handheld inside a 1500px window stays one
|
|
234
|
+
* rectangle instead of a column with its furniture spread across the glass.
|
|
235
|
+
*
|
|
236
|
+
* 2. `--navi-safe-area-inset-{top,right,bottom,left}` — from the window's edges
|
|
237
|
+
* to the band left free INSIDE that rectangle. Whatever flows, scrolls, or
|
|
238
|
+
* gets painted keeps to it.
|
|
239
|
+
*
|
|
240
|
+
* Level 2 is a sum, and the contract for taking part in it is only "publish
|
|
241
|
+
* what you take on one edge": the device's own notch (`env(safe-area-inset-*)`,
|
|
242
|
+
* which is the browser's version of this very idea), the fixed bars
|
|
243
|
+
* (fixed_bar_space.js), and anything an app adds. That is the point of naming
|
|
244
|
+
* it at all — a component that must stay clear of what covers the screen reads
|
|
245
|
+
* ONE set of numbers, and never has to learn what is covering it.
|
|
246
|
+
*
|
|
247
|
+
* `max()` between the notch and the bars rather than a sum: a bar pinned to an
|
|
248
|
+
* edge already reaches under the notch and counts it in its own size (see
|
|
249
|
+
* fixed_bar.jsx), so adding both would reserve it twice.
|
|
250
|
+
*
|
|
251
|
+
* JS placement answers to the level-1 rectangle too: getAppInsets
|
|
252
|
+
* (layout/responsive.js) is its reading of these same bands, handed to
|
|
253
|
+
* pickPositionRelativeTo via setPlacementViewportInsets (see
|
|
254
|
+
* navi_css_vars.js).
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
const SAFE_AREA_CSS = /* css */`@property --navi-safe-area-inset-top {
|
|
258
|
+
syntax: "<length>";
|
|
259
|
+
inherits: true;
|
|
260
|
+
initial-value: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@property --navi-safe-area-inset-right {
|
|
264
|
+
syntax: "<length>";
|
|
265
|
+
inherits: true;
|
|
266
|
+
initial-value: 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@property --navi-safe-area-inset-bottom {
|
|
270
|
+
syntax: "<length>";
|
|
271
|
+
inherits: true;
|
|
272
|
+
initial-value: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@property --navi-safe-area-inset-left {
|
|
276
|
+
syntax: "<length>";
|
|
277
|
+
inherits: true;
|
|
278
|
+
initial-value: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@layer navi {
|
|
282
|
+
:root {
|
|
283
|
+
--navi-fixed-bar-space-top: 0px;
|
|
284
|
+
--navi-fixed-bar-space-right: 0px;
|
|
285
|
+
--navi-fixed-bar-space-bottom: 0px;
|
|
286
|
+
--navi-fixed-bar-space-left: 0px;
|
|
287
|
+
--navi-keyboard-inset-bottom: env(keyboard-inset-height, 0px);
|
|
288
|
+
--navi-app-inset-top: max(0px,
|
|
289
|
+
(var(--navi-vvh) - var(--navi-app-max-height, var(--navi-vvh))) / 2);
|
|
290
|
+
--navi-app-inset-bottom: calc(var(--navi-app-inset-top) + var(--navi-keyboard-inset-bottom));
|
|
291
|
+
--navi-app-inset-left: max(0px,
|
|
292
|
+
(var(--navi-vvw) - var(--navi-app-max-width, var(--navi-vvw))) / 2);
|
|
293
|
+
--navi-app-inset-right: var(--navi-app-inset-left);
|
|
294
|
+
--navi-safe-area-inset-top: calc(var(--navi-app-inset-top) +
|
|
295
|
+
max(env(safe-area-inset-top), var(--navi-fixed-bar-space-top)));
|
|
296
|
+
--navi-safe-area-inset-right: calc(var(--navi-app-inset-right) +
|
|
297
|
+
max(env(safe-area-inset-right), var(--navi-fixed-bar-space-right)));
|
|
298
|
+
--navi-safe-area-inset-bottom: calc(var(--navi-app-inset-bottom) +
|
|
299
|
+
max(env(safe-area-inset-bottom), var(--navi-fixed-bar-space-bottom)));
|
|
300
|
+
--navi-safe-area-inset-left: calc(var(--navi-app-inset-left) +
|
|
301
|
+
max(env(safe-area-inset-left), var(--navi-fixed-bar-space-left)));
|
|
302
|
+
scroll-padding-top: var(--navi-safe-area-inset-top);
|
|
303
|
+
scroll-padding-right: var(--navi-safe-area-inset-right);
|
|
304
|
+
scroll-padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
305
|
+
scroll-padding-left: var(--navi-safe-area-inset-left);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
[data-navi-safe-area] {
|
|
309
|
+
padding-top: var(--navi-safe-area-inset-top);
|
|
310
|
+
padding-right: var(--navi-safe-area-inset-right);
|
|
311
|
+
padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
312
|
+
padding-left: var(--navi-safe-area-inset-left);
|
|
313
|
+
scroll-padding-top: var(--navi-safe-area-inset-top);
|
|
314
|
+
scroll-padding-right: var(--navi-safe-area-inset-right);
|
|
315
|
+
scroll-padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
316
|
+
scroll-padding-left: var(--navi-safe-area-inset-left);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
import.meta.css = [SAFE_AREA_CSS, "@jsenv/navi/src/layout/safe_area.js"];
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* navi's stance on the on-screen keyboard: it overlays the app rather than
|
|
324
|
+
* resizing the viewport, wherever the browser can be told so (the
|
|
325
|
+
* VirtualKeyboard API — Chromium only). See virtual_keyboard.js in @jsenv/dom
|
|
326
|
+
* for what that trades away and what it gives back.
|
|
327
|
+
*
|
|
328
|
+
* Turned on rather than offered, because navi already sizes everything that
|
|
329
|
+
* escapes normal flow against the app's own rectangle rather than against the
|
|
330
|
+
* window (--navi-app-width/height, see navi_css_vars.js), and
|
|
331
|
+
* --navi-keyboard-inset-bottom (safe_area.js) puts the keyboard into exactly
|
|
332
|
+
* that rectangle. So the two mechanisms reach the same numbers here, and the
|
|
333
|
+
* overlay reaches them without reflowing the page underneath — a resizing
|
|
334
|
+
* viewport is a resize of everything, fired transiently every time focus goes
|
|
335
|
+
* from one input to the next.
|
|
336
|
+
*
|
|
337
|
+
* An app that built its own layout around the viewport shrinking can say so
|
|
338
|
+
* with disableVirtualKeyboardOverlay(), and gets the behavior Firefox and
|
|
339
|
+
* Safari give it anyway.
|
|
340
|
+
*
|
|
341
|
+
* The one thing it hands back to the app: scrolling the focused field into
|
|
342
|
+
* view. A viewport that shrinks makes the browser do it; a keyboard that
|
|
343
|
+
* merely paints over the page leaves whatever is under it under it. navi
|
|
344
|
+
* answers that for what it places itself — a popup is sized and positioned
|
|
345
|
+
* against the app rectangle the keyboard was just subtracted from — and for
|
|
346
|
+
* anything marked [data-navi-safe-area], whose scroll-padding-bottom counts
|
|
347
|
+
* the keyboard in (safe_area.js). A field in a scroller the app never marked
|
|
348
|
+
* is the app's own to handle.
|
|
349
|
+
*/
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
setVirtualKeyboardOverlaysContent(true);
|
|
353
|
+
|
|
354
|
+
const disableVirtualKeyboardOverlay = () => {
|
|
355
|
+
setVirtualKeyboardOverlaysContent(false);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
installImportMetaCssBuild(import.meta);/**
|
|
359
|
+
* Regroup CSS vars that makes sense to share across all navi components.
|
|
360
|
+
*/
|
|
361
|
+
const button = document.createElement("button");
|
|
362
|
+
button.style.display = "none";
|
|
363
|
+
document.body.appendChild(button);
|
|
364
|
+
const computedStyle = getComputedStyle(button);
|
|
365
|
+
const controlDefaultFontFamily = computedStyle.fontFamily;
|
|
366
|
+
const controlDefaultFontSize = computedStyle.fontSize;
|
|
367
|
+
document.body.removeChild(button);
|
|
368
|
+
const css = /* css */`@layer navi {
|
|
369
|
+
:root {
|
|
370
|
+
--navi-vvw: 100dvw;
|
|
371
|
+
--navi-vvh: 100dvh;
|
|
372
|
+
--navi-app-width: calc(var(--navi-vvw) - var(--navi-app-inset-left) - var(--navi-app-inset-right));
|
|
373
|
+
--navi-app-height: calc(var(--navi-vvh) - var(--navi-app-inset-top) - var(--navi-app-inset-bottom));
|
|
374
|
+
--navi-focus-outline-width: 2px;
|
|
375
|
+
--navi-focus-outline-color: light-dark(#4476ff, #3b82f6);
|
|
376
|
+
--navi-loader-color: light-dark(#355fcc, #3b82f6);
|
|
377
|
+
-webkit-tap-highlight-color: var(--navi-tap-highlight-color);
|
|
378
|
+
--navi-tap-highlight-color: transparent;
|
|
379
|
+
--navi-control-font-size: ${controlDefaultFontSize};
|
|
380
|
+
--navi-control-border-radius: 2px;
|
|
381
|
+
--navi-checkbox-border-radius: min(var(--navi-control-border-radius),
|
|
382
|
+
.25em);
|
|
383
|
+
--navi-control-accent-color: light-dark(#1875ff, #3b82f6);
|
|
384
|
+
--navi-control-border-width: 1px;
|
|
385
|
+
--navi-control-border-color: light-dark(#767676, #8e8e93);
|
|
386
|
+
--navi-control-padding-x-default: 2px;
|
|
387
|
+
--navi-control-padding-y-default: 1px;
|
|
388
|
+
--navi-button-padding-x-default: 6px;
|
|
389
|
+
--navi-button-padding-y-default: 1px;
|
|
390
|
+
--navi-button-background-color: light-dark(#f3f4f6, #2d3748);
|
|
391
|
+
--navi-callout-success-color: #4caf50;
|
|
392
|
+
--navi-callout-info-color: #2196f3;
|
|
393
|
+
--navi-callout-warning-color: #ff9800;
|
|
394
|
+
--navi-callout-error-color: #f44336;
|
|
395
|
+
--navi-callout-neutral-color: var(--navi-control-border-color);
|
|
396
|
+
--navi-list-item-padding-x-default: 4px;
|
|
397
|
+
--navi-list-item-padding-y-default: 1px;
|
|
398
|
+
--navi-picker-padding-x-default: var(--navi-control-padding-x-default);
|
|
399
|
+
--navi-picker-padding-y-default: var(--navi-control-padding-y-default);
|
|
400
|
+
--navi-popup-border-radius: 8px;
|
|
401
|
+
--navi-popup-border-color: light-dark(#d0d0d0, #3b3b3b);
|
|
402
|
+
--navi-popup-box-shadow: 0 4px 8px #00000014, 0 12px 40px #00000038;
|
|
403
|
+
--navi-popup-background-color: var(--navi-surface-color);
|
|
404
|
+
--navi-popup-color: var(--navi-surface-text-color);
|
|
405
|
+
--navi-backdrop-close-background: #00000014;
|
|
406
|
+
--navi-backdrop-discrete-background: #00000005;
|
|
407
|
+
--navi-backdrop-capture-background: #ffffff14;
|
|
408
|
+
--navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);
|
|
409
|
+
--navi-link-color: #00e;
|
|
410
|
+
--navi-link-color-pressed: red;
|
|
411
|
+
--navi-link-current-indicator-color: #cd3425;
|
|
412
|
+
--navi-selection-border-color: #0078d4;
|
|
413
|
+
--navi-selection-background-color: #eaf1fd;
|
|
414
|
+
--navi-accent-color: #031e3c;
|
|
415
|
+
--navi-surface-color: light-dark(#fff, #1c1c1e);
|
|
416
|
+
--navi-surface-text-color: CanvasText;
|
|
417
|
+
--navi-separator-color-default: #d1d9e0;
|
|
418
|
+
--navi-color-white: white;
|
|
419
|
+
--navi-color-dark: #373c45;
|
|
420
|
+
--navi-info-color-light: #eaf6fc;
|
|
421
|
+
--navi-info-color: #376cc2;
|
|
422
|
+
--navi-success-color-light: #ecf9ef;
|
|
423
|
+
--navi-success-color: #50c464;
|
|
424
|
+
--navi-warning-color-light: #fdf6e3;
|
|
425
|
+
--navi-warning-color: #f19c05;
|
|
426
|
+
--navi-error-color-light: #fcebed;
|
|
427
|
+
--navi-error-color: #eb364b;
|
|
428
|
+
--navi-xxs: .125em;
|
|
429
|
+
--navi-xs: .25em;
|
|
430
|
+
--navi-s: .5em;
|
|
431
|
+
--navi-m: 1em;
|
|
432
|
+
--navi-l: 1.5em;
|
|
433
|
+
--navi-xl: 2em;
|
|
434
|
+
--navi-xxl: 3em;
|
|
435
|
+
--navi-typo-xxs: .625rem;
|
|
436
|
+
--navi-typo-xs: .75rem;
|
|
437
|
+
--navi-typo-s: .875rem;
|
|
438
|
+
--navi-typo-m: 1rem;
|
|
439
|
+
--navi-typo-l: 1.125rem;
|
|
440
|
+
--navi-typo-xl: 1.25rem;
|
|
441
|
+
--navi-typo-xxl: 1.5rem;
|
|
442
|
+
--navi-line-height: 1.25;
|
|
443
|
+
font-family: ${controlDefaultFontFamily};
|
|
444
|
+
line-height: var(--navi-line-height);
|
|
445
|
+
--navi-control-line-height: round(calc(var(--navi-line-height) * 1em),
|
|
446
|
+
1px);
|
|
447
|
+
--navi-color-secondary-mix: 80%;
|
|
448
|
+
--navi-color-emphasis-mix: 50%;
|
|
449
|
+
--navi-color-discrete-mix: 60%;
|
|
450
|
+
--navi-color-hint-mix: 25%;
|
|
451
|
+
--navi-color-primary: var(--navi-surface-text-color);
|
|
452
|
+
--navi-placeholder-color: var(--navi-color-discrete);
|
|
453
|
+
--navi-placeholder-font-style: normal;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.navi_popover, .navi_dialog {
|
|
457
|
+
--navi-color-primary: var(--navi-popup-color);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.navi_callout {
|
|
461
|
+
--navi-color-primary: CanvasText;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
:root, .navi_popover, .navi_dialog, .navi_callout {
|
|
465
|
+
--navi-color-secondary: color-mix(in srgb,
|
|
466
|
+
currentColor var(--navi-color-secondary-mix),
|
|
467
|
+
transparent);
|
|
468
|
+
--navi-color-emphasis: color-mix(in srgb,
|
|
469
|
+
currentColor var(--navi-color-emphasis-mix),
|
|
470
|
+
black);
|
|
471
|
+
--navi-color-discrete: color-mix(in srgb,
|
|
472
|
+
currentColor var(--navi-color-discrete-mix),
|
|
473
|
+
transparent);
|
|
474
|
+
--navi-color-hint: color-mix(in srgb,
|
|
475
|
+
currentColor var(--navi-color-hint-mix),
|
|
476
|
+
transparent);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
input[navi-visually-hidden], button[navi-visually-hidden], div[navi-visually-hidden] {
|
|
481
|
+
z-index: -1;
|
|
482
|
+
white-space: nowrap;
|
|
483
|
+
clip-path: inset(50%);
|
|
484
|
+
appearance: none;
|
|
485
|
+
border: 0;
|
|
486
|
+
width: 100%;
|
|
487
|
+
height: 100%;
|
|
488
|
+
margin: 0;
|
|
489
|
+
padding: 0;
|
|
490
|
+
position: absolute;
|
|
491
|
+
top: 0;
|
|
492
|
+
left: 0;
|
|
493
|
+
overflow: hidden;
|
|
494
|
+
|
|
495
|
+
&[navi-debug] {
|
|
496
|
+
clip-path: none;
|
|
497
|
+
appearance: auto;
|
|
498
|
+
width: auto;
|
|
499
|
+
height: auto;
|
|
500
|
+
margin: 0;
|
|
501
|
+
position: static;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
import.meta.css = [css, "@jsenv/navi/src/navi_css_vars.js"];
|
|
506
|
+
effect(() => {
|
|
507
|
+
document.documentElement.style.setProperty("--navi-vvw", `${visualViewportWidthSignal.value}px`);
|
|
508
|
+
document.documentElement.style.setProperty("--navi-vvh", `${visualViewportHeightSignal.value}px`);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Placement follows the same rectangle the size caps above describe: the JS
|
|
512
|
+
// that positions popups (pickPositionRelativeTo in @jsenv/dom) computes
|
|
513
|
+
// against the visual viewport narrowed by the app's own bands, so a dialog
|
|
514
|
+
// centers on the app column and a side panel docks flush against the app's
|
|
515
|
+
// edge rather than the window's. Here rather than in each component, for the
|
|
516
|
+
// same reason as virtual_keyboard.js above: one decision about the whole
|
|
517
|
+
// window.
|
|
518
|
+
setPlacementViewportInsets(getAppInsets);
|
|
519
|
+
|
|
520
|
+
export { coarsePointerSignal, disableVirtualKeyboardOverlay, getAppHeight, getAppWidth, installImportMetaCssBuild, smallTouchScreenSignal, visualViewportHeightSignal, visualViewportWidthSignal, windowHeightSignal, windowWidthSignal };
|
|
521
|
+
//# sourceMappingURL=jsenv_navi_side_effects.js.map
|