@jsenv/navi 0.29.142 → 0.29.144
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/dist/jsenv_navi.js +6376 -9151
- package/dist/jsenv_navi.js.map +44 -52
- package/dist/jsenv_navi_side_effects.js +129 -491
- package/dist/jsenv_navi_side_effects.js.map +2 -4
- package/docs/AI_INSTRUCTIONS.md +20 -3
- package/docs/actions.md +44 -4
- package/docs/control_value.md +7 -1
- package/docs/css_architecture.md +219 -1
- package/docs/navigation.md +122 -35
- package/docs/popup_open.md +117 -17
- package/docs/state_binding.md +142 -0
- package/package.json +2 -2
|
@@ -202,170 +202,6 @@ const smallTouchScreenSignal = computed(() => {
|
|
|
202
202
|
return false;
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
-
/**
|
|
206
|
-
* The part of the window an app actually has, in two levels.
|
|
207
|
-
*
|
|
208
|
-
* Two, and not one, for a reason worth stating up front: a fixed bar is one of
|
|
209
|
-
* the things that reduce the free region, so it cannot ALSO be placed against
|
|
210
|
-
* that region — it would push itself off the edge it is pinned to. What is
|
|
211
|
-
* anchored and what is anchored-inside are two different rectangles.
|
|
212
|
-
*
|
|
213
|
-
* 1. `--navi-app-inset-{top,right,bottom,left}` — from the window's edges to
|
|
214
|
-
* the app's own rectangle. Whatever is pinned to an edge (a fixed bar, a
|
|
215
|
-
* side panel, a popup aimed at a corner) is pinned to THAT, so an app
|
|
216
|
-
* pretending to be a 600px handheld inside a 1500px window stays one
|
|
217
|
-
* rectangle instead of a column with its furniture spread across the glass.
|
|
218
|
-
*
|
|
219
|
-
* 2. `--navi-safe-area-inset-{top,right,bottom,left}` — from the window's edges
|
|
220
|
-
* to the band left free INSIDE that rectangle. Whatever flows, scrolls, or
|
|
221
|
-
* gets painted keeps to it.
|
|
222
|
-
*
|
|
223
|
-
* Level 2 is a sum, and the contract for taking part in it is only "publish
|
|
224
|
-
* what you take on one edge": the device's own notch (`env(safe-area-inset-*)`,
|
|
225
|
-
* which is the browser's version of this very idea), the fixed bars
|
|
226
|
-
* (fixed_bar_space.js), and anything an app adds. That is the point of naming
|
|
227
|
-
* it at all — a component that must stay clear of what covers the screen reads
|
|
228
|
-
* ONE set of numbers, and never has to learn what is covering it.
|
|
229
|
-
*
|
|
230
|
-
* `max()` between the notch and the bars rather than a sum: a bar pinned to an
|
|
231
|
-
* edge already reaches under the notch and counts it in its own size (see
|
|
232
|
-
* fixed_bar.jsx), so adding both would reserve it twice.
|
|
233
|
-
*
|
|
234
|
-
* Sizes only, not placement, for the time being — see "Current limitations" in
|
|
235
|
-
* docs/css_architecture.md.
|
|
236
|
-
*/
|
|
237
|
-
|
|
238
|
-
const SAFE_AREA_CSS = /* css */ `
|
|
239
|
-
/* Declared as lengths so that they COMPUTE to one: an unregistered custom
|
|
240
|
-
property keeps the calc() it was written as, and the sum below is then a
|
|
241
|
-
string no one can read back. Reading it off the computed style — which a
|
|
242
|
-
route transition does, to keep the band the page being left had (see
|
|
243
|
-
nav/transition_window.js) — only works for a registered property. */
|
|
244
|
-
@property --navi-safe-area-inset-top {
|
|
245
|
-
syntax: "<length>";
|
|
246
|
-
inherits: true;
|
|
247
|
-
initial-value: 0px;
|
|
248
|
-
}
|
|
249
|
-
@property --navi-safe-area-inset-right {
|
|
250
|
-
syntax: "<length>";
|
|
251
|
-
inherits: true;
|
|
252
|
-
initial-value: 0px;
|
|
253
|
-
}
|
|
254
|
-
@property --navi-safe-area-inset-bottom {
|
|
255
|
-
syntax: "<length>";
|
|
256
|
-
inherits: true;
|
|
257
|
-
initial-value: 0px;
|
|
258
|
-
}
|
|
259
|
-
@property --navi-safe-area-inset-left {
|
|
260
|
-
syntax: "<length>";
|
|
261
|
-
inherits: true;
|
|
262
|
-
initial-value: 0px;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
@layer navi {
|
|
266
|
-
/* Layered whole, rules included: the two rules below are offers, not
|
|
267
|
-
structure. [data-navi-safe-area] is an attribute the app puts on its own
|
|
268
|
-
scroller, so the app's own padding on that element has to win over what
|
|
269
|
-
navi suggests for it. */
|
|
270
|
-
:root {
|
|
271
|
-
/* The room each kind of furniture takes, declared here at zero and
|
|
272
|
-
written by whoever takes it. A slot rather than a value: the sum below
|
|
273
|
-
has to be readable whether or not the app ever mounts a fixed bar. */
|
|
274
|
-
--navi-fixed-bar-space-top: 0px;
|
|
275
|
-
--navi-fixed-bar-space-right: 0px;
|
|
276
|
-
--navi-fixed-bar-space-bottom: 0px;
|
|
277
|
-
--navi-fixed-bar-space-left: 0px;
|
|
278
|
-
|
|
279
|
-
/* What the on-screen keyboard covers — and ONLY where it overlays the
|
|
280
|
-
content rather than shrinking the viewport, which is navi's default
|
|
281
|
-
wherever the browser has the VirtualKeyboard API (see
|
|
282
|
-
layout/virtual_keyboard.js). Zero on Firefox/Safari, which have no
|
|
283
|
-
such API, and zero for an app that called
|
|
284
|
-
disableVirtualKeyboardOverlay(): both get a keyboard that shrinks the
|
|
285
|
-
visual viewport instead, which --navi-vvh already tracks. Reading
|
|
286
|
-
env() rather than a JS-written value keeps it live: the keyboard
|
|
287
|
-
slides in over several frames and this follows it without a
|
|
288
|
-
listener. */
|
|
289
|
-
--navi-keyboard-inset-bottom: env(keyboard-inset-height, 0px);
|
|
290
|
-
|
|
291
|
-
/* Level 1. Centered bands, so that declaring one ceiling
|
|
292
|
-
(--navi-app-max-width) is all an app has to do to be a narrow screen in
|
|
293
|
-
a wide window; an app that wants them uneven writes these directly. */
|
|
294
|
-
--navi-app-inset-top: max(
|
|
295
|
-
0px,
|
|
296
|
-
(var(--navi-vvh) - var(--navi-app-max-height, var(--navi-vvh))) / 2
|
|
297
|
-
);
|
|
298
|
-
/* The keyboard on top of the band, and on this edge only: it eats into
|
|
299
|
-
the app's own rectangle exactly like a viewport that shrank, which is
|
|
300
|
-
what makes both paths end up at the same --navi-app-height (and so at
|
|
301
|
-
the same dialog/popover ceiling). Not part of the centering, hence
|
|
302
|
-
added here rather than folded into --navi-app-inset-top: a keyboard
|
|
303
|
-
takes the bottom, it doesn't re-center anything. */
|
|
304
|
-
--navi-app-inset-bottom: calc(
|
|
305
|
-
var(--navi-app-inset-top) + var(--navi-keyboard-inset-bottom)
|
|
306
|
-
);
|
|
307
|
-
--navi-app-inset-left: max(
|
|
308
|
-
0px,
|
|
309
|
-
(var(--navi-vvw) - var(--navi-app-max-width, var(--navi-vvw))) / 2
|
|
310
|
-
);
|
|
311
|
-
--navi-app-inset-right: var(--navi-app-inset-left);
|
|
312
|
-
|
|
313
|
-
/* Level 2. */
|
|
314
|
-
--navi-safe-area-inset-top: calc(
|
|
315
|
-
var(--navi-app-inset-top) +
|
|
316
|
-
max(env(safe-area-inset-top), var(--navi-fixed-bar-space-top))
|
|
317
|
-
);
|
|
318
|
-
--navi-safe-area-inset-right: calc(
|
|
319
|
-
var(--navi-app-inset-right) +
|
|
320
|
-
max(env(safe-area-inset-right), var(--navi-fixed-bar-space-right))
|
|
321
|
-
);
|
|
322
|
-
--navi-safe-area-inset-bottom: calc(
|
|
323
|
-
var(--navi-app-inset-bottom) +
|
|
324
|
-
max(env(safe-area-inset-bottom), var(--navi-fixed-bar-space-bottom))
|
|
325
|
-
);
|
|
326
|
-
--navi-safe-area-inset-left: calc(
|
|
327
|
-
var(--navi-app-inset-left) +
|
|
328
|
-
max(env(safe-area-inset-left), var(--navi-fixed-bar-space-left))
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
/* The document is the scrollport in the common case, and something the
|
|
332
|
-
browser scrolls to — an anchor, a focused field, a restored position —
|
|
333
|
-
landing under a bar is never what anyone wants. */
|
|
334
|
-
scroll-padding-top: var(--navi-safe-area-inset-top);
|
|
335
|
-
scroll-padding-right: var(--navi-safe-area-inset-right);
|
|
336
|
-
scroll-padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
337
|
-
scroll-padding-left: var(--navi-safe-area-inset-left);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/* Put this on whatever scrolls under the furniture.
|
|
341
|
-
|
|
342
|
-
There are TWO rooms to give back, and forgetting the second one is the
|
|
343
|
-
classic bug:
|
|
344
|
-
|
|
345
|
-
- padding, so the end of the content can be scrolled out from under it.
|
|
346
|
-
Without it the last screenful stays covered, unreachable.
|
|
347
|
-
- scroll-padding, so anything the browser scrolls TO lands in front of it
|
|
348
|
-
rather than under. The padding above does not help here: it moves the
|
|
349
|
-
content, not the place the browser scrolls the target to.
|
|
350
|
-
|
|
351
|
-
Marked by the app rather than picked by navi: which element scrolls is
|
|
352
|
-
the app's business, and an app with more than one would have to fight a
|
|
353
|
-
component that chose for it. An app is free to read the variables itself
|
|
354
|
-
instead. */
|
|
355
|
-
[data-navi-safe-area] {
|
|
356
|
-
padding-top: var(--navi-safe-area-inset-top);
|
|
357
|
-
padding-right: var(--navi-safe-area-inset-right);
|
|
358
|
-
padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
359
|
-
padding-left: var(--navi-safe-area-inset-left);
|
|
360
|
-
|
|
361
|
-
scroll-padding-top: var(--navi-safe-area-inset-top);
|
|
362
|
-
scroll-padding-right: var(--navi-safe-area-inset-right);
|
|
363
|
-
scroll-padding-bottom: var(--navi-safe-area-inset-bottom);
|
|
364
|
-
scroll-padding-left: var(--navi-safe-area-inset-left);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
`;
|
|
368
|
-
|
|
369
205
|
/**
|
|
370
206
|
* navi's stance on the on-screen keyboard: it overlays the app rather than
|
|
371
207
|
* resizing the viewport, wherever the browser can be told so (the
|
|
@@ -412,340 +248,142 @@ const computedStyle = getComputedStyle(button);
|
|
|
412
248
|
const controlDefaultFontFamily = computedStyle.fontFamily;
|
|
413
249
|
const controlDefaultFontSize = computedStyle.fontSize;
|
|
414
250
|
document.body.removeChild(button);
|
|
251
|
+
const css = /* css */`@layer navi {
|
|
252
|
+
:root {
|
|
253
|
+
--navi-vvw: 100dvw;
|
|
254
|
+
--navi-vvh: 100dvh;
|
|
255
|
+
--navi-app-width: calc(var(--navi-vvw) - var(--navi-app-inset-left) - var(--navi-app-inset-right));
|
|
256
|
+
--navi-app-height: calc(var(--navi-vvh) - var(--navi-app-inset-top) - var(--navi-app-inset-bottom));
|
|
257
|
+
--navi-focus-outline-width: 2px;
|
|
258
|
+
--navi-focus-outline-color: light-dark(#4476ff, #3b82f6);
|
|
259
|
+
--navi-loader-color: light-dark(#355fcc, #3b82f6);
|
|
260
|
+
-webkit-tap-highlight-color: var(--navi-tap-highlight-color);
|
|
261
|
+
--navi-tap-highlight-color: transparent;
|
|
262
|
+
--navi-control-font-family: ${controlDefaultFontFamily};
|
|
263
|
+
--navi-control-font-size: ${controlDefaultFontSize};
|
|
264
|
+
--navi-control-border-radius: 2px;
|
|
265
|
+
--navi-checkbox-border-radius: min(var(--navi-control-border-radius),
|
|
266
|
+
.25em);
|
|
267
|
+
--navi-control-accent-color: light-dark(#1875ff, #3b82f6);
|
|
268
|
+
--navi-control-border-width: 1px;
|
|
269
|
+
--navi-control-border-color: light-dark(#767676, #8e8e93);
|
|
270
|
+
--navi-control-padding-x-default: 2px;
|
|
271
|
+
--navi-control-padding-y-default: 1px;
|
|
272
|
+
--navi-button-padding-x-default: 6px;
|
|
273
|
+
--navi-button-padding-y-default: 1px;
|
|
274
|
+
--navi-button-background-color: light-dark(#f3f4f6, #2d3748);
|
|
275
|
+
--navi-callout-success-color: #4caf50;
|
|
276
|
+
--navi-callout-info-color: #2196f3;
|
|
277
|
+
--navi-callout-warning-color: #ff9800;
|
|
278
|
+
--navi-callout-error-color: #f44336;
|
|
279
|
+
--navi-callout-neutral-color: var(--navi-control-border-color);
|
|
280
|
+
--navi-list-item-padding-x-default: 4px;
|
|
281
|
+
--navi-list-item-padding-y-default: 1px;
|
|
282
|
+
--navi-picker-padding-x-default: var(--navi-control-padding-x-default);
|
|
283
|
+
--navi-picker-padding-y-default: var(--navi-control-padding-y-default);
|
|
284
|
+
--navi-popup-border-radius: 8px;
|
|
285
|
+
--navi-popup-border-color: light-dark(#d0d0d0, #3b3b3b);
|
|
286
|
+
--navi-popup-box-shadow: 0 4px 8px #00000014, 0 12px 40px #00000038;
|
|
287
|
+
--navi-popup-background-color: var(--navi-surface-color);
|
|
288
|
+
--navi-popup-color: var(--navi-surface-text-color);
|
|
289
|
+
--navi-backdrop-close-background: #00000014;
|
|
290
|
+
--navi-backdrop-discrete-background: #00000005;
|
|
291
|
+
--navi-backdrop-capture-background: #ffffff14;
|
|
292
|
+
--navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);
|
|
293
|
+
--navi-link-color: #00e;
|
|
294
|
+
--navi-link-color-pressed: red;
|
|
295
|
+
--navi-link-current-indicator-color: #cd3425;
|
|
296
|
+
--navi-selection-border-color: #0078d4;
|
|
297
|
+
--navi-selection-background-color: #eaf1fd;
|
|
298
|
+
--navi-accent-color: #031e3c;
|
|
299
|
+
--navi-surface-color: light-dark(#fff, #1c1c1e);
|
|
300
|
+
--navi-surface-text-color: CanvasText;
|
|
301
|
+
--navi-separator-color-default: #d1d9e0;
|
|
302
|
+
--navi-color-white: white;
|
|
303
|
+
--navi-color-dark: #373c45;
|
|
304
|
+
--navi-info-color-light: #eaf6fc;
|
|
305
|
+
--navi-info-color: #376cc2;
|
|
306
|
+
--navi-success-color-light: #ecf9ef;
|
|
307
|
+
--navi-success-color: #50c464;
|
|
308
|
+
--navi-warning-color-light: #fdf6e3;
|
|
309
|
+
--navi-warning-color: #f19c05;
|
|
310
|
+
--navi-error-color-light: #fcebed;
|
|
311
|
+
--navi-error-color: #eb364b;
|
|
312
|
+
--navi-xxs: .125em;
|
|
313
|
+
--navi-xs: .25em;
|
|
314
|
+
--navi-s: .5em;
|
|
315
|
+
--navi-m: 1em;
|
|
316
|
+
--navi-l: 1.5em;
|
|
317
|
+
--navi-xl: 2em;
|
|
318
|
+
--navi-xxl: 3em;
|
|
319
|
+
--navi-typo-xxs: .625rem;
|
|
320
|
+
--navi-typo-xs: .75rem;
|
|
321
|
+
--navi-typo-s: .875rem;
|
|
322
|
+
--navi-typo-m: 1rem;
|
|
323
|
+
--navi-typo-l: 1.125rem;
|
|
324
|
+
--navi-typo-xl: 1.25rem;
|
|
325
|
+
--navi-typo-xxl: 1.5rem;
|
|
326
|
+
--navi-line-height: 1.25;
|
|
327
|
+
line-height: var(--navi-line-height);
|
|
328
|
+
--navi-control-line-height: round(calc(var(--navi-line-height) * 1em),
|
|
329
|
+
1px);
|
|
330
|
+
--navi-color-secondary-mix: 80%;
|
|
331
|
+
--navi-color-emphasis-mix: 50%;
|
|
332
|
+
--navi-color-discrete-mix: 60%;
|
|
333
|
+
--navi-color-hint-mix: 25%;
|
|
334
|
+
--navi-color-primary: var(--navi-surface-text-color);
|
|
335
|
+
--navi-placeholder-color: var(--navi-color-discrete);
|
|
336
|
+
--navi-placeholder-font-style: normal;
|
|
337
|
+
}
|
|
415
338
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// ratios (see :root) are read where this is declared.
|
|
420
|
-
const INK_DERIVED_COLOR_TOKENS_CSS = `
|
|
421
|
-
--navi-color-secondary: color-mix(
|
|
422
|
-
in srgb,
|
|
423
|
-
currentColor var(--navi-color-secondary-mix),
|
|
424
|
-
transparent
|
|
425
|
-
);
|
|
426
|
-
--navi-color-emphasis: color-mix(
|
|
427
|
-
in srgb,
|
|
428
|
-
currentColor var(--navi-color-emphasis-mix),
|
|
429
|
-
black
|
|
430
|
-
);
|
|
431
|
-
--navi-color-discrete: color-mix(
|
|
432
|
-
in srgb,
|
|
433
|
-
currentColor var(--navi-color-discrete-mix),
|
|
434
|
-
transparent
|
|
435
|
-
);
|
|
436
|
-
--navi-color-hint: color-mix(
|
|
437
|
-
in srgb,
|
|
438
|
-
currentColor var(--navi-color-hint-mix),
|
|
439
|
-
transparent
|
|
440
|
-
);
|
|
441
|
-
`;
|
|
442
|
-
const css = /* css */`
|
|
443
|
-
@layer navi {
|
|
444
|
-
:root {
|
|
445
|
-
/* Overridden at runtime with precise VisualViewport pixel values so that dvw/dvh
|
|
446
|
-
(which don't track the virtual keyboard dimensions) are never used in practice on supported browsers. */
|
|
447
|
-
--navi-vvw: 100dvw;
|
|
448
|
-
--navi-vvh: 100dvh;
|
|
449
|
-
|
|
450
|
-
/* What navi treats as "the screen" when it sizes something that escapes
|
|
451
|
-
normal flow (a dialog in the top layer, a popover, anything built on
|
|
452
|
-
them): the app's own rectangle, which is the visual viewport minus the
|
|
453
|
-
bands an app asks for. An app that never spans the whole window says so
|
|
454
|
-
ONCE, without ever naming a component:
|
|
455
|
-
|
|
456
|
-
:root {
|
|
457
|
-
--navi-app-max-width: 600px;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
In pixels — see readAppMax in layout/responsive.js for why.
|
|
461
|
-
|
|
462
|
-
Typically an app simulating a handheld screen: a column centered in a
|
|
463
|
-
wide window with bands on the sides. A dialog is in the top layer, so
|
|
464
|
-
it answers to the viewport, not to that column — left alone it would
|
|
465
|
-
paint 1500px of modal over a 600px app. Declaring the ceiling here is
|
|
466
|
-
what makes the app's own width apply everywhere, including on top.
|
|
467
|
-
|
|
468
|
-
It stays a ceiling and nothing else: on a screen narrower than the app
|
|
469
|
-
it never binds, so popups keep shrinking with the phone, and the gap
|
|
470
|
-
each popup keeps with the edges (marginWithContainer) is subtracted
|
|
471
|
-
from it as before. A single popup that genuinely needs more can still
|
|
472
|
-
raise its own maxWidth/maxHeight prop.
|
|
473
|
-
|
|
474
|
-
Read from the insets rather than as a min() of its own so that the
|
|
475
|
-
width and the placement come from ONE description of where the app is
|
|
476
|
-
(see layout/safe_area.js). Placement does not follow yet everywhere —
|
|
477
|
-
see "Current limitations" in docs/css_architecture.md. */
|
|
478
|
-
--navi-app-width: calc(
|
|
479
|
-
var(--navi-vvw) - var(--navi-app-inset-left) - var(
|
|
480
|
-
--navi-app-inset-right
|
|
481
|
-
)
|
|
482
|
-
);
|
|
483
|
-
--navi-app-height: calc(
|
|
484
|
-
var(--navi-vvh) - var(--navi-app-inset-top) - var(
|
|
485
|
-
--navi-app-inset-bottom
|
|
486
|
-
)
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
--navi-focus-outline-width: 2px;
|
|
490
|
-
--navi-focus-outline-color: light-dark(#4476ff, #3b82f6);
|
|
491
|
-
--navi-loader-color: light-dark(#355fcc, #3b82f6);
|
|
492
|
-
/* The grey rectangle a mobile browser flashes under a finger: we draw
|
|
493
|
-
what a press does ourselves (background, ring, chevron), and the
|
|
494
|
-
browser's rectangle is a square painted over rounded corners.
|
|
495
|
-
Declared here alone because the property is inherited: :root covers
|
|
496
|
-
every element, in and out of navi, so nothing has to remember it.
|
|
497
|
-
Set the token to a color to get the flash back, on the whole app or
|
|
498
|
-
on one subtree. */
|
|
499
|
-
-webkit-tap-highlight-color: var(--navi-tap-highlight-color);
|
|
500
|
-
--navi-tap-highlight-color: transparent;
|
|
501
|
-
|
|
502
|
-
--navi-control-font-family: ${controlDefaultFontFamily};
|
|
503
|
-
--navi-control-font-size: ${controlDefaultFontSize};
|
|
504
|
-
--navi-control-border-radius: 2px;
|
|
505
|
-
/* A checkbox is only ~1em wide: applying a control radius meant for
|
|
506
|
-
buttons/inputs (say 8px) would turn it into a circle and make it read
|
|
507
|
-
as a radio. So it follows the control radius but capped at a quarter
|
|
508
|
-
of its own size. Override this token alone to opt out of the cap. */
|
|
509
|
-
--navi-checkbox-border-radius: min(
|
|
510
|
-
var(--navi-control-border-radius),
|
|
511
|
-
0.25em
|
|
512
|
-
);
|
|
513
|
-
/* The color a control uses to say "this one is on": a checked checkbox,
|
|
514
|
-
a checked radio, an enabled switch. Kept apart from --navi-accent-color
|
|
515
|
-
(the brand color for CTA and selection) because a control that is on
|
|
516
|
-
must stay readable as a control, which usually means a brighter, more
|
|
517
|
-
saturated color than a brand accent.
|
|
518
|
-
The light value matches the browser's own accent so a navi control and
|
|
519
|
-
a native one can sit side by side without reading as two blues. */
|
|
520
|
-
--navi-control-accent-color: light-dark(rgb(24, 117, 255), #3b82f6);
|
|
521
|
-
--navi-control-border-width: 1px;
|
|
522
|
-
--navi-control-border-color: light-dark(#767676, #8e8e93);
|
|
523
|
-
--navi-control-padding-x-default: 2px;
|
|
524
|
-
--navi-control-padding-y-default: 1px;
|
|
525
|
-
/* Global padding defaults — override these to change all button paddings. */
|
|
526
|
-
/* Use --button-padding, --button-padding-x, --button-padding-y for per-button overrides. */
|
|
527
|
-
--navi-button-padding-x-default: 6px;
|
|
528
|
-
--navi-button-padding-y-default: 1px;
|
|
529
|
-
/* Read by Button, and by whatever is drawn as one (a picker under
|
|
530
|
-
variant="button"), so the two keep the same surface. */
|
|
531
|
-
--navi-button-background-color: light-dark(#f3f4f6, #2d3748);
|
|
532
|
-
|
|
533
|
-
/* What a callout says about its message, as a color — and what draws
|
|
534
|
-
as one outside the callout too (the icon a picker in callout mode
|
|
535
|
-
presses, see CalloutStatusIcon). */
|
|
536
|
-
--navi-callout-success-color: #4caf50;
|
|
537
|
-
--navi-callout-info-color: #2196f3;
|
|
538
|
-
--navi-callout-warning-color: #ff9800;
|
|
539
|
-
--navi-callout-error-color: #f44336;
|
|
540
|
-
/* No status: the message is an aside, said without an icon and framed
|
|
541
|
-
like a control rather than like a warning. */
|
|
542
|
-
--navi-callout-neutral-color: var(--navi-control-border-color);
|
|
543
|
-
/* For list item we need slightly more padding to be able to see radio/checkbox outline */
|
|
544
|
-
--navi-list-item-padding-x-default: 4px;
|
|
545
|
-
--navi-list-item-padding-y-default: 1px;
|
|
546
|
-
/* default */
|
|
547
|
-
--navi-picker-padding-x-default: var(--navi-control-padding-x-default);
|
|
548
|
-
--navi-picker-padding-y-default: var(--navi-control-padding-y-default);
|
|
549
|
-
|
|
550
|
-
--navi-popup-border-radius: 8px;
|
|
551
|
-
--navi-popup-border-color: light-dark(#d0d0d0, #3b3b3b);
|
|
552
|
-
--navi-popup-box-shadow:
|
|
553
|
-
0 4px 8px rgba(0, 0, 0, 0.08), 0 12px 40px rgba(0, 0, 0, 0.22);
|
|
554
|
-
/* A popup's paper is the same paper as everything else's: derived from
|
|
555
|
-
the surface token so one override themes fields, layouts and popups
|
|
556
|
-
together. Still its own token, so popups alone can be re-papered. */
|
|
557
|
-
--navi-popup-background-color: var(--navi-surface-color);
|
|
558
|
-
/* The ink written on that paper. A popup declares both on itself (see
|
|
559
|
-
dialog.jsx / popover.jsx): it is a new surface, so it does not write
|
|
560
|
-
in whatever the container it was declared in writes in. */
|
|
561
|
-
--navi-popup-color: var(--navi-surface-text-color);
|
|
562
|
-
--navi-backdrop-close-background: rgba(0, 0, 0, 0.08);
|
|
563
|
-
/* backdropVariant="discrete": the popup still catches every outside
|
|
564
|
-
click, it just stops announcing that it did. For an affordance one
|
|
565
|
-
reaches past rather than through — the dim is there to mark the layer,
|
|
566
|
-
not to push the page behind it away. */
|
|
567
|
-
--navi-backdrop-discrete-background: rgba(0, 0, 0, 0.02);
|
|
568
|
-
/* "capture" means the rest of the page is fully non-interactive —
|
|
569
|
-
blurred, not just dimmed, so it reads as clearly secondary and
|
|
570
|
-
pulls visual focus onto the popover's own content. */
|
|
571
|
-
--navi-backdrop-capture-background: rgb(255 255 255 / 0.08);
|
|
572
|
-
--navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);
|
|
573
|
-
|
|
574
|
-
/* Link colors. They live here rather than only on .navi_link because a
|
|
575
|
-
var declared on the element itself always beats the same var inherited
|
|
576
|
-
from an ancestor: a page setting --link-color-pressed on :root would
|
|
577
|
-
never reach a link. These :root tokens are the theme-level surface;
|
|
578
|
-
--link-color-* stays the per-link (or per-subtree) override. */
|
|
579
|
-
--navi-link-color: rgb(0, 0, 238);
|
|
580
|
-
/* --navi-link-color-visited is intentionally left undefined: by default a
|
|
581
|
-
visited link is derived from whatever --link-color ended up being on
|
|
582
|
-
the link itself (see link.jsx), which a :root token cannot see. Set it
|
|
583
|
-
here from an app to pin one visited color for every link. */
|
|
584
|
-
--navi-link-color-pressed: red;
|
|
585
|
-
--navi-link-current-indicator-color: rgb(205, 52, 37);
|
|
586
|
-
|
|
587
|
-
--navi-selection-border-color: #0078d4;
|
|
588
|
-
--navi-selection-background-color: #eaf1fd;
|
|
589
|
-
/* Accent color — used for call-to-action buttons and selected list items.
|
|
590
|
-
Override this single variable to apply a consistent brand color across
|
|
591
|
-
all components that need to stand out. */
|
|
592
|
-
--navi-accent-color: rgb(3, 30, 60);
|
|
593
|
-
/* The surface a control's content sits on — the "paper" behind the text.
|
|
594
|
-
Fields use it whenever they need a solid background (their default
|
|
595
|
-
background, a transparent field being edited, …). */
|
|
596
|
-
--navi-surface-color: light-dark(#ffffff, #1c1c1e);
|
|
597
|
-
/* What is written on that paper. The browser's own text color rather
|
|
598
|
-
than a literal, so it follows color-scheme the way the paper does. */
|
|
599
|
-
--navi-surface-text-color: CanvasText;
|
|
600
|
-
/* The line that separates two regions of one surface — a scrolling area's
|
|
601
|
-
header from what scrolls under it, for instance. Not a border: the
|
|
602
|
-
separation belongs to the layout, not to the box that draws it. */
|
|
603
|
-
--navi-separator-color-default: #d1d9e0;
|
|
604
|
-
--navi-color-white: white;
|
|
605
|
-
--navi-color-dark: rgb(55, 60, 69);
|
|
606
|
-
|
|
607
|
-
--navi-info-color-light: #eaf6fc;
|
|
608
|
-
--navi-info-color: #376cc2;
|
|
609
|
-
--navi-success-color-light: #ecf9ef;
|
|
610
|
-
--navi-success-color: #50c464;
|
|
611
|
-
--navi-warning-color-light: #fdf6e3;
|
|
612
|
-
--navi-warning-color: #f19c05;
|
|
613
|
-
--navi-error-color-light: #fcebed;
|
|
614
|
-
--navi-error-color: #eb364b;
|
|
615
|
-
|
|
616
|
-
--navi-xxs: 0.125em; /* = 2px at 16px base */
|
|
617
|
-
--navi-xs: 0.25em; /* = 4px at 16px base */
|
|
618
|
-
--navi-s: 0.5em; /* = 8px at 16px base */
|
|
619
|
-
--navi-m: 1em; /* = 16px at 16px base (base font size) */
|
|
620
|
-
--navi-l: 1.5em; /* = 24px at 16px base */
|
|
621
|
-
--navi-xl: 2em; /* = 32px at 16px base */
|
|
622
|
-
--navi-xxl: 3em; /* 48px at 16px base */
|
|
623
|
-
|
|
624
|
-
--navi-typo-xxs: 0.625rem; /* 10px at 16px base */
|
|
625
|
-
--navi-typo-xs: 0.75rem; /* 12px at 16px base */
|
|
626
|
-
--navi-typo-s: 0.875rem; /* 14px at 16px base */
|
|
627
|
-
--navi-typo-m: 1rem; /* 16px at 16px base (base font size) */
|
|
628
|
-
--navi-typo-l: 1.125rem; /* 18px at 16px base */
|
|
629
|
-
--navi-typo-xl: 1.25rem; /* 20px at 16px base */
|
|
630
|
-
--navi-typo-xxl: 1.5rem; /* 24px at 16px base */
|
|
631
|
-
|
|
632
|
-
/* The line every text is written on, controls included. A number
|
|
633
|
-
rather than "normal", because of the emoji: its box is taller than a
|
|
634
|
-
letter's, so under "normal" the one line carrying one stands taller
|
|
635
|
-
than the lines around it, and under a tighter line its top is cut.
|
|
636
|
-
1.25 is where neither happens — 1 clips the glyph, 1.5 spaces the rows
|
|
637
|
-
out more than reading them asks for — which also makes it the floor
|
|
638
|
-
for an app that displays what people typed.
|
|
639
|
-
The document is written on it; the components that come with a line of
|
|
640
|
-
their own from the browser (Button, Input, Textarea, Select) are handed
|
|
641
|
-
it by name. One number everywhere is what keeps a value on the same
|
|
642
|
-
line whether it is typed in a field or drawn in the page.
|
|
643
|
-
See docs/typography.md. */
|
|
644
|
-
--navi-line-height: 1.25;
|
|
645
|
-
/* Layered: the document's line is what navi writes on when the app says
|
|
646
|
-
nothing, so the app's own body rule wins. */
|
|
647
|
-
line-height: var(--navi-line-height);
|
|
648
|
-
/* The same line for a control, snapped to the pixel. The browser lays a
|
|
649
|
-
line out at its exact height but paints the glyph on a pixel row: at
|
|
650
|
-
the default control size (13.333px) the line is 16.666px, and the
|
|
651
|
-
two-thirds that do not fit go entirely under the glyph, which then
|
|
652
|
-
sits a pixel above the middle of its field. A whole number of pixels
|
|
653
|
-
has no remainder to put anywhere. Written with em so it resolves on
|
|
654
|
-
the control that uses it, at that control's own size. */
|
|
655
|
-
--navi-control-line-height: round(
|
|
656
|
-
calc(var(--navi-line-height) * 1em),
|
|
657
|
-
1px
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
/* Color keywords.
|
|
661
|
-
primary: the ink of the paper, at full strength. An absolute
|
|
662
|
-
rather than currentColor: it is what brings a run back
|
|
663
|
-
to plain text inside a muted one (a value in a secondary
|
|
664
|
-
label). Themed through --navi-surface-text-color.
|
|
665
|
-
secondary: supporting text, captions, less important labels
|
|
666
|
-
emphasis: reinforce meaning, make content stand out more
|
|
667
|
-
discrete: unobtrusive elements that shouldn't compete for attention
|
|
668
|
-
hint: barely-there color, watermarks, ghost placeholders
|
|
669
|
-
The last four mix currentColor toward transparent or black, so they
|
|
670
|
-
follow whatever ink a container writes in: a dark card sets color and
|
|
671
|
-
nothing else. The share of ink in each is the theme's knob, a -mix
|
|
672
|
-
token read wherever the formulas are declared — here and on each
|
|
673
|
-
surface that re-declares them (rules below) — so a ratio set on :root
|
|
674
|
-
reaches the page and its popups alike. Set on a container it reaches
|
|
675
|
-
none of that container's own text, a var() resolving where its custom
|
|
676
|
-
property is declared; a container that wants one keyword otherwise
|
|
677
|
-
for ITS paper (a white at 88% where 80% of white reads too faint on a
|
|
678
|
-
colored resin) pins the keyword itself, and that value stops at the
|
|
679
|
-
next surface. */
|
|
680
|
-
--navi-color-secondary-mix: 80%;
|
|
681
|
-
--navi-color-emphasis-mix: 50%;
|
|
682
|
-
--navi-color-discrete-mix: 60%;
|
|
683
|
-
--navi-color-hint-mix: 25%;
|
|
684
|
-
--navi-color-primary: var(--navi-surface-text-color);
|
|
685
|
-
${INK_DERIVED_COLOR_TOKENS_CSS}
|
|
686
|
-
|
|
687
|
-
/* What a control shows while it holds nothing: the ::placeholder of an
|
|
688
|
-
input, the empty value slot of a picker. One place for the whole app:
|
|
689
|
-
|
|
690
|
-
:root {
|
|
691
|
-
--navi-placeholder-color: #8a94a6;
|
|
692
|
-
--navi-placeholder-font-style: italic;
|
|
693
|
-
}
|
|
694
|
-
*/
|
|
695
|
-
--navi-placeholder-color: var(--navi-color-discrete);
|
|
696
|
-
--navi-placeholder-font-style: normal;
|
|
697
|
-
}
|
|
339
|
+
.navi_popover, .navi_dialog {
|
|
340
|
+
--navi-color-primary: var(--navi-popup-color);
|
|
341
|
+
}
|
|
698
342
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
re-declared so a container's own value for one of them stops here. On
|
|
702
|
-
the element itself, so it beats that value whatever the layer; an app
|
|
703
|
-
that wants a surface to keep its container's ink says so on the
|
|
704
|
-
surface, unlayered, and wins in turn.
|
|
705
|
-
A popup writes in --navi-popup-color. */
|
|
706
|
-
.navi_popover,
|
|
707
|
-
.navi_dialog {
|
|
708
|
-
--navi-color-primary: var(--navi-popup-color);
|
|
709
|
-
${INK_DERIVED_COLOR_TOKENS_CSS}
|
|
710
|
-
}
|
|
711
|
-
/* A callout writes in the UA's own ink: callout.js sets color: revert on
|
|
712
|
-
a [popover] element, which the UA styles CanvasText. */
|
|
713
|
-
.navi_callout {
|
|
714
|
-
--navi-color-primary: CanvasText;
|
|
715
|
-
${INK_DERIVED_COLOR_TOKENS_CSS}
|
|
716
|
-
}
|
|
343
|
+
.navi_callout {
|
|
344
|
+
--navi-color-primary: CanvasText;
|
|
717
345
|
}
|
|
718
346
|
|
|
719
|
-
|
|
347
|
+
:root, .navi_popover, .navi_dialog, .navi_callout {
|
|
348
|
+
--navi-color-secondary: color-mix(in srgb,
|
|
349
|
+
currentColor var(--navi-color-secondary-mix),
|
|
350
|
+
transparent);
|
|
351
|
+
--navi-color-emphasis: color-mix(in srgb,
|
|
352
|
+
currentColor var(--navi-color-emphasis-mix),
|
|
353
|
+
black);
|
|
354
|
+
--navi-color-discrete: color-mix(in srgb,
|
|
355
|
+
currentColor var(--navi-color-discrete-mix),
|
|
356
|
+
transparent);
|
|
357
|
+
--navi-color-hint: color-mix(in srgb,
|
|
358
|
+
currentColor var(--navi-color-hint-mix),
|
|
359
|
+
transparent);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
720
362
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
363
|
+
input[navi-visually-hidden], button[navi-visually-hidden], div[navi-visually-hidden] {
|
|
364
|
+
z-index: -1;
|
|
365
|
+
white-space: nowrap;
|
|
366
|
+
clip-path: inset(50%);
|
|
367
|
+
appearance: none;
|
|
368
|
+
border: 0;
|
|
369
|
+
width: 100%;
|
|
370
|
+
height: 100%;
|
|
371
|
+
margin: 0;
|
|
372
|
+
padding: 0;
|
|
373
|
+
position: absolute;
|
|
374
|
+
top: 0;
|
|
375
|
+
left: 0;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
|
|
378
|
+
&[navi-debug] {
|
|
379
|
+
clip-path: none;
|
|
380
|
+
appearance: auto;
|
|
381
|
+
width: auto;
|
|
382
|
+
height: auto;
|
|
732
383
|
margin: 0;
|
|
733
|
-
|
|
734
|
-
white-space: nowrap;
|
|
735
|
-
border: 0;
|
|
736
|
-
clip-path: inset(50%);
|
|
737
|
-
appearance: none;
|
|
738
|
-
overflow: hidden;
|
|
739
|
-
|
|
740
|
-
&[navi-debug] {
|
|
741
|
-
position: static;
|
|
742
|
-
width: auto;
|
|
743
|
-
height: auto;
|
|
744
|
-
margin: 0;
|
|
745
|
-
clip-path: none;
|
|
746
|
-
appearance: auto;
|
|
747
|
-
}
|
|
384
|
+
position: static;
|
|
748
385
|
}
|
|
386
|
+
}
|
|
749
387
|
`;
|
|
750
388
|
import.meta.css = [css, "@jsenv/navi/src/navi_css_vars.js"];
|
|
751
389
|
effect(() => {
|