@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.
@@ -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
- // The color keywords derived from the ink. Written once and declared on every
417
- // paper — :root, and each surface that is a new one (popup, callout) — so what
418
- // a container pinned for its own paper does not reach the surface. The -mix
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
- /* A surface is a new paper: its color keywords are computed against the
700
- ink it writes in, not the container's — the :root formulas again,
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
- ${SAFE_AREA_CSS}
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
- /* Hidden appearance */
722
- input[navi-visually-hidden],
723
- button[navi-visually-hidden],
724
- div[navi-visually-hidden] {
725
- position: absolute;
726
- top: 0;
727
- left: 0;
728
- z-index: -1;
729
- /* Important to take full size so that scrollIntoView work as expected */
730
- width: 100%;
731
- height: 100%;
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
- padding: 0;
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(() => {