@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