@jsenv/navi 0.29.16 → 0.29.18
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 +995 -521
- package/dist/jsenv_navi.js.map +49 -31
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
5
|
import { windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, installImportMetaCssBuild, coarsePointerSignal } from "./jsenv_navi_side_effects.js";
|
|
6
|
-
import { createContext, isValidElement, h, Fragment, toChildArray, render, cloneElement } from "preact";
|
|
6
|
+
import { createContext, isValidElement, h, Fragment, toChildArray, render, options, cloneElement } from "preact";
|
|
7
7
|
import { useContext, useLayoutEffect, useRef, useEffect, useCallback, useState, useMemo, useId, useErrorBoundary } from "preact/hooks";
|
|
8
8
|
import { jsx, jsxs, Fragment as Fragment$1 } from "preact/jsx-runtime";
|
|
9
9
|
import { computed, signal, effect, batch, useSignal } from "@preact/signals";
|
|
10
|
-
import { createPubSub, normalizeStyle, mergeOneStyle, getPositionedParent,
|
|
10
|
+
import { createPubSub, normalizeStyle, mergeOneStyle, getPositionedParent, dispatchInternalCustomEvent, dispatchCustomEvent, findEvent, mergeTwoStyles, normalizeStyles, resolveCSSSize, measureLongestVisualLineWidth, hasCSSSizeUnit, resolveOklchLightness, contrastColor, createIterableWeakSet, getElementSignature, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, createEventGroupLogger, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, getKeyboardEventDefaultAction, chainEvent, activeElementSignal, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, initFocusGroup, elementIsFocusable, scrollIntoViewScoped, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, dragAfterThreshold, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, stringifyStyle as stringifyStyle$1 } from "@jsenv/dom";
|
|
11
11
|
export { contrastColor, startDragToReorder } from "@jsenv/dom";
|
|
12
12
|
import { createValidity, parseDuration, durationContainsNaN, compareTwoDurations, durationToSeconds, durationToISOString } from "@jsenv/validity";
|
|
13
13
|
export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
|
|
@@ -7267,6 +7267,367 @@ const findControlRoot = (el) => {
|
|
|
7267
7267
|
return null;
|
|
7268
7268
|
};
|
|
7269
7269
|
|
|
7270
|
+
const dispatchRequestSetUIState = (element, value, detail) => {
|
|
7271
|
+
const controlHost = findControlHost(element) || element;
|
|
7272
|
+
return dispatchInternalCustomEvent(controlHost, "navi_set_ui_state", {
|
|
7273
|
+
...detail,
|
|
7274
|
+
value,
|
|
7275
|
+
});
|
|
7276
|
+
};
|
|
7277
|
+
const dispatchRequestClearUIState = (element, e) => {
|
|
7278
|
+
const controlHost = findControlHost(element) || element;
|
|
7279
|
+
return dispatchInternalCustomEvent(controlHost, "navi_clear_ui_state", {
|
|
7280
|
+
event: e,
|
|
7281
|
+
});
|
|
7282
|
+
};
|
|
7283
|
+
const dispatchRequestResetUIState = (element, e) => {
|
|
7284
|
+
const controlHost = findControlHost(element) || element;
|
|
7285
|
+
return dispatchInternalCustomEvent(controlHost, "navi_reset_ui_state", {
|
|
7286
|
+
event: e,
|
|
7287
|
+
});
|
|
7288
|
+
};
|
|
7289
|
+
/**
|
|
7290
|
+
* @param {Element} el
|
|
7291
|
+
* @param {{ own?: boolean }} [options] `own`: what the element holds BY ITSELF.
|
|
7292
|
+
* Only a button ever answers differently — one with no value of its own
|
|
7293
|
+
* inherits the value of the control around it, which is what makes
|
|
7294
|
+
* `--navi-send` on a form's button be about that form. Something asking what
|
|
7295
|
+
* THIS element says (a travel command reading what the travel is about) wants
|
|
7296
|
+
* the own value and would otherwise be handed the surrounding control's.
|
|
7297
|
+
*/
|
|
7298
|
+
const getUIStateFromElement = (el, { own } = {}) => {
|
|
7299
|
+
let uiState;
|
|
7300
|
+
dispatchInternalCustomEvent(el, "navi_get_ui_state", {
|
|
7301
|
+
own,
|
|
7302
|
+
respondWith: (v) => {
|
|
7303
|
+
uiState = v;
|
|
7304
|
+
},
|
|
7305
|
+
});
|
|
7306
|
+
return uiState;
|
|
7307
|
+
};
|
|
7308
|
+
|
|
7309
|
+
/**
|
|
7310
|
+
* Converts a JS value into the form expected by the browser DOM property for a
|
|
7311
|
+
* given control type/input type combination.
|
|
7312
|
+
*
|
|
7313
|
+
* For example:
|
|
7314
|
+
* - `datetime-local` inputs expect a local datetime string without timezone
|
|
7315
|
+
* - `number`/`range` inputs expect a numeric string or number
|
|
7316
|
+
* - `color` inputs require a non-empty hex string (falls back to `#000000`)
|
|
7317
|
+
* - All other inputs receive the value as-is (undefined → "")
|
|
7318
|
+
*
|
|
7319
|
+
* Returns either the converted value directly, or a converter function when the
|
|
7320
|
+
* conversion depends on the runtime value (e.g. plain inputs return `asInputValue`).
|
|
7321
|
+
*
|
|
7322
|
+
* @param {any} value - The JS value to convert.
|
|
7323
|
+
* @param {{ controlType: string, type: string }} options
|
|
7324
|
+
* @returns {any} The DOM-compatible value or a converter function.
|
|
7325
|
+
*/
|
|
7326
|
+
const asControlHostValue = (
|
|
7327
|
+
jsValue,
|
|
7328
|
+
{ controlType, type, inputMode },
|
|
7329
|
+
) => {
|
|
7330
|
+
if (controlType === "select") {
|
|
7331
|
+
// A select holds one of its options, always a string; holding nothing is
|
|
7332
|
+
// the empty option, which the element spells "".
|
|
7333
|
+
return asInputValue(jsValue);
|
|
7334
|
+
}
|
|
7335
|
+
if (controlType === "input" || controlType === "picker") {
|
|
7336
|
+
if (type === "datetime-local") {
|
|
7337
|
+
return asDatetimeLocalString(jsValue);
|
|
7338
|
+
}
|
|
7339
|
+
if (
|
|
7340
|
+
type === "number" ||
|
|
7341
|
+
type === "range" ||
|
|
7342
|
+
inputMode === "numeric" ||
|
|
7343
|
+
inputMode === "decimal"
|
|
7344
|
+
) {
|
|
7345
|
+
return asNumberString(jsValue);
|
|
7346
|
+
}
|
|
7347
|
+
if (type === "color") {
|
|
7348
|
+
return asColorString(jsValue);
|
|
7349
|
+
}
|
|
7350
|
+
return asInputValue(jsValue);
|
|
7351
|
+
}
|
|
7352
|
+
return jsValue;
|
|
7353
|
+
};
|
|
7354
|
+
// As explained in https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/datetime-local#setting_timezones
|
|
7355
|
+
// datetime-local does not support timezones
|
|
7356
|
+
const asDatetimeLocalString = (dateTimeString) => {
|
|
7357
|
+
const date = new Date(dateTimeString);
|
|
7358
|
+
if (isNaN(date.getTime())) {
|
|
7359
|
+
return dateTimeString;
|
|
7360
|
+
}
|
|
7361
|
+
const year = date.getFullYear();
|
|
7362
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
7363
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
7364
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
7365
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
7366
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
7367
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
7368
|
+
};
|
|
7369
|
+
const asNumberString = (jsValue) => {
|
|
7370
|
+
if (jsValue === undefined) {
|
|
7371
|
+
return "";
|
|
7372
|
+
}
|
|
7373
|
+
return jsValue;
|
|
7374
|
+
};
|
|
7375
|
+
// Browser requires a non-empty value for <input type="color">.
|
|
7376
|
+
// When our logical value is empty we give it #000000 so it doesn't choke.
|
|
7377
|
+
// The UI uses the original (possibly empty) value to show the checkerboard.
|
|
7378
|
+
const asColorString = (jsValue) => {
|
|
7379
|
+
return jsValue || "#000000";
|
|
7380
|
+
};
|
|
7381
|
+
const asInputValue = (jsValue) => {
|
|
7382
|
+
if (jsValue === undefined) {
|
|
7383
|
+
return "";
|
|
7384
|
+
}
|
|
7385
|
+
return jsValue;
|
|
7386
|
+
};
|
|
7387
|
+
|
|
7388
|
+
/**
|
|
7389
|
+
* Reads the current logical JS value from a control host DOM element.
|
|
7390
|
+
*
|
|
7391
|
+
* Handles all navi control host element types:
|
|
7392
|
+
* - `<button>` — reads via `navi_get_value` custom event, falls back to `button.value`
|
|
7393
|
+
* - `<input type="number|range">` — parses as a number, returns `undefined` when empty
|
|
7394
|
+
* - `<input type="checkbox|radio">` — returns `undefined` when unchecked, otherwise reads
|
|
7395
|
+
* via `navi_get_value` custom event (to preserve the original JS type of the value prop)
|
|
7396
|
+
* - `<input type="datetime-local">` — converts the local datetime string to an ISO 8601 string
|
|
7397
|
+
* - `<input type="navi_picker">` — delegates to the controller via `navi_get_ui_state`
|
|
7398
|
+
* - All other inputs — returns `input.value` as a string
|
|
7399
|
+
*
|
|
7400
|
+
* @param {HTMLElement} controlHost - The control host DOM element to read from.
|
|
7401
|
+
* @returns {any} The current logical value of the control.
|
|
7402
|
+
*/
|
|
7403
|
+
const readControlValue = (controlHost) => {
|
|
7404
|
+
if (
|
|
7405
|
+
controlHost.tagName === "BUTTON" ||
|
|
7406
|
+
controlHost.getAttribute("role") === "button"
|
|
7407
|
+
) {
|
|
7408
|
+
return readValueFromButton(controlHost);
|
|
7409
|
+
}
|
|
7410
|
+
if (controlHost.tagName === "INPUT") {
|
|
7411
|
+
// important: input.type = "navi_js"; followed by input.type; returns "text"
|
|
7412
|
+
// so use getAttribute
|
|
7413
|
+
const type = controlHost.getAttribute("type");
|
|
7414
|
+
|
|
7415
|
+
if (
|
|
7416
|
+
type === "number" ||
|
|
7417
|
+
type === "range" ||
|
|
7418
|
+
controlHost.inputMode === "numeric" ||
|
|
7419
|
+
controlHost.inputMode === "decimal"
|
|
7420
|
+
) {
|
|
7421
|
+
return readNumberFromInput(controlHost);
|
|
7422
|
+
}
|
|
7423
|
+
if (type === "color") {
|
|
7424
|
+
return readValueFromControlHost(controlHost);
|
|
7425
|
+
}
|
|
7426
|
+
if (type === "checkbox" || type === "radio") {
|
|
7427
|
+
return readValueFromCheckableInput(controlHost);
|
|
7428
|
+
}
|
|
7429
|
+
if (type === "datetime-local") {
|
|
7430
|
+
return readDatetimeLocalFromInput(controlHost);
|
|
7431
|
+
}
|
|
7432
|
+
if (type === "navi_js") {
|
|
7433
|
+
return getUIStateFromElement(controlHost);
|
|
7434
|
+
}
|
|
7435
|
+
return readValueFromInput(controlHost);
|
|
7436
|
+
}
|
|
7437
|
+
if (controlHost.hasAttribute("navi-control-host")) {
|
|
7438
|
+
// Non-button, non-input navi controls (e.g. Badge.Button rendered as span)
|
|
7439
|
+
return readValueFromControlHost(controlHost);
|
|
7440
|
+
}
|
|
7441
|
+
return readValueFromElement(controlHost);
|
|
7442
|
+
};
|
|
7443
|
+
const readValueFromControlHost = (controlHost) => {
|
|
7444
|
+
return readValueFromNaviCustomEvent(controlHost, controlHost.value);
|
|
7445
|
+
};
|
|
7446
|
+
const readValueFromButton = (button) => {
|
|
7447
|
+
return readValueFromControlHost(button);
|
|
7448
|
+
};
|
|
7449
|
+
const readDatetimeLocalFromInput = (input) => {
|
|
7450
|
+
const localDateTimeString = input.value;
|
|
7451
|
+
if (localDateTimeString === "") {
|
|
7452
|
+
return "";
|
|
7453
|
+
}
|
|
7454
|
+
const localDate = new Date(localDateTimeString);
|
|
7455
|
+
if (isNaN(localDate.getTime())) {
|
|
7456
|
+
return localDateTimeString;
|
|
7457
|
+
}
|
|
7458
|
+
return localDate.toISOString();
|
|
7459
|
+
};
|
|
7460
|
+
const readNumberFromInput = (input) => {
|
|
7461
|
+
const numberString = input.value;
|
|
7462
|
+
if (numberString === "") {
|
|
7463
|
+
return "";
|
|
7464
|
+
}
|
|
7465
|
+
const asNumber = Number(numberString);
|
|
7466
|
+
if (isNaN(asNumber)) {
|
|
7467
|
+
return numberString;
|
|
7468
|
+
}
|
|
7469
|
+
return asNumber;
|
|
7470
|
+
};
|
|
7471
|
+
const readValueFromCheckableInput = (input) => {
|
|
7472
|
+
const checked = input.checked;
|
|
7473
|
+
if (!checked) {
|
|
7474
|
+
return undefined;
|
|
7475
|
+
}
|
|
7476
|
+
return readValueFromControlHost(input);
|
|
7477
|
+
};
|
|
7478
|
+
const readValueFromInput = (input) => {
|
|
7479
|
+
const value = input.value;
|
|
7480
|
+
return value;
|
|
7481
|
+
};
|
|
7482
|
+
const readValueFromElement = (element) => {
|
|
7483
|
+
const value = element.value;
|
|
7484
|
+
return value;
|
|
7485
|
+
};
|
|
7486
|
+
const readValueFromNaviCustomEvent = (field, fallback) => {
|
|
7487
|
+
// prefer the value given as prop (respect original type, browser would convert to string)
|
|
7488
|
+
let responded;
|
|
7489
|
+
let value;
|
|
7490
|
+
dispatchCustomEvent(field, "navi_get_value", {
|
|
7491
|
+
respondWith: (jsValue) => {
|
|
7492
|
+
responded = true;
|
|
7493
|
+
value = jsValue;
|
|
7494
|
+
},
|
|
7495
|
+
});
|
|
7496
|
+
if (responded) {
|
|
7497
|
+
return value;
|
|
7498
|
+
}
|
|
7499
|
+
return fallback;
|
|
7500
|
+
};
|
|
7501
|
+
|
|
7502
|
+
// In-memory registry of all mounted ui state controllers keyed by their id.
|
|
7503
|
+
// Allows direct controller access without dispatching DOM events — used by external
|
|
7504
|
+
// callers (e.g. selectable_list) to call setUIState by id instead of via the DOM.
|
|
7505
|
+
const controllersById = new Map();
|
|
7506
|
+
|
|
7507
|
+
// In-memory registry for radio controllers, keyed by input name.
|
|
7508
|
+
// Allows radio sibling unchecking without querying the DOM — necessary when
|
|
7509
|
+
// items are virtualized and their DOM element may not exist at the time.
|
|
7510
|
+
// Form scoping is reproduced by comparing parentUIStateController references.
|
|
7511
|
+
const radioControllersByName = new Map();
|
|
7512
|
+
|
|
7513
|
+
// Registry for non-serializable JS values that cannot be written to DOM attributes as-is.
|
|
7514
|
+
// When a value is an object/array, we store it here and write a reference string to the DOM
|
|
7515
|
+
// instead of "[object Object]". Console-inspectable via window.__navi_js('id').
|
|
7516
|
+
// The controller id is used as key — if the controller has no id, the value is not registered.
|
|
7517
|
+
const naviJsRegistry = new Map();
|
|
7518
|
+
|
|
7519
|
+
const getUIStateControllerById = (id) => controllersById.get(id);
|
|
7520
|
+
const getRadioSiblings = (radioUIStateController) => {
|
|
7521
|
+
const siblings = radioControllersByName.get(radioUIStateController.name);
|
|
7522
|
+
return siblings;
|
|
7523
|
+
};
|
|
7524
|
+
|
|
7525
|
+
const toDomValue = (jsValue, { controlType, id, type, inputMode }) => {
|
|
7526
|
+
const domValue = asControlHostValue(jsValue, {
|
|
7527
|
+
controlType,
|
|
7528
|
+
type,
|
|
7529
|
+
inputMode,
|
|
7530
|
+
});
|
|
7531
|
+
if (isSerializableAsDomValue(domValue)) {
|
|
7532
|
+
return domValue;
|
|
7533
|
+
}
|
|
7534
|
+
naviJsRegistry.set(id, domValue);
|
|
7535
|
+
return `window.__navi_js('${id}')`;
|
|
7536
|
+
};
|
|
7537
|
+
|
|
7538
|
+
window.__navi_js = (id) => naviJsRegistry.get(id);
|
|
7539
|
+
const isSerializableAsDomValue = (value) => {
|
|
7540
|
+
if (value === null || value === undefined) {
|
|
7541
|
+
return true;
|
|
7542
|
+
}
|
|
7543
|
+
const type = typeof value;
|
|
7544
|
+
return type === "string" || type === "number" || type === "boolean";
|
|
7545
|
+
};
|
|
7546
|
+
|
|
7547
|
+
const onUIStateControllerCreated = (uiStateController) => {
|
|
7548
|
+
const { id, name, controlType } = uiStateController;
|
|
7549
|
+
if (id) {
|
|
7550
|
+
controllersById.set(id, uiStateController);
|
|
7551
|
+
}
|
|
7552
|
+
const proxyFor = uiStateController.props["navi-control-proxy-for"];
|
|
7553
|
+
if (proxyFor) {
|
|
7554
|
+
let proxySet = proxyControllersByRealInputId.get(proxyFor);
|
|
7555
|
+
if (!proxySet) {
|
|
7556
|
+
proxySet = new Set();
|
|
7557
|
+
proxyControllersByRealInputId.set(proxyFor, proxySet);
|
|
7558
|
+
}
|
|
7559
|
+
proxySet.add(uiStateController);
|
|
7560
|
+
}
|
|
7561
|
+
if (
|
|
7562
|
+
controlType === "input" &&
|
|
7563
|
+
uiStateController.props.type === "radio" &&
|
|
7564
|
+
name
|
|
7565
|
+
) {
|
|
7566
|
+
let set = radioControllersByName.get(name);
|
|
7567
|
+
if (!set) {
|
|
7568
|
+
set = new Set();
|
|
7569
|
+
radioControllersByName.set(name, set);
|
|
7570
|
+
}
|
|
7571
|
+
set.add(uiStateController);
|
|
7572
|
+
}
|
|
7573
|
+
};
|
|
7574
|
+
const onUIStateControllerDestroyed = (uiStateController) => {
|
|
7575
|
+
const { id, name, controlType } = uiStateController;
|
|
7576
|
+
if (id) {
|
|
7577
|
+
controllersById.delete(id);
|
|
7578
|
+
naviJsRegistry.delete(id);
|
|
7579
|
+
}
|
|
7580
|
+
const proxyFor = uiStateController.props["navi-control-proxy-for"];
|
|
7581
|
+
if (proxyFor) {
|
|
7582
|
+
const proxySet = proxyControllersByRealInputId.get(proxyFor);
|
|
7583
|
+
if (proxySet) {
|
|
7584
|
+
proxySet.delete(uiStateController);
|
|
7585
|
+
if (proxySet.size === 0) {
|
|
7586
|
+
proxyControllersByRealInputId.delete(proxyFor);
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
if (
|
|
7591
|
+
controlType === "input" &&
|
|
7592
|
+
uiStateController.controlHostProps.type === "radio" &&
|
|
7593
|
+
name
|
|
7594
|
+
) {
|
|
7595
|
+
const set = radioControllersByName.get(name);
|
|
7596
|
+
if (set) {
|
|
7597
|
+
set.delete(uiStateController);
|
|
7598
|
+
if (set.size === 0) {
|
|
7599
|
+
radioControllersByName.delete(name);
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
};
|
|
7604
|
+
|
|
7605
|
+
/**
|
|
7606
|
+
* Controller-based equivalent of findControlProxyTarget.
|
|
7607
|
+
* Given a proxy controller, returns the real control's controller.
|
|
7608
|
+
* Finds the target by walking the parent controller's children — no DOM queries.
|
|
7609
|
+
* Returns `null` when the controller is not a proxy or the target is not found.
|
|
7610
|
+
*/
|
|
7611
|
+
const findControlProxyTargetController = (controller) => {
|
|
7612
|
+
const proxyFor = controller.controlHostProps["navi-control-proxy-for"];
|
|
7613
|
+
if (!proxyFor) {
|
|
7614
|
+
return null;
|
|
7615
|
+
}
|
|
7616
|
+
return getUIStateControllerById(proxyFor) ?? null;
|
|
7617
|
+
};
|
|
7618
|
+
|
|
7619
|
+
// Reverse-lookup map: real-input id → the proxy controllers that reference it
|
|
7620
|
+
// via `navi-control-proxy-for`. A single control can be represented by several
|
|
7621
|
+
// proxies (an "enable"/"disable" button pair for one radio, for instance), so
|
|
7622
|
+
// each id holds a set. Maintained on create/destroy so lookup is O(1).
|
|
7623
|
+
const proxyControllersByRealInputId = new Map();
|
|
7624
|
+
const findProxyControllers = (realInputId) => {
|
|
7625
|
+
if (!realInputId) {
|
|
7626
|
+
return null;
|
|
7627
|
+
}
|
|
7628
|
+
return proxyControllersByRealInputId.get(realInputId) ?? null;
|
|
7629
|
+
};
|
|
7630
|
+
|
|
7270
7631
|
/**
|
|
7271
7632
|
* DOM utilities for the proxy control pattern.
|
|
7272
7633
|
*
|
|
@@ -7298,6 +7659,7 @@ const findControlRoot = (el) => {
|
|
|
7298
7659
|
* entirely. For now we keep the proxy pattern.
|
|
7299
7660
|
*/
|
|
7300
7661
|
|
|
7662
|
+
|
|
7301
7663
|
/**
|
|
7302
7664
|
* Given a proxy element, returns the real control it represents.
|
|
7303
7665
|
* Returns `null` when `el` is not a proxy.
|
|
@@ -7310,22 +7672,49 @@ const findControlProxyTarget = (el) => {
|
|
|
7310
7672
|
return document.getElementById(proxyFor);
|
|
7311
7673
|
};
|
|
7312
7674
|
|
|
7675
|
+
/**
|
|
7676
|
+
* Given a real control element, returns every proxy that visually represents
|
|
7677
|
+
* it — a control can have more than one (an "enable"/"disable" button pair for
|
|
7678
|
+
* one radio, for instance).
|
|
7679
|
+
*
|
|
7680
|
+
* Answered from the controller registry rather than the document: every proxy
|
|
7681
|
+
* declares itself through the `navi-control-proxy-for` prop, so the registry
|
|
7682
|
+
* knows them all, while asking the document means walking it in full for each
|
|
7683
|
+
* of the (overwhelmingly many) controls that have no proxy at all.
|
|
7684
|
+
*
|
|
7685
|
+
* Returns an empty array when no proxy exists for `el`.
|
|
7686
|
+
*/
|
|
7687
|
+
const findControlProxies = (el) => {
|
|
7688
|
+
if (!el.id) {
|
|
7689
|
+
return [];
|
|
7690
|
+
}
|
|
7691
|
+
const proxyControllerSet = findProxyControllers(el.id);
|
|
7692
|
+
if (!proxyControllerSet) {
|
|
7693
|
+
return [];
|
|
7694
|
+
}
|
|
7695
|
+
const proxyElements = [];
|
|
7696
|
+
for (const proxyController of proxyControllerSet) {
|
|
7697
|
+
const proxyElement = proxyController.ref.current;
|
|
7698
|
+
if (proxyElement) {
|
|
7699
|
+
proxyElements.push(proxyElement);
|
|
7700
|
+
}
|
|
7701
|
+
}
|
|
7702
|
+
return proxyElements;
|
|
7703
|
+
};
|
|
7704
|
+
|
|
7313
7705
|
/**
|
|
7314
7706
|
* Given a real control element, returns the proxy that visually represents it.
|
|
7315
7707
|
*
|
|
7316
|
-
* Use when you need
|
|
7317
|
-
*
|
|
7318
|
-
*
|
|
7708
|
+
* Use when you need a single visible stand-in for the real control — anchoring
|
|
7709
|
+
* a callout, for instance. Anything notifying proxies of a state change wants
|
|
7710
|
+
* `findControlProxies` instead, so a control represented by several of them
|
|
7711
|
+
* updates all of them.
|
|
7319
7712
|
*
|
|
7320
7713
|
* Returns `null` when no proxy exists for `el`.
|
|
7321
7714
|
*/
|
|
7322
7715
|
const findControlProxy = (el) => {
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
}
|
|
7326
|
-
return document.querySelector(
|
|
7327
|
-
`[navi-control-proxy-for="${CSS.escape(el.id)}"]`,
|
|
7328
|
-
);
|
|
7716
|
+
const [firstProxy = null] = findControlProxies(el);
|
|
7717
|
+
return firstProxy;
|
|
7329
7718
|
};
|
|
7330
7719
|
|
|
7331
7720
|
const addInputEffect = (
|
|
@@ -7584,56 +7973,16 @@ const listenInputStateChange = (
|
|
|
7584
7973
|
return teardown;
|
|
7585
7974
|
};
|
|
7586
7975
|
|
|
7587
|
-
const dispatchRequestSetUIState = (element, value, detail) => {
|
|
7588
|
-
const controlHost = findControlHost(element) || element;
|
|
7589
|
-
return dispatchInternalCustomEvent(controlHost, "navi_set_ui_state", {
|
|
7590
|
-
...detail,
|
|
7591
|
-
value,
|
|
7592
|
-
});
|
|
7593
|
-
};
|
|
7594
|
-
const dispatchRequestClearUIState = (element, e) => {
|
|
7595
|
-
const controlHost = findControlHost(element) || element;
|
|
7596
|
-
return dispatchInternalCustomEvent(controlHost, "navi_clear_ui_state", {
|
|
7597
|
-
event: e,
|
|
7598
|
-
});
|
|
7599
|
-
};
|
|
7600
|
-
const dispatchRequestResetUIState = (element, e) => {
|
|
7601
|
-
const controlHost = findControlHost(element) || element;
|
|
7602
|
-
return dispatchInternalCustomEvent(controlHost, "navi_reset_ui_state", {
|
|
7603
|
-
event: e,
|
|
7604
|
-
});
|
|
7605
|
-
};
|
|
7606
|
-
/**
|
|
7607
|
-
* @param {Element} el
|
|
7608
|
-
* @param {{ own?: boolean }} [options] `own`: what the element holds BY ITSELF.
|
|
7609
|
-
* Only a button ever answers differently — one with no value of its own
|
|
7610
|
-
* inherits the value of the control around it, which is what makes
|
|
7611
|
-
* `--navi-send` on a form's button be about that form. Something asking what
|
|
7612
|
-
* THIS element says (a travel command reading what the travel is about) wants
|
|
7613
|
-
* the own value and would otherwise be handed the surrounding control's.
|
|
7614
|
-
*/
|
|
7615
|
-
const getUIStateFromElement = (el, { own } = {}) => {
|
|
7616
|
-
let uiState;
|
|
7617
|
-
dispatchInternalCustomEvent(el, "navi_get_ui_state", {
|
|
7618
|
-
own,
|
|
7619
|
-
respondWith: (v) => {
|
|
7620
|
-
uiState = v;
|
|
7621
|
-
},
|
|
7622
|
-
});
|
|
7623
|
-
return uiState;
|
|
7624
|
-
};
|
|
7625
|
-
|
|
7626
7976
|
const requestPseudoStateCheck = (element, detail) => {
|
|
7627
7977
|
dispatchInternalCustomEvent(
|
|
7628
7978
|
element,
|
|
7629
7979
|
"navi_pseudo_state_request_check",
|
|
7630
7980
|
detail,
|
|
7631
7981
|
);
|
|
7632
|
-
// When a control has
|
|
7633
|
-
// radio with `navi-control-proxy-for`), re-check
|
|
7634
|
-
//
|
|
7635
|
-
const proxy
|
|
7636
|
-
if (proxy) {
|
|
7982
|
+
// When a control has visible proxies mirroring its state (e.g. selectable
|
|
7983
|
+
// radio with `navi-control-proxy-for`), re-check them too so they stay in
|
|
7984
|
+
// sync with the real control.
|
|
7985
|
+
for (const proxy of findControlProxies(element)) {
|
|
7637
7986
|
dispatchInternalCustomEvent(
|
|
7638
7987
|
proxy,
|
|
7639
7988
|
"navi_pseudo_state_request_check",
|
|
@@ -7686,8 +8035,7 @@ definePseudoClass(":hover", {
|
|
|
7686
8035
|
return () => {};
|
|
7687
8036
|
}
|
|
7688
8037
|
const recheckProxy = (e) => {
|
|
7689
|
-
const proxy
|
|
7690
|
-
if (proxy) {
|
|
8038
|
+
for (const proxy of findControlProxies(el)) {
|
|
7691
8039
|
requestPseudoStateCheck(proxy, { event: e });
|
|
7692
8040
|
}
|
|
7693
8041
|
};
|
|
@@ -7754,9 +8102,10 @@ definePseudoClass(":hover", {
|
|
|
7754
8102
|
if (el.matches(":hover")) {
|
|
7755
8103
|
return true;
|
|
7756
8104
|
}
|
|
7757
|
-
const proxy
|
|
7758
|
-
|
|
7759
|
-
|
|
8105
|
+
for (const proxy of findControlProxies(el)) {
|
|
8106
|
+
if (proxy.matches(":hover")) {
|
|
8107
|
+
return true;
|
|
8108
|
+
}
|
|
7760
8109
|
}
|
|
7761
8110
|
return false;
|
|
7762
8111
|
},
|
|
@@ -8118,23 +8467,30 @@ const isKeyboardModality = () => keyboardNavigationUsed;
|
|
|
8118
8467
|
requireFocusVisible
|
|
8119
8468
|
? isMatchingFocusVisible(target)
|
|
8120
8469
|
: target.matches(":focus");
|
|
8470
|
+
// Both branches of isFocusedTarget rest on :focus / :focus-visible, and only
|
|
8471
|
+
// one element in the document can match those: document.activeElement. So
|
|
8472
|
+
// the single controller worth testing is known upfront — asking the document
|
|
8473
|
+
// for every [aria-controls] would collect candidates that cannot qualify,
|
|
8474
|
+
// once per element and again on every re-check, on a document each new
|
|
8475
|
+
// element makes bigger.
|
|
8121
8476
|
const isControlledBy = (target) => {
|
|
8122
8477
|
const id = target.id;
|
|
8123
8478
|
if (!id) {
|
|
8124
8479
|
return false;
|
|
8125
8480
|
}
|
|
8126
|
-
const
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
// native (:focus-within) — no need to inherit it.
|
|
8130
|
-
if (target.contains(controller)) {
|
|
8131
|
-
continue;
|
|
8132
|
-
}
|
|
8133
|
-
if (isFocusedTarget(controller)) {
|
|
8134
|
-
return true;
|
|
8135
|
-
}
|
|
8481
|
+
const activeElement = document.activeElement;
|
|
8482
|
+
if (!activeElement || activeElement === document.body) {
|
|
8483
|
+
return false;
|
|
8136
8484
|
}
|
|
8137
|
-
|
|
8485
|
+
if (!activeElement.matches(`[aria-controls~="${id}"]`)) {
|
|
8486
|
+
return false;
|
|
8487
|
+
}
|
|
8488
|
+
// A controller inside the element it controls means focus is already
|
|
8489
|
+
// native (:focus-within) — nothing to inherit.
|
|
8490
|
+
if (target.contains(activeElement)) {
|
|
8491
|
+
return false;
|
|
8492
|
+
}
|
|
8493
|
+
return isFocusedTarget(activeElement);
|
|
8138
8494
|
};
|
|
8139
8495
|
if (isControlledBy(el)) {
|
|
8140
8496
|
return true;
|
|
@@ -8461,8 +8817,7 @@ const initPseudoStyles = (
|
|
|
8461
8817
|
}
|
|
8462
8818
|
// When this element's state changes, notify any proxy element that mirrors it
|
|
8463
8819
|
// so it can re-check and visually reflect the new state.
|
|
8464
|
-
const proxy
|
|
8465
|
-
if (proxy) {
|
|
8820
|
+
for (const proxy of findControlProxies(element)) {
|
|
8466
8821
|
requestPseudoStateCheck(proxy, {});
|
|
8467
8822
|
}
|
|
8468
8823
|
};
|
|
@@ -9760,7 +10115,7 @@ const setupNetworkMonitoring = () => {
|
|
|
9760
10115
|
};
|
|
9761
10116
|
setupNetworkMonitoring();
|
|
9762
10117
|
|
|
9763
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
10118
|
+
installImportMetaCssBuild(import.meta);const css$Z = /* css */`
|
|
9764
10119
|
.navi_loading_indicator_fluid_container {
|
|
9765
10120
|
position: relative;
|
|
9766
10121
|
display: flex;
|
|
@@ -9792,7 +10147,7 @@ const LoadingIndicatorFluid = ({
|
|
|
9792
10147
|
visuallyHidden,
|
|
9793
10148
|
...rest
|
|
9794
10149
|
}) => {
|
|
9795
|
-
import.meta.css = [css$
|
|
10150
|
+
import.meta.css = [css$Z, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
|
|
9796
10151
|
const ref = useRef(null);
|
|
9797
10152
|
// The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
|
|
9798
10153
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
@@ -9997,7 +10352,7 @@ const LoadingRectangleSvg = ({
|
|
|
9997
10352
|
});
|
|
9998
10353
|
};
|
|
9999
10354
|
|
|
10000
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
10355
|
+
installImportMetaCssBuild(import.meta);const css$Y = /* css */`
|
|
10001
10356
|
.navi_loading_outline_wrapper {
|
|
10002
10357
|
position: absolute;
|
|
10003
10358
|
/* Controls place the outline slightly outside their box, right on top of
|
|
@@ -10034,7 +10389,7 @@ installImportMetaCssBuild(import.meta);const css$X = /* css */`
|
|
|
10034
10389
|
}
|
|
10035
10390
|
`;
|
|
10036
10391
|
const LoadingOutline = props => {
|
|
10037
|
-
import.meta.css = [css$
|
|
10392
|
+
import.meta.css = [css$Y, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
|
|
10038
10393
|
if (props.containerRef) {
|
|
10039
10394
|
const container = props.containerRef.current;
|
|
10040
10395
|
if (!container) {
|
|
@@ -10368,7 +10723,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
|
|
|
10368
10723
|
};
|
|
10369
10724
|
|
|
10370
10725
|
installImportMetaCssBuild(import.meta);// https://jsfiddle.net/v5xzJ/4/
|
|
10371
|
-
const css$
|
|
10726
|
+
const css$X = /* css */`
|
|
10372
10727
|
@layer navi {
|
|
10373
10728
|
.navi_text {
|
|
10374
10729
|
&[data-skeleton] {
|
|
@@ -10874,7 +11229,7 @@ const TextShrinkWrap = props => {
|
|
|
10874
11229
|
});
|
|
10875
11230
|
};
|
|
10876
11231
|
const TextUI = props => {
|
|
10877
|
-
import.meta.css = [css$
|
|
11232
|
+
import.meta.css = [css$X, "@jsenv/navi/src/text/text.jsx"];
|
|
10878
11233
|
let {
|
|
10879
11234
|
ref,
|
|
10880
11235
|
spacing,
|
|
@@ -13191,310 +13546,6 @@ const useActionStatus = (action) => {
|
|
|
13191
13546
|
};
|
|
13192
13547
|
};
|
|
13193
13548
|
|
|
13194
|
-
/**
|
|
13195
|
-
* Converts a JS value into the form expected by the browser DOM property for a
|
|
13196
|
-
* given control type/input type combination.
|
|
13197
|
-
*
|
|
13198
|
-
* For example:
|
|
13199
|
-
* - `datetime-local` inputs expect a local datetime string without timezone
|
|
13200
|
-
* - `number`/`range` inputs expect a numeric string or number
|
|
13201
|
-
* - `color` inputs require a non-empty hex string (falls back to `#000000`)
|
|
13202
|
-
* - All other inputs receive the value as-is (undefined → "")
|
|
13203
|
-
*
|
|
13204
|
-
* Returns either the converted value directly, or a converter function when the
|
|
13205
|
-
* conversion depends on the runtime value (e.g. plain inputs return `asInputValue`).
|
|
13206
|
-
*
|
|
13207
|
-
* @param {any} value - The JS value to convert.
|
|
13208
|
-
* @param {{ controlType: string, type: string }} options
|
|
13209
|
-
* @returns {any} The DOM-compatible value or a converter function.
|
|
13210
|
-
*/
|
|
13211
|
-
const asControlHostValue = (
|
|
13212
|
-
jsValue,
|
|
13213
|
-
{ controlType, type, inputMode },
|
|
13214
|
-
) => {
|
|
13215
|
-
if (controlType === "input" || controlType === "picker") {
|
|
13216
|
-
if (type === "datetime-local") {
|
|
13217
|
-
return asDatetimeLocalString(jsValue);
|
|
13218
|
-
}
|
|
13219
|
-
if (
|
|
13220
|
-
type === "number" ||
|
|
13221
|
-
type === "range" ||
|
|
13222
|
-
inputMode === "numeric" ||
|
|
13223
|
-
inputMode === "decimal"
|
|
13224
|
-
) {
|
|
13225
|
-
return asNumberString(jsValue);
|
|
13226
|
-
}
|
|
13227
|
-
if (type === "color") {
|
|
13228
|
-
return asColorString(jsValue);
|
|
13229
|
-
}
|
|
13230
|
-
return asInputValue(jsValue);
|
|
13231
|
-
}
|
|
13232
|
-
return jsValue;
|
|
13233
|
-
};
|
|
13234
|
-
// As explained in https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/datetime-local#setting_timezones
|
|
13235
|
-
// datetime-local does not support timezones
|
|
13236
|
-
const asDatetimeLocalString = (dateTimeString) => {
|
|
13237
|
-
const date = new Date(dateTimeString);
|
|
13238
|
-
if (isNaN(date.getTime())) {
|
|
13239
|
-
return dateTimeString;
|
|
13240
|
-
}
|
|
13241
|
-
const year = date.getFullYear();
|
|
13242
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
13243
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
13244
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
13245
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
13246
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
13247
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
13248
|
-
};
|
|
13249
|
-
const asNumberString = (jsValue) => {
|
|
13250
|
-
if (jsValue === undefined) {
|
|
13251
|
-
return "";
|
|
13252
|
-
}
|
|
13253
|
-
return jsValue;
|
|
13254
|
-
};
|
|
13255
|
-
// Browser requires a non-empty value for <input type="color">.
|
|
13256
|
-
// When our logical value is empty we give it #000000 so it doesn't choke.
|
|
13257
|
-
// The UI uses the original (possibly empty) value to show the checkerboard.
|
|
13258
|
-
const asColorString = (jsValue) => {
|
|
13259
|
-
return jsValue || "#000000";
|
|
13260
|
-
};
|
|
13261
|
-
const asInputValue = (jsValue) => {
|
|
13262
|
-
if (jsValue === undefined) {
|
|
13263
|
-
return "";
|
|
13264
|
-
}
|
|
13265
|
-
return jsValue;
|
|
13266
|
-
};
|
|
13267
|
-
|
|
13268
|
-
/**
|
|
13269
|
-
* Reads the current logical JS value from a control host DOM element.
|
|
13270
|
-
*
|
|
13271
|
-
* Handles all navi control host element types:
|
|
13272
|
-
* - `<button>` — reads via `navi_get_value` custom event, falls back to `button.value`
|
|
13273
|
-
* - `<input type="number|range">` — parses as a number, returns `undefined` when empty
|
|
13274
|
-
* - `<input type="checkbox|radio">` — returns `undefined` when unchecked, otherwise reads
|
|
13275
|
-
* via `navi_get_value` custom event (to preserve the original JS type of the value prop)
|
|
13276
|
-
* - `<input type="datetime-local">` — converts the local datetime string to an ISO 8601 string
|
|
13277
|
-
* - `<input type="navi_picker">` — delegates to the controller via `navi_get_ui_state`
|
|
13278
|
-
* - All other inputs — returns `input.value` as a string
|
|
13279
|
-
*
|
|
13280
|
-
* @param {HTMLElement} controlHost - The control host DOM element to read from.
|
|
13281
|
-
* @returns {any} The current logical value of the control.
|
|
13282
|
-
*/
|
|
13283
|
-
const readControlValue = (controlHost) => {
|
|
13284
|
-
if (
|
|
13285
|
-
controlHost.tagName === "BUTTON" ||
|
|
13286
|
-
controlHost.getAttribute("role") === "button"
|
|
13287
|
-
) {
|
|
13288
|
-
return readValueFromButton(controlHost);
|
|
13289
|
-
}
|
|
13290
|
-
if (controlHost.tagName === "INPUT") {
|
|
13291
|
-
// important: input.type = "navi_js"; followed by input.type; returns "text"
|
|
13292
|
-
// so use getAttribute
|
|
13293
|
-
const type = controlHost.getAttribute("type");
|
|
13294
|
-
|
|
13295
|
-
if (
|
|
13296
|
-
type === "number" ||
|
|
13297
|
-
type === "range" ||
|
|
13298
|
-
controlHost.inputMode === "numeric" ||
|
|
13299
|
-
controlHost.inputMode === "decimal"
|
|
13300
|
-
) {
|
|
13301
|
-
return readNumberFromInput(controlHost);
|
|
13302
|
-
}
|
|
13303
|
-
if (type === "color") {
|
|
13304
|
-
return readValueFromControlHost(controlHost);
|
|
13305
|
-
}
|
|
13306
|
-
if (type === "checkbox" || type === "radio") {
|
|
13307
|
-
return readValueFromCheckableInput(controlHost);
|
|
13308
|
-
}
|
|
13309
|
-
if (type === "datetime-local") {
|
|
13310
|
-
return readDatetimeLocalFromInput(controlHost);
|
|
13311
|
-
}
|
|
13312
|
-
if (type === "navi_js") {
|
|
13313
|
-
return getUIStateFromElement(controlHost);
|
|
13314
|
-
}
|
|
13315
|
-
return readValueFromInput(controlHost);
|
|
13316
|
-
}
|
|
13317
|
-
if (controlHost.hasAttribute("navi-control-host")) {
|
|
13318
|
-
// Non-button, non-input navi controls (e.g. Badge.Button rendered as span)
|
|
13319
|
-
return readValueFromControlHost(controlHost);
|
|
13320
|
-
}
|
|
13321
|
-
return readValueFromElement(controlHost);
|
|
13322
|
-
};
|
|
13323
|
-
const readValueFromControlHost = (controlHost) => {
|
|
13324
|
-
return readValueFromNaviCustomEvent(controlHost, controlHost.value);
|
|
13325
|
-
};
|
|
13326
|
-
const readValueFromButton = (button) => {
|
|
13327
|
-
return readValueFromControlHost(button);
|
|
13328
|
-
};
|
|
13329
|
-
const readDatetimeLocalFromInput = (input) => {
|
|
13330
|
-
const localDateTimeString = input.value;
|
|
13331
|
-
if (localDateTimeString === "") {
|
|
13332
|
-
return "";
|
|
13333
|
-
}
|
|
13334
|
-
const localDate = new Date(localDateTimeString);
|
|
13335
|
-
if (isNaN(localDate.getTime())) {
|
|
13336
|
-
return localDateTimeString;
|
|
13337
|
-
}
|
|
13338
|
-
return localDate.toISOString();
|
|
13339
|
-
};
|
|
13340
|
-
const readNumberFromInput = (input) => {
|
|
13341
|
-
const numberString = input.value;
|
|
13342
|
-
if (numberString === "") {
|
|
13343
|
-
return "";
|
|
13344
|
-
}
|
|
13345
|
-
const asNumber = Number(numberString);
|
|
13346
|
-
if (isNaN(asNumber)) {
|
|
13347
|
-
return numberString;
|
|
13348
|
-
}
|
|
13349
|
-
return asNumber;
|
|
13350
|
-
};
|
|
13351
|
-
const readValueFromCheckableInput = (input) => {
|
|
13352
|
-
const checked = input.checked;
|
|
13353
|
-
if (!checked) {
|
|
13354
|
-
return undefined;
|
|
13355
|
-
}
|
|
13356
|
-
return readValueFromControlHost(input);
|
|
13357
|
-
};
|
|
13358
|
-
const readValueFromInput = (input) => {
|
|
13359
|
-
const value = input.value;
|
|
13360
|
-
return value;
|
|
13361
|
-
};
|
|
13362
|
-
const readValueFromElement = (element) => {
|
|
13363
|
-
const value = element.value;
|
|
13364
|
-
return value;
|
|
13365
|
-
};
|
|
13366
|
-
const readValueFromNaviCustomEvent = (field, fallback) => {
|
|
13367
|
-
// prefer the value given as prop (respect original type, browser would convert to string)
|
|
13368
|
-
let responded;
|
|
13369
|
-
let value;
|
|
13370
|
-
dispatchCustomEvent(field, "navi_get_value", {
|
|
13371
|
-
respondWith: (jsValue) => {
|
|
13372
|
-
responded = true;
|
|
13373
|
-
value = jsValue;
|
|
13374
|
-
},
|
|
13375
|
-
});
|
|
13376
|
-
if (responded) {
|
|
13377
|
-
return value;
|
|
13378
|
-
}
|
|
13379
|
-
return fallback;
|
|
13380
|
-
};
|
|
13381
|
-
|
|
13382
|
-
// In-memory registry of all mounted ui state controllers keyed by their id.
|
|
13383
|
-
// Allows direct controller access without dispatching DOM events — used by external
|
|
13384
|
-
// callers (e.g. selectable_list) to call setUIState by id instead of via the DOM.
|
|
13385
|
-
const controllersById = new Map();
|
|
13386
|
-
|
|
13387
|
-
// In-memory registry for radio controllers, keyed by input name.
|
|
13388
|
-
// Allows radio sibling unchecking without querying the DOM — necessary when
|
|
13389
|
-
// items are virtualized and their DOM element may not exist at the time.
|
|
13390
|
-
// Form scoping is reproduced by comparing parentUIStateController references.
|
|
13391
|
-
const radioControllersByName = new Map();
|
|
13392
|
-
|
|
13393
|
-
// Registry for non-serializable JS values that cannot be written to DOM attributes as-is.
|
|
13394
|
-
// When a value is an object/array, we store it here and write a reference string to the DOM
|
|
13395
|
-
// instead of "[object Object]". Console-inspectable via window.__navi_js('id').
|
|
13396
|
-
// The controller id is used as key — if the controller has no id, the value is not registered.
|
|
13397
|
-
const naviJsRegistry = new Map();
|
|
13398
|
-
|
|
13399
|
-
const getUIStateControllerById = (id) => controllersById.get(id);
|
|
13400
|
-
const getRadioSiblings = (radioUIStateController) => {
|
|
13401
|
-
const siblings = radioControllersByName.get(radioUIStateController.name);
|
|
13402
|
-
return siblings;
|
|
13403
|
-
};
|
|
13404
|
-
|
|
13405
|
-
const toDomValue = (jsValue, { controlType, id, type, inputMode }) => {
|
|
13406
|
-
const domValue = asControlHostValue(jsValue, {
|
|
13407
|
-
controlType,
|
|
13408
|
-
type,
|
|
13409
|
-
inputMode,
|
|
13410
|
-
});
|
|
13411
|
-
if (isSerializableAsDomValue(domValue)) {
|
|
13412
|
-
return domValue;
|
|
13413
|
-
}
|
|
13414
|
-
naviJsRegistry.set(id, domValue);
|
|
13415
|
-
return `window.__navi_js('${id}')`;
|
|
13416
|
-
};
|
|
13417
|
-
|
|
13418
|
-
window.__navi_js = (id) => naviJsRegistry.get(id);
|
|
13419
|
-
const isSerializableAsDomValue = (value) => {
|
|
13420
|
-
if (value === null || value === undefined) {
|
|
13421
|
-
return true;
|
|
13422
|
-
}
|
|
13423
|
-
const type = typeof value;
|
|
13424
|
-
return type === "string" || type === "number" || type === "boolean";
|
|
13425
|
-
};
|
|
13426
|
-
|
|
13427
|
-
const onUIStateControllerCreated = (uiStateController) => {
|
|
13428
|
-
const { id, name, controlType } = uiStateController;
|
|
13429
|
-
if (id) {
|
|
13430
|
-
controllersById.set(id, uiStateController);
|
|
13431
|
-
}
|
|
13432
|
-
const proxyFor = uiStateController.props["navi-control-proxy-for"];
|
|
13433
|
-
if (proxyFor) {
|
|
13434
|
-
proxyControllerByRealInputId.set(proxyFor, uiStateController);
|
|
13435
|
-
}
|
|
13436
|
-
if (
|
|
13437
|
-
controlType === "input" &&
|
|
13438
|
-
uiStateController.props.type === "radio" &&
|
|
13439
|
-
name
|
|
13440
|
-
) {
|
|
13441
|
-
let set = radioControllersByName.get(name);
|
|
13442
|
-
if (!set) {
|
|
13443
|
-
set = new Set();
|
|
13444
|
-
radioControllersByName.set(name, set);
|
|
13445
|
-
}
|
|
13446
|
-
set.add(uiStateController);
|
|
13447
|
-
}
|
|
13448
|
-
};
|
|
13449
|
-
const onUIStateControllerDestroyed = (uiStateController) => {
|
|
13450
|
-
const { id, name, controlType } = uiStateController;
|
|
13451
|
-
if (id) {
|
|
13452
|
-
controllersById.delete(id);
|
|
13453
|
-
naviJsRegistry.delete(id);
|
|
13454
|
-
}
|
|
13455
|
-
const proxyFor = uiStateController.props["navi-control-proxy-for"];
|
|
13456
|
-
if (proxyFor) {
|
|
13457
|
-
proxyControllerByRealInputId.delete(proxyFor);
|
|
13458
|
-
}
|
|
13459
|
-
if (
|
|
13460
|
-
controlType === "input" &&
|
|
13461
|
-
uiStateController.controlHostProps.type === "radio" &&
|
|
13462
|
-
name
|
|
13463
|
-
) {
|
|
13464
|
-
const set = radioControllersByName.get(name);
|
|
13465
|
-
if (set) {
|
|
13466
|
-
set.delete(uiStateController);
|
|
13467
|
-
if (set.size === 0) {
|
|
13468
|
-
radioControllersByName.delete(name);
|
|
13469
|
-
}
|
|
13470
|
-
}
|
|
13471
|
-
}
|
|
13472
|
-
};
|
|
13473
|
-
|
|
13474
|
-
/**
|
|
13475
|
-
* Controller-based equivalent of findControlProxyTarget.
|
|
13476
|
-
* Given a proxy controller, returns the real control's controller.
|
|
13477
|
-
* Finds the target by walking the parent controller's children — no DOM queries.
|
|
13478
|
-
* Returns `null` when the controller is not a proxy or the target is not found.
|
|
13479
|
-
*/
|
|
13480
|
-
const findControlProxyTargetController = (controller) => {
|
|
13481
|
-
const proxyFor = controller.controlHostProps["navi-control-proxy-for"];
|
|
13482
|
-
if (!proxyFor) {
|
|
13483
|
-
return null;
|
|
13484
|
-
}
|
|
13485
|
-
return getUIStateControllerById(proxyFor) ?? null;
|
|
13486
|
-
};
|
|
13487
|
-
|
|
13488
|
-
// Reverse-lookup map: real-input id → proxy controller that references it via
|
|
13489
|
-
// `navi-control-proxy-for`. Maintained on create/destroy so lookup is O(1).
|
|
13490
|
-
const proxyControllerByRealInputId = new Map();
|
|
13491
|
-
const findProxyController = (realInputId) => {
|
|
13492
|
-
if (!realInputId) {
|
|
13493
|
-
return null;
|
|
13494
|
-
}
|
|
13495
|
-
return proxyControllerByRealInputId.get(realInputId) ?? null;
|
|
13496
|
-
};
|
|
13497
|
-
|
|
13498
13549
|
const CONSTRAINT_NAME_TO_PROP = {
|
|
13499
13550
|
disabled: "disabledMessage",
|
|
13500
13551
|
required: "requiredMessage",
|
|
@@ -13595,7 +13646,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
13595
13646
|
* - Arrow automatically shows when pointing at a valid anchor element
|
|
13596
13647
|
* - Centers in viewport when no anchor element provided or anchor is too big
|
|
13597
13648
|
*/
|
|
13598
|
-
const css$
|
|
13649
|
+
const css$W = /* css */`
|
|
13599
13650
|
@layer navi {
|
|
13600
13651
|
.navi_callout {
|
|
13601
13652
|
/* A callout is parented to what it explains, so it inherits from it — and
|
|
@@ -13834,7 +13885,7 @@ const openCallout = (message, {
|
|
|
13834
13885
|
skipFocus = false,
|
|
13835
13886
|
debug = () => {}
|
|
13836
13887
|
} = {}) => {
|
|
13837
|
-
import.meta.css = [css$
|
|
13888
|
+
import.meta.css = [css$W, "@jsenv/navi/src/control/rules/callout/callout.js"];
|
|
13838
13889
|
if (debug === true) {
|
|
13839
13890
|
debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
|
|
13840
13891
|
}
|
|
@@ -14435,12 +14486,12 @@ const positionCallout = (calloutElement, anchorElement, {
|
|
|
14435
14486
|
} else if (anchorElement.hasAttribute("data-callout-point-to-content-box")) {
|
|
14436
14487
|
alignToAnchorBox = "content-box";
|
|
14437
14488
|
} else {
|
|
14438
|
-
// Smart default:
|
|
14489
|
+
// Smart default: form controls and buttons are tight boxes where border-box makes sense.
|
|
14439
14490
|
// For everything else (labels, divs, fieldsets…) content-box maximizes the chance
|
|
14440
14491
|
// the arrow points at visible text rather than the outer padding/border.
|
|
14441
14492
|
const controHost = findControlHost(anchorElement) || anchorElement;
|
|
14442
14493
|
const tagName = controHost.tagName;
|
|
14443
|
-
if (tagName === "INPUT" || tagName === "BUTTON" || tagName === "FIELDSET") {
|
|
14494
|
+
if (tagName === "INPUT" || tagName === "SELECT" || tagName === "BUTTON" || tagName === "FIELDSET") {
|
|
14444
14495
|
alignToAnchorBox = "border-box";
|
|
14445
14496
|
} else {
|
|
14446
14497
|
alignToAnchorBox = "content-box";
|
|
@@ -17916,6 +17967,10 @@ const isInertOnClick = (element) => {
|
|
|
17916
17967
|
if (tagName === "BUTTON") {
|
|
17917
17968
|
return element.type === "button";
|
|
17918
17969
|
}
|
|
17970
|
+
if (tagName === "SELECT") {
|
|
17971
|
+
// The click opens the option list; cancelling it leaves the select shut.
|
|
17972
|
+
return false;
|
|
17973
|
+
}
|
|
17919
17974
|
return true;
|
|
17920
17975
|
};
|
|
17921
17976
|
|
|
@@ -19734,6 +19789,78 @@ createContext();
|
|
|
19734
19789
|
const ActionContext = createContext();
|
|
19735
19790
|
const ActionRequesterContext = createContext();
|
|
19736
19791
|
|
|
19792
|
+
/**
|
|
19793
|
+
* How a control tells the labels pointing at it what it is (disabled, readOnly,
|
|
19794
|
+
* required) and when it goes away.
|
|
19795
|
+
*
|
|
19796
|
+
* A label linked to its control by id has no DOM relationship to walk: the two
|
|
19797
|
+
* only know each other's id. A non-native control has no `element.labels`
|
|
19798
|
+
* either, so the only way to go from the control to its labels through the DOM
|
|
19799
|
+
* is to ask the whole document for `label[for="…"]` — once per control, on a
|
|
19800
|
+
* document that every mounted control makes bigger.
|
|
19801
|
+
*
|
|
19802
|
+
* The link is held here instead. The control publishes its state under its own
|
|
19803
|
+
* id; a label subscribes to the id it points at. Order does not matter —
|
|
19804
|
+
* whichever mounts second finds what the first left, so a label written after
|
|
19805
|
+
* its control is told just as much as one written before it.
|
|
19806
|
+
*
|
|
19807
|
+
* A label that WRAPS its control has the native relationship already
|
|
19808
|
+
* (`element.labels`) and is notified through a DOM event instead — see
|
|
19809
|
+
* `getAssociatedLabels` in control_hooks.jsx. Both channels carry the same
|
|
19810
|
+
* values and land on the same setters, so a control reachable through both is
|
|
19811
|
+
* simply told twice.
|
|
19812
|
+
*/
|
|
19813
|
+
|
|
19814
|
+
const stateByControlId = new Map();
|
|
19815
|
+
const callbackSetByControlId = new Map();
|
|
19816
|
+
|
|
19817
|
+
const publishControlStateToLabels = (controlId, controlState) => {
|
|
19818
|
+
if (!controlId) {
|
|
19819
|
+
return;
|
|
19820
|
+
}
|
|
19821
|
+
stateByControlId.set(controlId, controlState);
|
|
19822
|
+
const callbackSet = callbackSetByControlId.get(controlId);
|
|
19823
|
+
if (callbackSet) {
|
|
19824
|
+
for (const callback of callbackSet) {
|
|
19825
|
+
callback(controlState);
|
|
19826
|
+
}
|
|
19827
|
+
}
|
|
19828
|
+
};
|
|
19829
|
+
|
|
19830
|
+
const unpublishControlStateToLabels = (controlId) => {
|
|
19831
|
+
if (!controlId) {
|
|
19832
|
+
return;
|
|
19833
|
+
}
|
|
19834
|
+
stateByControlId.delete(controlId);
|
|
19835
|
+
const callbackSet = callbackSetByControlId.get(controlId);
|
|
19836
|
+
if (callbackSet) {
|
|
19837
|
+
for (const callback of callbackSet) {
|
|
19838
|
+
callback(null);
|
|
19839
|
+
}
|
|
19840
|
+
}
|
|
19841
|
+
};
|
|
19842
|
+
|
|
19843
|
+
/**
|
|
19844
|
+
* Subscribes to the state published by the control identified by `controlId`.
|
|
19845
|
+
* The callback is called right away with the current state (or `null` when no
|
|
19846
|
+
* such control is mounted), then on every change. Returns the teardown.
|
|
19847
|
+
*/
|
|
19848
|
+
const subscribeToControlState = (controlId, callback) => {
|
|
19849
|
+
let callbackSet = callbackSetByControlId.get(controlId);
|
|
19850
|
+
if (!callbackSet) {
|
|
19851
|
+
callbackSet = new Set();
|
|
19852
|
+
callbackSetByControlId.set(controlId, callbackSet);
|
|
19853
|
+
}
|
|
19854
|
+
callbackSet.add(callback);
|
|
19855
|
+
callback(stateByControlId.get(controlId) ?? null);
|
|
19856
|
+
return () => {
|
|
19857
|
+
callbackSet.delete(callback);
|
|
19858
|
+
if (callbackSet.size === 0) {
|
|
19859
|
+
callbackSetByControlId.delete(controlId);
|
|
19860
|
+
}
|
|
19861
|
+
};
|
|
19862
|
+
};
|
|
19863
|
+
|
|
19737
19864
|
/**
|
|
19738
19865
|
* Named presets for the `charGuard` prop.
|
|
19739
19866
|
* Each value is a regex character class (including the [ ] delimiters).
|
|
@@ -20337,15 +20464,17 @@ const useUIStateController = (
|
|
|
20337
20464
|
// later through a React re-render — visible as e.g. two radios
|
|
20338
20465
|
// appearing checked at once between the real input update and the
|
|
20339
20466
|
// next render (radio_sibling_uncheck case).
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
const
|
|
20345
|
-
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20467
|
+
// Every mounted controller that declared itself as a proxy for this
|
|
20468
|
+
// one. Communicates directly to them — no DOM query needed.
|
|
20469
|
+
const proxyControllerSet = findProxyControllers(s.id);
|
|
20470
|
+
if (proxyControllerSet) {
|
|
20471
|
+
for (const proxyController of proxyControllerSet) {
|
|
20472
|
+
const mirrorEvent = new CustomEvent("proxy_mirror_state", {
|
|
20473
|
+
detail: {},
|
|
20474
|
+
});
|
|
20475
|
+
chainEvent(mirrorEvent, e);
|
|
20476
|
+
proxyController.setUIState(newUIState, mirrorEvent);
|
|
20477
|
+
}
|
|
20349
20478
|
}
|
|
20350
20479
|
}
|
|
20351
20480
|
if (isInternalEvent(e)) {
|
|
@@ -20462,8 +20591,18 @@ const useUIStateController = (
|
|
|
20462
20591
|
);
|
|
20463
20592
|
syntheticInputFired = true;
|
|
20464
20593
|
}
|
|
20594
|
+
} else if (el.tagName === "SELECT") {
|
|
20595
|
+
debugUIState(
|
|
20596
|
+
e,
|
|
20597
|
+
`dispatching synthetic input event for select "${newUIState}"`,
|
|
20598
|
+
);
|
|
20599
|
+
// A plain Event, not an InputEvent: that is what the browser
|
|
20600
|
+
// itself fires on a select, and input_effect reads the value off
|
|
20601
|
+
// the element anyway.
|
|
20602
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
20603
|
+
syntheticInputFired = true;
|
|
20465
20604
|
}
|
|
20466
|
-
// TODO:
|
|
20605
|
+
// TODO: textarea
|
|
20467
20606
|
}
|
|
20468
20607
|
}
|
|
20469
20608
|
if (!syntheticInputFired) {
|
|
@@ -21952,6 +22091,61 @@ const useControlProps = (props, {
|
|
|
21952
22091
|
}
|
|
21953
22092
|
};
|
|
21954
22093
|
}
|
|
22094
|
+
const enterToSend = e => {
|
|
22095
|
+
const control = e.currentTarget;
|
|
22096
|
+
return {
|
|
22097
|
+
name: "enter to send closest control group",
|
|
22098
|
+
bypassInteractivity: true,
|
|
22099
|
+
// allow to dispatch --navi-send even if readonly
|
|
22100
|
+
allowed: () => triggerNaviCommand(control, "--navi-send", e),
|
|
22101
|
+
// prevent dispatching click as result of this enter
|
|
22102
|
+
prevented: () => e.preventDefault()
|
|
22103
|
+
};
|
|
22104
|
+
};
|
|
22105
|
+
if (controlType === "select") {
|
|
22106
|
+
return {
|
|
22107
|
+
keyDown: e => {
|
|
22108
|
+
if (e.key === "Enter") {
|
|
22109
|
+
return enterToSend(e);
|
|
22110
|
+
}
|
|
22111
|
+
if (getKeyboardEventDefaultAction(e) === "activate") {
|
|
22112
|
+
// Space opens the list. Nothing has been chosen at that point, so
|
|
22113
|
+
// there is no ui action to trigger — only whether the list is
|
|
22114
|
+
// allowed to open at all.
|
|
22115
|
+
return {
|
|
22116
|
+
name: "keydown to open the option list",
|
|
22117
|
+
prevented: () => e.preventDefault()
|
|
22118
|
+
};
|
|
22119
|
+
}
|
|
22120
|
+
return null;
|
|
22121
|
+
},
|
|
22122
|
+
mouseDown: e => {
|
|
22123
|
+
// Same as the keydown above: opening the list is the interaction to
|
|
22124
|
+
// ask about, and refusing it is what keeps a read-only select shut.
|
|
22125
|
+
return {
|
|
22126
|
+
name: "mousedown to open the option list",
|
|
22127
|
+
prevented: () => e.preventDefault()
|
|
22128
|
+
};
|
|
22129
|
+
},
|
|
22130
|
+
input: e => {
|
|
22131
|
+
return {
|
|
22132
|
+
name: "input",
|
|
22133
|
+
allowed: () => syncUIStateWithDOM(e),
|
|
22134
|
+
// The keyboard moves the selection on a closed select, and the
|
|
22135
|
+
// platform's own list can hand back a choice, both before anything
|
|
22136
|
+
// was asked. A refused change puts the element back on the state it
|
|
22137
|
+
// never left.
|
|
22138
|
+
prevented: () => syncDomState(uiStateController.uiState, e)
|
|
22139
|
+
};
|
|
22140
|
+
},
|
|
22141
|
+
naviChange: e => {
|
|
22142
|
+
return {
|
|
22143
|
+
name: "navi_change",
|
|
22144
|
+
allowed: () => requestActionOnAllowed(e)
|
|
22145
|
+
};
|
|
22146
|
+
}
|
|
22147
|
+
};
|
|
22148
|
+
}
|
|
21955
22149
|
const keyDownDefaultOnInput = e => {
|
|
21956
22150
|
if (e.key === "Enter") {
|
|
21957
22151
|
if (actionDebounce) {
|
|
@@ -21960,15 +22154,7 @@ const useControlProps = (props, {
|
|
|
21960
22154
|
// Don't propagate to --navi-send, which would cause a double action call.
|
|
21961
22155
|
return null;
|
|
21962
22156
|
}
|
|
21963
|
-
|
|
21964
|
-
return {
|
|
21965
|
-
name: "enter on input to send closest control group",
|
|
21966
|
-
bypassInteractivity: true,
|
|
21967
|
-
// allow to dispatch --navi-send even if input is readonly
|
|
21968
|
-
allowed: () => triggerNaviCommand(input, "--navi-send", e),
|
|
21969
|
-
// prevent dispatching click as result of this enter
|
|
21970
|
-
prevented: () => e.preventDefault()
|
|
21971
|
-
};
|
|
22157
|
+
return enterToSend(e);
|
|
21972
22158
|
}
|
|
21973
22159
|
return keyDownDefault(e);
|
|
21974
22160
|
};
|
|
@@ -22364,7 +22550,7 @@ const createControlInfo = (props, {
|
|
|
22364
22550
|
defaultStatePropName = "defaultOpen";
|
|
22365
22551
|
stateInitial = props.open || props.defaultOpen;
|
|
22366
22552
|
value = props.value || "open";
|
|
22367
|
-
} else if (controlType === "picker") {
|
|
22553
|
+
} else if (controlType === "picker" || controlType === "select") {
|
|
22368
22554
|
statePropName = "value";
|
|
22369
22555
|
defaultStatePropName = "defaultValue";
|
|
22370
22556
|
if (Object.hasOwn(props, "value")) {
|
|
@@ -22383,7 +22569,10 @@ const createControlInfo = (props, {
|
|
|
22383
22569
|
stateInitial = undefined;
|
|
22384
22570
|
}
|
|
22385
22571
|
disabledSupported = true;
|
|
22386
|
-
|
|
22572
|
+
// A native <select> has no readonly attribute. What says it is read-only is
|
|
22573
|
+
// aria-readonly plus a refused interaction — see the select reactions in
|
|
22574
|
+
// getDefaultEventReactionDefinitions.
|
|
22575
|
+
readOnlySupported = controlType === "picker" && INPUT_TYPE_SUPPORTING_READONLY_SET.has(typeProp);
|
|
22387
22576
|
}
|
|
22388
22577
|
return {
|
|
22389
22578
|
controlType,
|
|
@@ -22709,39 +22898,47 @@ const useInteractiveProps = (props, {
|
|
|
22709
22898
|
controlHostProps["inert"] = "";
|
|
22710
22899
|
}
|
|
22711
22900
|
}
|
|
22712
|
-
//
|
|
22901
|
+
// Inform any associated label of our state (connected, disabled, readOnly,
|
|
22713
22902
|
// required — a Label with requiredIndicator marks itself from it rather
|
|
22714
|
-
// than being told twice what the control already knows)
|
|
22715
|
-
//
|
|
22716
|
-
// (
|
|
22903
|
+
// than being told twice what the control already knows), through both
|
|
22904
|
+
// channels a label can be reached by: a DOM event on the labels the element
|
|
22905
|
+
// itself hands over (a wrapping <label>, or a label[for] on a native form
|
|
22906
|
+
// element), and a publication under this control's id for the labels that
|
|
22907
|
+
// only know it by that (see control_label_state.js).
|
|
22908
|
+
//
|
|
22909
|
+
// The id is remembered rather than re-read at unmount time: ref.current is
|
|
22910
|
+
// often already null by then, and the labels subscribed under that id would
|
|
22911
|
+
// stay told about a control that no longer exists.
|
|
22912
|
+
const publishedIdRef = useRef(null);
|
|
22717
22913
|
useLayoutEffect(() => {
|
|
22718
22914
|
const element = ref.current;
|
|
22719
22915
|
if (!element) {
|
|
22720
22916
|
return;
|
|
22721
22917
|
}
|
|
22722
|
-
const labels = getAssociatedLabels(element);
|
|
22723
22918
|
const readOnlyForced = element.hasAttribute("data-readonly-forced");
|
|
22724
22919
|
const readOnly = readOnlyForced ? false : readOnlyResolved;
|
|
22725
|
-
|
|
22920
|
+
const controlState = {
|
|
22921
|
+
disabled: disabledResolved,
|
|
22922
|
+
readOnly,
|
|
22923
|
+
required: requiredResolved
|
|
22924
|
+
};
|
|
22925
|
+
for (const label of getAssociatedLabels(element)) {
|
|
22726
22926
|
label.dispatchEvent(new CustomEvent("navi_control_state", {
|
|
22727
|
-
detail:
|
|
22728
|
-
disabled: disabledResolved,
|
|
22729
|
-
readOnly,
|
|
22730
|
-
required: requiredResolved
|
|
22731
|
-
}
|
|
22927
|
+
detail: controlState
|
|
22732
22928
|
}));
|
|
22733
22929
|
}
|
|
22930
|
+
publishedIdRef.current = element.id;
|
|
22931
|
+
publishControlStateToLabels(element.id, controlState);
|
|
22734
22932
|
}, [disabledResolved, readOnlyResolved, requiredResolved, ref]);
|
|
22735
22933
|
useLayoutEffect(() => {
|
|
22736
22934
|
return () => {
|
|
22737
22935
|
const element = ref.current;
|
|
22738
|
-
if (
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
for (const label of labels) {
|
|
22743
|
-
label.dispatchEvent(new CustomEvent("navi_control_disconnected"));
|
|
22936
|
+
if (element) {
|
|
22937
|
+
for (const label of getAssociatedLabels(element)) {
|
|
22938
|
+
label.dispatchEvent(new CustomEvent("navi_control_disconnected"));
|
|
22939
|
+
}
|
|
22744
22940
|
}
|
|
22941
|
+
unpublishControlStateToLabels(publishedIdRef.current);
|
|
22745
22942
|
};
|
|
22746
22943
|
}, []);
|
|
22747
22944
|
}
|
|
@@ -22991,29 +23188,19 @@ const splitControlProps = props => {
|
|
|
22991
23188
|
}
|
|
22992
23189
|
return [controlRootProps, controlHostProps];
|
|
22993
23190
|
};
|
|
23191
|
+
|
|
23192
|
+
// The labels the DOM itself can hand over: a wrapping <label>, or a label[for]
|
|
23193
|
+
// pointing at a native form element. Everything else — a label[for] on a
|
|
23194
|
+
// non-native control — goes through control_label_state.js instead, which knows
|
|
23195
|
+
// the pairing without asking the document for it.
|
|
22994
23196
|
const getAssociatedLabels = element => {
|
|
22995
|
-
if (!element) {
|
|
23197
|
+
if (!element || !element.labels) {
|
|
22996
23198
|
return [];
|
|
22997
23199
|
}
|
|
22998
|
-
|
|
22999
|
-
// const insidePicker = closestPicker && element !== closestPicker;
|
|
23000
|
-
// const formElement = insidePicker ? closestPicker : element;
|
|
23001
|
-
const formElement = element;
|
|
23002
|
-
// Native form elements expose .labels directly
|
|
23003
|
-
if (formElement.labels && formElement.labels.length > 0) {
|
|
23004
|
-
return Array.from(formElement.labels);
|
|
23005
|
-
}
|
|
23006
|
-
const id = formElement.id;
|
|
23007
|
-
if (id) {
|
|
23008
|
-
const byId = Array.from(document.querySelectorAll(`label[for="${CSS.escape(id)}"]`));
|
|
23009
|
-
if (byId.length > 0) {
|
|
23010
|
-
return byId;
|
|
23011
|
-
}
|
|
23012
|
-
}
|
|
23013
|
-
return [];
|
|
23200
|
+
return Array.from(element.labels);
|
|
23014
23201
|
};
|
|
23015
23202
|
|
|
23016
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
23203
|
+
installImportMetaCssBuild(import.meta);const css$V = /* css */`
|
|
23017
23204
|
@layer navi {
|
|
23018
23205
|
.navi_button {
|
|
23019
23206
|
--button-border-radius: var(--navi-control-border-radius);
|
|
@@ -23406,7 +23593,7 @@ installImportMetaCssBuild(import.meta);const css$U = /* css */`
|
|
|
23406
23593
|
}
|
|
23407
23594
|
`;
|
|
23408
23595
|
const ButtonUI = props => {
|
|
23409
|
-
import.meta.css = [css$
|
|
23596
|
+
import.meta.css = [css$V, "@jsenv/navi/src/control/input/button_ui.jsx"];
|
|
23410
23597
|
const {
|
|
23411
23598
|
ref,
|
|
23412
23599
|
// href/link
|
|
@@ -24818,6 +25005,10 @@ const createOpenController = (
|
|
|
24818
25005
|
const controller = {
|
|
24819
25006
|
opened: false,
|
|
24820
25007
|
openEffect: null,
|
|
25008
|
+
// Set by the controlled element (see popup_content_mount.js) when its
|
|
25009
|
+
// content is still waiting for a first open to be built. Called below,
|
|
25010
|
+
// before openEffect, so the popup measures and positions the real thing.
|
|
25011
|
+
mountContent: null,
|
|
24821
25012
|
open: (e, detail) => {
|
|
24822
25013
|
if (controller.opened || !controller.openEffect) {
|
|
24823
25014
|
return;
|
|
@@ -24871,6 +25062,10 @@ const createOpenController = (
|
|
|
24871
25062
|
}
|
|
24872
25063
|
};
|
|
24873
25064
|
};
|
|
25065
|
+
// After prepareFocusTransfer, which has to record what held the focus
|
|
25066
|
+
// before anything inside the popup can claim it, and before openEffect,
|
|
25067
|
+
// which measures the popup to place it.
|
|
25068
|
+
controller.mountContent?.();
|
|
24874
25069
|
const openEffectReturnValue =
|
|
24875
25070
|
controller.openEffect(requestOpenEvent) || null;
|
|
24876
25071
|
openEffectCleanup = (closeEvent) => {
|
|
@@ -25064,6 +25259,84 @@ const useOpenPropsEffectOnOpenController = (openController, props) => {
|
|
|
25064
25259
|
}, [open]);
|
|
25065
25260
|
};
|
|
25066
25261
|
|
|
25262
|
+
/**
|
|
25263
|
+
* Runs `fn` and commits whatever it re-renders before returning, instead of
|
|
25264
|
+
* letting Preact batch it into the next microtask. Layout effects of what gets
|
|
25265
|
+
* mounted run inside the call too, exactly as they would on any other commit.
|
|
25266
|
+
*
|
|
25267
|
+
* For the caller that has to read the DOM it just asked for — measuring an
|
|
25268
|
+
* element whose content it mounts in the same breath — and cannot wait a tick
|
|
25269
|
+
* to do it, because what comes after is a browser event still in flight
|
|
25270
|
+
* (preventDefault, focus placement) that no longer accepts being answered late.
|
|
25271
|
+
*
|
|
25272
|
+
* `options.debounceRendering` is Preact's own hook for deciding *when* the
|
|
25273
|
+
* render queue drains; swapping it for "right now" for the duration of the call
|
|
25274
|
+
* is exactly how preact/compat implements React's flushSync. Reserve it for the
|
|
25275
|
+
* case above: rendering synchronously in the middle of an event gives up the
|
|
25276
|
+
* batching that makes several state changes one commit.
|
|
25277
|
+
*/
|
|
25278
|
+
const flushSyncRendering = (fn) => {
|
|
25279
|
+
const debounceRenderingPrevious = options.debounceRendering;
|
|
25280
|
+
options.debounceRendering = (drainRenderQueue) => {
|
|
25281
|
+
drainRenderQueue();
|
|
25282
|
+
};
|
|
25283
|
+
try {
|
|
25284
|
+
fn();
|
|
25285
|
+
} finally {
|
|
25286
|
+
options.debounceRendering = debounceRenderingPrevious;
|
|
25287
|
+
}
|
|
25288
|
+
};
|
|
25289
|
+
|
|
25290
|
+
/**
|
|
25291
|
+
* When a popup builds what it holds.
|
|
25292
|
+
*
|
|
25293
|
+
* A closed popup shows nothing, focuses nothing, and answers nothing: what it
|
|
25294
|
+
* holds is out of reach until it opens. Building that content at mount time
|
|
25295
|
+
* means a page carrying a handful of closed popups pays, on the very render
|
|
25296
|
+
* that decides how fast it appears, for content nobody has asked for — and
|
|
25297
|
+
* pays again on every subsequent measurement, since each of those nodes makes
|
|
25298
|
+
* the document the rest of the page queries bigger.
|
|
25299
|
+
*
|
|
25300
|
+
* So the content is built when the popup first opens, and stays built from
|
|
25301
|
+
* then on: closing is not throwing away, and a reopened popup finds its scroll
|
|
25302
|
+
* position, its half-typed form and its list state where it left them.
|
|
25303
|
+
*
|
|
25304
|
+
* It is built synchronously, from inside `openController.open()` and before
|
|
25305
|
+
* `openEffect` runs (see open_controller.js), so the popup still measures real
|
|
25306
|
+
* content when it positions and animates itself, and so anything inside it
|
|
25307
|
+
* still observes the opening the way it always did — mounted while the popup
|
|
25308
|
+
* reads as closed, told it opened right after (see
|
|
25309
|
+
* use_displayed_layout_effect.js).
|
|
25310
|
+
*
|
|
25311
|
+
* `mountWhenClosed` is for content something else depends on before any of
|
|
25312
|
+
* this: a value the popup's owner reads off its own children, fields a form
|
|
25313
|
+
* around it collects on submit, a size measured from outside.
|
|
25314
|
+
*/
|
|
25315
|
+
|
|
25316
|
+
|
|
25317
|
+
const usePopupContentMount = (
|
|
25318
|
+
openController,
|
|
25319
|
+
{ children, mountWhenClosed },
|
|
25320
|
+
) => {
|
|
25321
|
+
const [contentMounted, setContentMounted] = useState(
|
|
25322
|
+
() => Boolean(mountWhenClosed) || openController.opened,
|
|
25323
|
+
);
|
|
25324
|
+
openController.mountContent = contentMounted
|
|
25325
|
+
? null
|
|
25326
|
+
: () => {
|
|
25327
|
+
flushSyncRendering(() => {
|
|
25328
|
+
setContentMounted(true);
|
|
25329
|
+
});
|
|
25330
|
+
};
|
|
25331
|
+
useLayoutEffect(() => {
|
|
25332
|
+
if (mountWhenClosed) {
|
|
25333
|
+
setContentMounted(true);
|
|
25334
|
+
}
|
|
25335
|
+
}, [mountWhenClosed]);
|
|
25336
|
+
|
|
25337
|
+
return contentMounted ? children : null;
|
|
25338
|
+
};
|
|
25339
|
+
|
|
25067
25340
|
/**
|
|
25068
25341
|
* Entry/exit animation CSS shared by Popover and Dialog.
|
|
25069
25342
|
*
|
|
@@ -25534,7 +25807,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
25534
25807
|
* reaches the real container.
|
|
25535
25808
|
*/
|
|
25536
25809
|
let openLocalDialogCount = 0;
|
|
25537
|
-
const css$
|
|
25810
|
+
const css$U = /* css */`
|
|
25538
25811
|
@layer navi {
|
|
25539
25812
|
.navi_dialog {
|
|
25540
25813
|
/* Min gap between the dialog and the edges of its container. Written
|
|
@@ -25941,10 +26214,15 @@ const css$T = /* css */`
|
|
|
25941
26214
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
25942
26215
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
25943
26216
|
* by `picker_custom.jsx`).
|
|
26217
|
+
* @param {boolean} [props.mountWhenClosed] - Builds `children` right away
|
|
26218
|
+
* instead of waiting for the first open (see popup_content_mount.js). For
|
|
26219
|
+
* content something depends on while the popup is still closed: a value read
|
|
26220
|
+
* off it, fields a surrounding form collects on submit, a size measured from
|
|
26221
|
+
* outside.
|
|
25944
26222
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
25945
26223
|
*/
|
|
25946
26224
|
const Dialog = props => {
|
|
25947
|
-
import.meta.css = [css$
|
|
26225
|
+
import.meta.css = [css$U, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
25948
26226
|
if (props.openController) {
|
|
25949
26227
|
return jsx(ControlledDialog, {
|
|
25950
26228
|
...props
|
|
@@ -26132,9 +26410,14 @@ const useDialogProps = props => {
|
|
|
26132
26410
|
// instead, so it's read here rather than left in `rest`.
|
|
26133
26411
|
autoFocus = "last-resort",
|
|
26134
26412
|
onKeyDown,
|
|
26135
|
-
children,
|
|
26413
|
+
children: childrenProp,
|
|
26414
|
+
mountWhenClosed,
|
|
26136
26415
|
...rest
|
|
26137
26416
|
} = props;
|
|
26417
|
+
const children = usePopupContentMount(openController, {
|
|
26418
|
+
children: childrenProp,
|
|
26419
|
+
mountWhenClosed
|
|
26420
|
+
});
|
|
26138
26421
|
const isModal = layer === "top";
|
|
26139
26422
|
const ref = props.ref;
|
|
26140
26423
|
// Only touch changes anything: with a mouse a dialog already wants to be the
|
|
@@ -26772,7 +27055,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
26772
27055
|
* and applied.
|
|
26773
27056
|
*/
|
|
26774
27057
|
let openLocalPopoverCount = 0;
|
|
26775
|
-
const css$
|
|
27058
|
+
const css$T = /* css */`
|
|
26776
27059
|
@layer navi {
|
|
26777
27060
|
.navi_popover {
|
|
26778
27061
|
/* soft: user-configurable preferred max-height. Kept as a *default*
|
|
@@ -27139,10 +27422,15 @@ const css$S = /* css */`
|
|
|
27139
27422
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
27140
27423
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
27141
27424
|
* by `picker_custom.jsx`/`side_panel.jsx`).
|
|
27425
|
+
* @param {boolean} [props.mountWhenClosed] - Builds `children` right away
|
|
27426
|
+
* instead of waiting for the first open (see popup_content_mount.js). For
|
|
27427
|
+
* content something depends on while the popup is still closed: a value read
|
|
27428
|
+
* off it, fields a surrounding form collects on submit, a size measured from
|
|
27429
|
+
* outside.
|
|
27142
27430
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
27143
27431
|
*/
|
|
27144
27432
|
const Popover = props => {
|
|
27145
|
-
import.meta.css = [css$
|
|
27433
|
+
import.meta.css = [css$T, "@jsenv/navi/src/layout/popover.jsx"];
|
|
27146
27434
|
if (props.openController) {
|
|
27147
27435
|
return jsx(ControlledPopover, {
|
|
27148
27436
|
...props
|
|
@@ -27323,9 +27611,14 @@ const usePopoverProps = props => {
|
|
|
27323
27611
|
// instead, so it's read here rather than left in `rest`.
|
|
27324
27612
|
autoFocus = "last-resort",
|
|
27325
27613
|
onKeyDown,
|
|
27326
|
-
children,
|
|
27614
|
+
children: childrenProp,
|
|
27615
|
+
mountWhenClosed,
|
|
27327
27616
|
...rest
|
|
27328
27617
|
} = props;
|
|
27618
|
+
const children = usePopupContentMount(openController, {
|
|
27619
|
+
children: childrenProp,
|
|
27620
|
+
mountWhenClosed
|
|
27621
|
+
});
|
|
27329
27622
|
const isTopLayer = layer === "top";
|
|
27330
27623
|
const ref = props.ref;
|
|
27331
27624
|
const backdropRef = useRef();
|
|
@@ -28106,7 +28399,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
28106
28399
|
* event, and a caller replacing the body entirely then has one protocol to
|
|
28107
28400
|
* follow — `--navi-confirm` for yes, anything that closes for no.
|
|
28108
28401
|
*/
|
|
28109
|
-
const css$
|
|
28402
|
+
const css$S = /* css */`
|
|
28110
28403
|
/* The width lives on the body rather than on the popup, so that custom
|
|
28111
28404
|
content (which replaces this body entirely) sizes itself instead of
|
|
28112
28405
|
inheriting a ceiling meant for a sentence-long question. */
|
|
@@ -28243,7 +28536,7 @@ const ConfirmPopup = ({
|
|
|
28243
28536
|
onAnswer,
|
|
28244
28537
|
onClosed
|
|
28245
28538
|
}) => {
|
|
28246
|
-
import.meta.css = [css$
|
|
28539
|
+
import.meta.css = [css$S, "@jsenv/navi/src/action/confirm_popup.jsx"];
|
|
28247
28540
|
const {
|
|
28248
28541
|
mode,
|
|
28249
28542
|
confirmLabel,
|
|
@@ -28327,7 +28620,7 @@ const defaultBody = (message, {
|
|
|
28327
28620
|
});
|
|
28328
28621
|
};
|
|
28329
28622
|
|
|
28330
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
28623
|
+
installImportMetaCssBuild(import.meta);const css$R = /* css */`
|
|
28331
28624
|
.action_error {
|
|
28332
28625
|
margin-top: 0;
|
|
28333
28626
|
margin-bottom: 20px;
|
|
@@ -28352,7 +28645,7 @@ const ActionRenderer = ({
|
|
|
28352
28645
|
children,
|
|
28353
28646
|
disabled
|
|
28354
28647
|
}) => {
|
|
28355
|
-
import.meta.css = [css$
|
|
28648
|
+
import.meta.css = [css$R, "@jsenv/navi/src/action/action_renderer.jsx"];
|
|
28356
28649
|
if (action === undefined) {
|
|
28357
28650
|
throw new Error("ActionRenderer requires an action to render, but none was provided.");
|
|
28358
28651
|
}
|
|
@@ -35547,7 +35840,7 @@ const PhoneSvg = () => {
|
|
|
35547
35840
|
};
|
|
35548
35841
|
|
|
35549
35842
|
installImportMetaCssBuild(import.meta);// # TextAnchor — how it works
|
|
35550
|
-
const css$
|
|
35843
|
+
const css$Q = /* css */`
|
|
35551
35844
|
.navi_text_anchor {
|
|
35552
35845
|
vertical-align: baseline;
|
|
35553
35846
|
user-select: none;
|
|
@@ -35582,7 +35875,7 @@ const TextAnchor = ({
|
|
|
35582
35875
|
textSize,
|
|
35583
35876
|
lineLayout
|
|
35584
35877
|
}) => {
|
|
35585
|
-
import.meta.css = [css$
|
|
35878
|
+
import.meta.css = [css$Q, "@jsenv/navi/src/text/text_anchor.jsx"];
|
|
35586
35879
|
const anchorRef = useRef();
|
|
35587
35880
|
|
|
35588
35881
|
// Plain useLayoutEffect would also fire while an ancestor dialog/popover
|
|
@@ -35697,7 +35990,7 @@ const computeTopOffset = ({
|
|
|
35697
35990
|
};
|
|
35698
35991
|
const charTopCanvas = document.createElement("canvas");
|
|
35699
35992
|
|
|
35700
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
35993
|
+
installImportMetaCssBuild(import.meta);const css$P = /* css */`
|
|
35701
35994
|
@layer navi {
|
|
35702
35995
|
/* Ensure data attributes from box.jsx can win to update display */
|
|
35703
35996
|
.navi_icon {
|
|
@@ -35855,7 +36148,7 @@ const Icon = ({
|
|
|
35855
36148
|
fillLine,
|
|
35856
36149
|
...props
|
|
35857
36150
|
}) => {
|
|
35858
|
-
import.meta.css = [css$
|
|
36151
|
+
import.meta.css = [css$P, "@jsenv/navi/src/text/icon.jsx"];
|
|
35859
36152
|
const innerChildren = href ? jsx("svg", {
|
|
35860
36153
|
width: "100%",
|
|
35861
36154
|
height: "100%",
|
|
@@ -36008,7 +36301,7 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
36008
36301
|
});
|
|
36009
36302
|
};
|
|
36010
36303
|
|
|
36011
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
36304
|
+
installImportMetaCssBuild(import.meta);const css$O = /* css */`
|
|
36012
36305
|
@layer navi {
|
|
36013
36306
|
.navi_link {
|
|
36014
36307
|
--link-border-radius: unset;
|
|
@@ -36450,7 +36743,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
36450
36743
|
* @param {boolean} [props.readOnly]
|
|
36451
36744
|
*/
|
|
36452
36745
|
const Link = props => {
|
|
36453
|
-
import.meta.css = [css$
|
|
36746
|
+
import.meta.css = [css$O, "@jsenv/navi/src/nav/link/link.jsx"];
|
|
36454
36747
|
if (props.route) {
|
|
36455
36748
|
return jsx(LinkWithRoute, {
|
|
36456
36749
|
...props
|
|
@@ -36685,7 +36978,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
36685
36978
|
* TabList component with support for horizontal and vertical layouts
|
|
36686
36979
|
* https://dribbble.com/search/tabs
|
|
36687
36980
|
*/
|
|
36688
|
-
const css$
|
|
36981
|
+
const css$N = /* css */`
|
|
36689
36982
|
@layer navi {
|
|
36690
36983
|
.navi_nav {
|
|
36691
36984
|
--nav-border: none;
|
|
@@ -36860,7 +37153,7 @@ const Nav = ({
|
|
|
36860
37153
|
// "before" or "after": which side the panel sits on, turning the nav into folder tabs
|
|
36861
37154
|
...props
|
|
36862
37155
|
}) => {
|
|
36863
|
-
import.meta.css = [css$
|
|
37156
|
+
import.meta.css = [css$N, "@jsenv/navi/src/nav/link/nav.jsx"];
|
|
36864
37157
|
children = toChildArray(children);
|
|
36865
37158
|
return jsx(Box, {
|
|
36866
37159
|
as: "nav",
|
|
@@ -37252,7 +37545,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
37252
37545
|
* Border width participates in layout (it is added to the tab and page
|
|
37253
37546
|
* padding): a thick border grows the binder rather than eating into the text.
|
|
37254
37547
|
*/
|
|
37255
|
-
const css$
|
|
37548
|
+
const css$M = /* css */`
|
|
37256
37549
|
@layer navi {
|
|
37257
37550
|
.navi_binder {
|
|
37258
37551
|
--binder-border-width: var(--navi-control-border-width);
|
|
@@ -37565,7 +37858,7 @@ const Binder = ({
|
|
|
37565
37858
|
pagePadding,
|
|
37566
37859
|
...props
|
|
37567
37860
|
}) => {
|
|
37568
|
-
import.meta.css = [css$
|
|
37861
|
+
import.meta.css = [css$M, "@jsenv/navi/src/nav/binder/binder.jsx"];
|
|
37569
37862
|
const items = toChildArray(children).map((child, index) => {
|
|
37570
37863
|
const {
|
|
37571
37864
|
value: itemValue,
|
|
@@ -38091,7 +38384,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
38091
38384
|
* into the size; a box-shadow draws the identical line and stays out of
|
|
38092
38385
|
* layout.
|
|
38093
38386
|
*/
|
|
38094
|
-
const css$
|
|
38387
|
+
const css$L = /* css */`
|
|
38095
38388
|
@layer navi {
|
|
38096
38389
|
:root {
|
|
38097
38390
|
--navi-fixed-bar-width: 56px;
|
|
@@ -38229,7 +38522,7 @@ const FixedBar = ({
|
|
|
38229
38522
|
border = true,
|
|
38230
38523
|
...props
|
|
38231
38524
|
}) => {
|
|
38232
|
-
import.meta.css = [css$
|
|
38525
|
+
import.meta.css = [css$L, "@jsenv/navi/src/layout/fixed_bar/fixed_bar.jsx"];
|
|
38233
38526
|
const defaultRef = useRef();
|
|
38234
38527
|
props.ref = props.ref || defaultRef;
|
|
38235
38528
|
// Whichever of width/height crosses the edge the bar sits on is what the
|
|
@@ -38317,7 +38610,7 @@ const FixedBar = ({
|
|
|
38317
38610
|
// Subpixel layout rounds rectangles up on boxes that fit exactly.
|
|
38318
38611
|
const OVERFLOW_TOLERANCE = 1;
|
|
38319
38612
|
|
|
38320
|
-
const css$
|
|
38613
|
+
const css$K = /* css */ `
|
|
38321
38614
|
[data-navi-overflow-x] {
|
|
38322
38615
|
outline: 2px dashed #e74c3c;
|
|
38323
38616
|
outline-offset: -2px;
|
|
@@ -38341,7 +38634,7 @@ const detectHorizontalOverflow = ({
|
|
|
38341
38634
|
let styleEl = null;
|
|
38342
38635
|
if (highlight) {
|
|
38343
38636
|
styleEl = document.createElement("style");
|
|
38344
|
-
styleEl.textContent = css$
|
|
38637
|
+
styleEl.textContent = css$K;
|
|
38345
38638
|
document.head.appendChild(styleEl);
|
|
38346
38639
|
}
|
|
38347
38640
|
|
|
@@ -38497,7 +38790,7 @@ const useFocusGroup = (
|
|
|
38497
38790
|
|
|
38498
38791
|
installImportMetaCssBuild(import.meta);const rightArrowPath = "M680-480L360-160l-80-80 240-240-240-240 80-80 320 320z";
|
|
38499
38792
|
const downArrowPath = "M480-280L160-600l80-80 240 240 240-240 80 80-320 320z";
|
|
38500
|
-
const css$
|
|
38793
|
+
const css$J = /* css */`
|
|
38501
38794
|
.navi_summary_marker {
|
|
38502
38795
|
width: 1em;
|
|
38503
38796
|
height: 1em;
|
|
@@ -38582,7 +38875,7 @@ const SummaryMarker = ({
|
|
|
38582
38875
|
open,
|
|
38583
38876
|
loading
|
|
38584
38877
|
}) => {
|
|
38585
|
-
import.meta.css = [css$
|
|
38878
|
+
import.meta.css = [css$J, "@jsenv/navi/src/control/details/summary_marker.jsx"];
|
|
38586
38879
|
const showLoading = useDebounceTrue(loading, 300);
|
|
38587
38880
|
const mountedRef = useRef(false);
|
|
38588
38881
|
const prevOpenRef = useRef(open);
|
|
@@ -38636,7 +38929,7 @@ const SummaryMarker = ({
|
|
|
38636
38929
|
});
|
|
38637
38930
|
};
|
|
38638
38931
|
|
|
38639
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
38932
|
+
installImportMetaCssBuild(import.meta);const css$I = /* css */`
|
|
38640
38933
|
.navi_details {
|
|
38641
38934
|
position: relative;
|
|
38642
38935
|
z-index: 1;
|
|
@@ -38682,7 +38975,7 @@ const Details = props => {
|
|
|
38682
38975
|
return details;
|
|
38683
38976
|
};
|
|
38684
38977
|
const DetailsField = props => {
|
|
38685
|
-
import.meta.css = [css$
|
|
38978
|
+
import.meta.css = [css$I, "@jsenv/navi/src/control/details/details.jsx"];
|
|
38686
38979
|
const {
|
|
38687
38980
|
ref,
|
|
38688
38981
|
persists,
|
|
@@ -38938,7 +39231,7 @@ const ControlGroup = props => {
|
|
|
38938
39231
|
};
|
|
38939
39232
|
const CONTROL_GROUP_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
38940
39233
|
|
|
38941
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
39234
|
+
installImportMetaCssBuild(import.meta);const css$H = /* css */`
|
|
38942
39235
|
@layer navi {
|
|
38943
39236
|
.navi_checkbox {
|
|
38944
39237
|
--switch-margin: 0; /* Useful to reserve space for outline */
|
|
@@ -39016,7 +39309,7 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
39016
39309
|
}
|
|
39017
39310
|
`;
|
|
39018
39311
|
const SwitchUI = () => {
|
|
39019
|
-
import.meta.css = [css$
|
|
39312
|
+
import.meta.css = [css$H, "@jsenv/navi/src/control/input/switch_ui.jsx"];
|
|
39020
39313
|
return jsx(Box, {
|
|
39021
39314
|
className: "navi_switch",
|
|
39022
39315
|
as: "svg",
|
|
@@ -39058,7 +39351,7 @@ const useCheckableProps = (props, options) => {
|
|
|
39058
39351
|
return result;
|
|
39059
39352
|
};
|
|
39060
39353
|
|
|
39061
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
39354
|
+
installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
39062
39355
|
@layer navi {
|
|
39063
39356
|
.navi_checkbox {
|
|
39064
39357
|
--border-radius: var(--navi-checkbox-border-radius);
|
|
@@ -39385,7 +39678,7 @@ const InputCheckboxHeadless = props => {
|
|
|
39385
39678
|
});
|
|
39386
39679
|
};
|
|
39387
39680
|
const InputCheckboxFieldInterface = props => {
|
|
39388
|
-
import.meta.css = [css$
|
|
39681
|
+
import.meta.css = [css$G, "@jsenv/navi/src/control/input/input_checkbox.jsx"];
|
|
39389
39682
|
const [checkboxRootProps, checkboxHostProps] = useCheckableProps(props);
|
|
39390
39683
|
const {
|
|
39391
39684
|
icon,
|
|
@@ -39507,7 +39800,7 @@ const CheckboxButtonStyleCSSVars = {
|
|
|
39507
39800
|
const CheckboxPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
|
|
39508
39801
|
const CheckboxPseudoElements = ["::-navi-loader", "::-navi-checkmark"];
|
|
39509
39802
|
|
|
39510
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
39803
|
+
installImportMetaCssBuild(import.meta);const css$F = /* css */`
|
|
39511
39804
|
@layer navi {
|
|
39512
39805
|
.navi_label {
|
|
39513
39806
|
--label-required-indicator-color: var(--navi-color-danger, #b42318);
|
|
@@ -39587,7 +39880,7 @@ installImportMetaCssBuild(import.meta);const css$E = /* css */`
|
|
|
39587
39880
|
* </Field>
|
|
39588
39881
|
*/
|
|
39589
39882
|
const Field = props => {
|
|
39590
|
-
import.meta.css = [css$
|
|
39883
|
+
import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
|
|
39591
39884
|
const refDefault = useRef();
|
|
39592
39885
|
props.ref = props.ref || refDefault;
|
|
39593
39886
|
const {
|
|
@@ -39622,7 +39915,7 @@ const FieldCSSVars = {
|
|
|
39622
39915
|
spacingWithControl: "--spacing-with-control"
|
|
39623
39916
|
};
|
|
39624
39917
|
const FieldAsContainer = props => {
|
|
39625
|
-
import.meta.css = [css$
|
|
39918
|
+
import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
|
|
39626
39919
|
const {
|
|
39627
39920
|
children
|
|
39628
39921
|
} = props;
|
|
@@ -39654,7 +39947,7 @@ const FieldAsContainer = props => {
|
|
|
39654
39947
|
};
|
|
39655
39948
|
const FIELD_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
39656
39949
|
const Label = props => {
|
|
39657
|
-
import.meta.css = [css$
|
|
39950
|
+
import.meta.css = [css$F, "@jsenv/navi/src/control/field.jsx"];
|
|
39658
39951
|
const {
|
|
39659
39952
|
children,
|
|
39660
39953
|
// Marks the label when its control is required. Takes what to show, or
|
|
@@ -39677,6 +39970,25 @@ const Label = props => {
|
|
|
39677
39970
|
if (!Object.hasOwn(props, "htmlFor") && controlId) {
|
|
39678
39971
|
props.htmlFor = controlId;
|
|
39679
39972
|
}
|
|
39973
|
+
// A label pointing at its control by id is not inside it and does not contain
|
|
39974
|
+
// it, so nothing in the DOM links the two — the control publishes its state
|
|
39975
|
+
// under that id and this is where the label picks it up (see
|
|
39976
|
+
// control_label_state.js). A label that wraps its control instead is told
|
|
39977
|
+
// through the navi_control_state event below.
|
|
39978
|
+
const {
|
|
39979
|
+
htmlFor
|
|
39980
|
+
} = props;
|
|
39981
|
+
useLayoutEffect(() => {
|
|
39982
|
+
if (!htmlFor) {
|
|
39983
|
+
return undefined;
|
|
39984
|
+
}
|
|
39985
|
+
return subscribeToControlState(htmlFor, controlState => {
|
|
39986
|
+
setConnected(Boolean(controlState));
|
|
39987
|
+
setDisabled(Boolean(controlState?.disabled));
|
|
39988
|
+
setReadOnly(Boolean(controlState?.readOnly));
|
|
39989
|
+
setRequired(Boolean(controlState?.required));
|
|
39990
|
+
});
|
|
39991
|
+
}, [htmlFor]);
|
|
39680
39992
|
const [messageProps, remainingProps] = extractMessageAndRemainingProps({
|
|
39681
39993
|
...props,
|
|
39682
39994
|
requiredIndicator: undefined
|
|
@@ -39797,7 +40109,7 @@ const InputSlot = ({
|
|
|
39797
40109
|
});
|
|
39798
40110
|
};
|
|
39799
40111
|
|
|
39800
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
40112
|
+
installImportMetaCssBuild(import.meta);const css$E = /* css */`
|
|
39801
40113
|
@layer navi {
|
|
39802
40114
|
.navi_radio {
|
|
39803
40115
|
--margin: 3px 3px 3px 5px;
|
|
@@ -40160,7 +40472,7 @@ const InputRadioHeadless = props => {
|
|
|
40160
40472
|
};
|
|
40161
40473
|
const APPEARANCE_SET = new Set(["icon", "button", "radio"]);
|
|
40162
40474
|
const InputRadioFieldInterface = props => {
|
|
40163
|
-
import.meta.css = [css$
|
|
40475
|
+
import.meta.css = [css$E, "@jsenv/navi/src/control/input/input_radio.jsx"];
|
|
40164
40476
|
const [radioRootProps, radioHostProps] = useCheckableProps(props);
|
|
40165
40477
|
const {
|
|
40166
40478
|
icon,
|
|
@@ -40306,7 +40618,7 @@ const RadioButtonStyleCSSVars = {
|
|
|
40306
40618
|
const RadioPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":checked", ":-navi-loading"];
|
|
40307
40619
|
const RadioPseudoElements = ["::-navi-loader", "::-navi-radiomark"];
|
|
40308
40620
|
|
|
40309
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
40621
|
+
installImportMetaCssBuild(import.meta);const css$D = /* css */`
|
|
40310
40622
|
@layer navi {
|
|
40311
40623
|
.navi_input_range {
|
|
40312
40624
|
--border-radius: 6px;
|
|
@@ -40561,7 +40873,7 @@ const InputRange = props => {
|
|
|
40561
40873
|
});
|
|
40562
40874
|
};
|
|
40563
40875
|
const InputRangeFieldInterface = props => {
|
|
40564
|
-
import.meta.css = [css$
|
|
40876
|
+
import.meta.css = [css$D, "@jsenv/navi/src/control/input/input_range.jsx"];
|
|
40565
40877
|
const {
|
|
40566
40878
|
ref
|
|
40567
40879
|
} = props;
|
|
@@ -42488,7 +42800,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
42488
42800
|
* This means an editable thing MUST have a parent with position relative that wraps the content and the eventual editable input
|
|
42489
42801
|
*
|
|
42490
42802
|
*/
|
|
42491
|
-
const css$
|
|
42803
|
+
const css$C = /* css */`
|
|
42492
42804
|
.navi_editable_wrapper {
|
|
42493
42805
|
--inset-top: 0px;
|
|
42494
42806
|
--inset-right: 0px;
|
|
@@ -42537,7 +42849,7 @@ const useEditionController = () => {
|
|
|
42537
42849
|
};
|
|
42538
42850
|
};
|
|
42539
42851
|
const Editable = props => {
|
|
42540
|
-
import.meta.css = [css$
|
|
42852
|
+
import.meta.css = [css$C, "@jsenv/navi/src/control/edition/editable.jsx"];
|
|
42541
42853
|
let {
|
|
42542
42854
|
children,
|
|
42543
42855
|
action,
|
|
@@ -42767,6 +43079,7 @@ const useFormGroup = props => {
|
|
|
42767
43079
|
// against the state of the previous frame.
|
|
42768
43080
|
uiStateController.shouldRequestAction = value => Boolean(props.canSendWhileUnchanged) || !compareTwoJsValues(withoutEmptyFields(value), uiStateController.sentUIState);
|
|
42769
43081
|
useFirstUIStateAsSent(uiStateController);
|
|
43082
|
+
useUnregisteredControlWarning(props.ref);
|
|
42770
43083
|
const {
|
|
42771
43084
|
basePseudoState,
|
|
42772
43085
|
children
|
|
@@ -42915,6 +43228,15 @@ const useFirstUIStateAsSent = uiStateController => {
|
|
|
42915
43228
|
uiStateController.sentUIState = readHeldUIState(uiStateController);
|
|
42916
43229
|
}, [uiStateController]);
|
|
42917
43230
|
};
|
|
43231
|
+
const useUnregisteredControlWarning = ref => {
|
|
43232
|
+
// No dependency array: fields appear and disappear as the form re-renders,
|
|
43233
|
+
// and a field rendered later is exactly the one worth catching.
|
|
43234
|
+
useLayoutEffect(() => {
|
|
43235
|
+
{
|
|
43236
|
+
return;
|
|
43237
|
+
}
|
|
43238
|
+
});
|
|
43239
|
+
};
|
|
42918
43240
|
const FormPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading"];
|
|
42919
43241
|
|
|
42920
43242
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
|
|
@@ -42951,7 +43273,7 @@ HTMLFormElement.prototype.requestSubmit = function (submitter) {
|
|
|
42951
43273
|
// form.dispatchEvent(customEvent);
|
|
42952
43274
|
// };
|
|
42953
43275
|
|
|
42954
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
43276
|
+
installImportMetaCssBuild(import.meta);const css$B = /* css */`
|
|
42955
43277
|
.navi_group {
|
|
42956
43278
|
--group-border-width: 1px;
|
|
42957
43279
|
|
|
@@ -43047,7 +43369,7 @@ const Group = ({
|
|
|
43047
43369
|
vertical = row,
|
|
43048
43370
|
...props
|
|
43049
43371
|
}) => {
|
|
43050
|
-
import.meta.css = [css$
|
|
43372
|
+
import.meta.css = [css$B, "@jsenv/navi/src/control/group.jsx"];
|
|
43051
43373
|
return jsx(Box, {
|
|
43052
43374
|
baseClassName: "navi_group",
|
|
43053
43375
|
"data-vertical": vertical ? "" : undefined,
|
|
@@ -43174,7 +43496,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
43174
43496
|
* pair. One popup holding slides of its own contents has no such problem — and
|
|
43175
43497
|
* it is the same component in the document, in a dialog or in a popover.
|
|
43176
43498
|
*/
|
|
43177
|
-
const css$
|
|
43499
|
+
const css$A = /* css */`
|
|
43178
43500
|
/* Every slide in the same grid cell: the box then measures itself on the
|
|
43179
43501
|
LARGEST of them, in both directions, without anything being measured by
|
|
43180
43502
|
hand — which is also why nothing here resizes as the slides change. Each
|
|
@@ -43484,7 +43806,7 @@ const SlideContainer = ({
|
|
|
43484
43806
|
children,
|
|
43485
43807
|
...rest
|
|
43486
43808
|
}) => {
|
|
43487
|
-
import.meta.css = [css$
|
|
43809
|
+
import.meta.css = [css$A, "@jsenv/navi/src/layout/slide_container.jsx"];
|
|
43488
43810
|
const debugFocus = useDebugFocus();
|
|
43489
43811
|
const trackRef = useRef();
|
|
43490
43812
|
// The box itself: it is what takes the keyboard when what is on screen holds
|
|
@@ -45348,7 +45670,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
45348
45670
|
* pass through untouched via `...rest` to whichever of Popover/Dialog
|
|
45349
45671
|
* actually renders.
|
|
45350
45672
|
*/
|
|
45351
|
-
const css$
|
|
45673
|
+
const css$z = /* css */`
|
|
45352
45674
|
@layer navi {
|
|
45353
45675
|
.navi_popup {
|
|
45354
45676
|
--popup-border-radius: var(--navi-popup-border-radius);
|
|
@@ -45439,10 +45761,15 @@ const css$y = /* css */`
|
|
|
45439
45761
|
* @param {string} [props.className] - Merged with the shared
|
|
45440
45762
|
* `"navi_popup"` class (see this file's own CSS) rather than replacing
|
|
45441
45763
|
* it.
|
|
45764
|
+
* @param {boolean} [props.mountWhenClosed] - Builds `children` right away
|
|
45765
|
+
* instead of waiting for the first open (see popup_content_mount.js). For
|
|
45766
|
+
* content something depends on while the popup is still closed: a value read
|
|
45767
|
+
* off it, fields a surrounding form collects on submit, a size measured from
|
|
45768
|
+
* outside.
|
|
45442
45769
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
45443
45770
|
*/
|
|
45444
45771
|
const Popup = props => {
|
|
45445
|
-
import.meta.css = [css$
|
|
45772
|
+
import.meta.css = [css$z, "@jsenv/navi/src/layout/popup.jsx"];
|
|
45446
45773
|
const {
|
|
45447
45774
|
mode: modeProp,
|
|
45448
45775
|
maxWidth,
|
|
@@ -45585,7 +45912,7 @@ const commitSubtree = (controller, e) => {
|
|
|
45585
45912
|
}
|
|
45586
45913
|
};
|
|
45587
45914
|
|
|
45588
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
45915
|
+
installImportMetaCssBuild(import.meta);const css$y = /* css */`
|
|
45589
45916
|
.navi_picker {
|
|
45590
45917
|
/* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
|
|
45591
45918
|
overflow... are already handled correctly by Popup/Popover/Dialog
|
|
@@ -45697,7 +46024,7 @@ installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
|
45697
46024
|
}
|
|
45698
46025
|
`;
|
|
45699
46026
|
const PickerCustomResolver = props => {
|
|
45700
|
-
import.meta.css = [css$
|
|
46027
|
+
import.meta.css = [css$y, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
|
|
45701
46028
|
if (props.children === undefined) {
|
|
45702
46029
|
return jsx(PickerNative, {
|
|
45703
46030
|
...props
|
|
@@ -45963,6 +46290,13 @@ const PickerCustom = props => {
|
|
|
45963
46290
|
Object.assign(popupProps, {
|
|
45964
46291
|
anchor: props.ref,
|
|
45965
46292
|
openController,
|
|
46293
|
+
// A picker whose value was never given to it reads it off the control in
|
|
46294
|
+
// its popup (see useUIFacadeStateController): the trigger shows what the
|
|
46295
|
+
// list inside says is selected, so that list has to exist before anyone
|
|
46296
|
+
// opens anything. Told a value — even an empty one — the picker owns it
|
|
46297
|
+
// and pushes it down instead, leaving the popup free to build its
|
|
46298
|
+
// content only when it is first opened (see popup_content_mount.js).
|
|
46299
|
+
mountWhenClosed: !Object.hasOwn(props, "value") && !Object.hasOwn(props, "defaultValue"),
|
|
45966
46300
|
// Not on pickerProps (the trigger): commands.js's own
|
|
45967
46301
|
// resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
|
|
45968
46302
|
// find where to dispatch navi_request_open/navi_request_close — and
|
|
@@ -46342,7 +46676,7 @@ const LoadingIndicator = ({
|
|
|
46342
46676
|
});
|
|
46343
46677
|
};
|
|
46344
46678
|
|
|
46345
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
46679
|
+
installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
46346
46680
|
@layer navi {
|
|
46347
46681
|
.navi_separator {
|
|
46348
46682
|
--size: 1px;
|
|
@@ -46420,7 +46754,7 @@ const Separator = ({
|
|
|
46420
46754
|
style,
|
|
46421
46755
|
...props
|
|
46422
46756
|
}) => {
|
|
46423
|
-
import.meta.css = [css$
|
|
46757
|
+
import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
|
|
46424
46758
|
return jsx(Box, {
|
|
46425
46759
|
as: vertical ? "span" : "hr",
|
|
46426
46760
|
...props,
|
|
@@ -46913,7 +47247,7 @@ const ListItemFooter = props => {
|
|
|
46913
47247
|
});
|
|
46914
47248
|
};
|
|
46915
47249
|
|
|
46916
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
47250
|
+
installImportMetaCssBuild(import.meta);const css$w = /* css */`
|
|
46917
47251
|
@layer navi {
|
|
46918
47252
|
.navi_list_container[navi-selectable] {
|
|
46919
47253
|
/* Focus outline */
|
|
@@ -47117,7 +47451,7 @@ const ListSelectableResolver = props => {
|
|
|
47117
47451
|
};
|
|
47118
47452
|
const ListSelectable = props => {
|
|
47119
47453
|
const Next = useNextResolver();
|
|
47120
|
-
import.meta.css = [css$
|
|
47454
|
+
import.meta.css = [css$w, "@jsenv/navi/src/control/list/list_selectable.jsx"];
|
|
47121
47455
|
// we allow ourselves to auto-generate a name
|
|
47122
47456
|
const defaultName = useId();
|
|
47123
47457
|
props.name = props.name || `listbox_${defaultName}`;
|
|
@@ -47712,7 +48046,7 @@ const ListVirtualContext = createContext(null);
|
|
|
47712
48046
|
// that returning a component of one's own — instead of a bare <List.Item> —
|
|
47713
48047
|
// works the same way.
|
|
47714
48048
|
const ListRowContext = createContext(null);
|
|
47715
|
-
const css$
|
|
48049
|
+
const css$v = /* css */`
|
|
47716
48050
|
@layer navi {
|
|
47717
48051
|
.navi_list_container {
|
|
47718
48052
|
--list-outline-width: 1px;
|
|
@@ -48237,7 +48571,7 @@ const css$u = /* css */`
|
|
|
48237
48571
|
}
|
|
48238
48572
|
`;
|
|
48239
48573
|
const ListUI = props => {
|
|
48240
|
-
import.meta.css = [css$
|
|
48574
|
+
import.meta.css = [css$v, "@jsenv/navi/src/control/list/list.jsx"];
|
|
48241
48575
|
const {
|
|
48242
48576
|
ref,
|
|
48243
48577
|
renderBudget: renderBudgetProp = RENDER_BUDGET_DEFAULT,
|
|
@@ -51340,7 +51674,7 @@ const PickerPresetResolver = props => {
|
|
|
51340
51674
|
});
|
|
51341
51675
|
};
|
|
51342
51676
|
|
|
51343
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
51677
|
+
installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
51344
51678
|
@layer navi {
|
|
51345
51679
|
}
|
|
51346
51680
|
.navi_badge {
|
|
@@ -51452,7 +51786,7 @@ const Badge = ({
|
|
|
51452
51786
|
className,
|
|
51453
51787
|
...props
|
|
51454
51788
|
}) => {
|
|
51455
|
-
import.meta.css = [css$
|
|
51789
|
+
import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
|
|
51456
51790
|
const defaultRef = useRef();
|
|
51457
51791
|
props.ref = props.ref || defaultRef;
|
|
51458
51792
|
const {
|
|
@@ -51504,7 +51838,7 @@ const BadgeButton = props => {
|
|
|
51504
51838
|
};
|
|
51505
51839
|
Badge.Button = BadgeButton;
|
|
51506
51840
|
|
|
51507
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
51841
|
+
installImportMetaCssBuild(import.meta);const css$t = /* css */`
|
|
51508
51842
|
@layer navi {
|
|
51509
51843
|
}
|
|
51510
51844
|
.navi_badge_list {
|
|
@@ -51529,7 +51863,7 @@ const BadgeList = ({
|
|
|
51529
51863
|
max,
|
|
51530
51864
|
...props
|
|
51531
51865
|
}) => {
|
|
51532
|
-
import.meta.css = [css$
|
|
51866
|
+
import.meta.css = [css$t, "@jsenv/navi/src/text/badge_list.jsx"];
|
|
51533
51867
|
const measureRef = useRef();
|
|
51534
51868
|
const visibleRef = useRef();
|
|
51535
51869
|
useLayoutEffect(() => {
|
|
@@ -51604,7 +51938,7 @@ const BadgeList = ({
|
|
|
51604
51938
|
});
|
|
51605
51939
|
};
|
|
51606
51940
|
|
|
51607
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
51941
|
+
installImportMetaCssBuild(import.meta);const css$s = /* css */`
|
|
51608
51942
|
.navi_color {
|
|
51609
51943
|
display: block;
|
|
51610
51944
|
aspect-ratio: 1/1;
|
|
@@ -51635,7 +51969,7 @@ const Color = ({
|
|
|
51635
51969
|
children,
|
|
51636
51970
|
...rest
|
|
51637
51971
|
}) => {
|
|
51638
|
-
import.meta.css = [css$
|
|
51972
|
+
import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
|
|
51639
51973
|
const color = children || undefined;
|
|
51640
51974
|
return jsx(Box, {
|
|
51641
51975
|
as: "span",
|
|
@@ -52090,7 +52424,7 @@ const PickerFileUI = () => {
|
|
|
52090
52424
|
return String(value);
|
|
52091
52425
|
};
|
|
52092
52426
|
|
|
52093
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
52427
|
+
installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
52094
52428
|
@layer navi {
|
|
52095
52429
|
.navi_picker {
|
|
52096
52430
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
@@ -52416,7 +52750,7 @@ installImportMetaCssBuild(import.meta);const css$q = /* css */`
|
|
|
52416
52750
|
}
|
|
52417
52751
|
`;
|
|
52418
52752
|
const PickerButton = props => {
|
|
52419
|
-
import.meta.css = [css$
|
|
52753
|
+
import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker.jsx"];
|
|
52420
52754
|
if (typeof props.maxLines === "string") {
|
|
52421
52755
|
props.maxLines = parseInt(props.maxLines);
|
|
52422
52756
|
}
|
|
@@ -52879,7 +53213,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
52879
53213
|
* refuse it on purpose, which is what keeps the focus where the travel happens
|
|
52880
53214
|
* instead of moving it into a slide that is about to leave.
|
|
52881
53215
|
*/
|
|
52882
|
-
const css$
|
|
53216
|
+
const css$q = /* css */`
|
|
52883
53217
|
@layer navi {
|
|
52884
53218
|
.navi_picker_spin {
|
|
52885
53219
|
/* A picker one steps through is still a picker: what themes every picker
|
|
@@ -53225,7 +53559,7 @@ const Spin = ({
|
|
|
53225
53559
|
nextLabel,
|
|
53226
53560
|
...rest
|
|
53227
53561
|
}) => {
|
|
53228
|
-
import.meta.css = [css$
|
|
53562
|
+
import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
|
|
53229
53563
|
const id = useId();
|
|
53230
53564
|
const containerId = `${id}_values`;
|
|
53231
53565
|
const controlId = `${id}_control`;
|
|
@@ -53235,7 +53569,14 @@ const Spin = ({
|
|
|
53235
53569
|
// and this reads the answer back. Nothing of that story is told twice.
|
|
53236
53570
|
const controlRef = useRef();
|
|
53237
53571
|
const middleRef = useRef();
|
|
53238
|
-
const
|
|
53572
|
+
const valueHeld = useControlUIState(controlRef, value ?? defaultValue ?? signalProp?.peek());
|
|
53573
|
+
// A spin always shows a value, and empty is not one: the calendar has an
|
|
53574
|
+
// "erase" in it, and what comes back from it is "" rather than nothing —
|
|
53575
|
+
// which is a value as far as `??` is concerned and is not one as far as
|
|
53576
|
+
// `valueAtStep` is (a day made of an empty string is an invalid date, and
|
|
53577
|
+
// stepping away from it writes NaN into the control). So the fallback is
|
|
53578
|
+
// asked for here, once, for every kind of value a spin steps through.
|
|
53579
|
+
const valueShown = valueHeld === undefined || valueHeld === null || valueHeld === "" ? fallbackValue : valueHeld;
|
|
53239
53580
|
|
|
53240
53581
|
// …and the other way round when a signal was handed over: a bound signal is
|
|
53241
53582
|
// the value, wherever it is moved from — the url, a back/forward, a button
|
|
@@ -53836,7 +54177,7 @@ const addDays = (day, count) => {
|
|
|
53836
54177
|
};
|
|
53837
54178
|
|
|
53838
54179
|
installImportMetaCssBuild(import.meta);// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
|
|
53839
|
-
const css$
|
|
54180
|
+
const css$p = /* css */`
|
|
53840
54181
|
.navi_checkbox_group {
|
|
53841
54182
|
border-style: solid;
|
|
53842
54183
|
|
|
@@ -53856,7 +54197,7 @@ const CheckboxGroup = props => {
|
|
|
53856
54197
|
return checkboxGroup;
|
|
53857
54198
|
};
|
|
53858
54199
|
const CheckboxGroupInterface = props => {
|
|
53859
|
-
import.meta.css = [css$
|
|
54200
|
+
import.meta.css = [css$p, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
|
|
53860
54201
|
const {
|
|
53861
54202
|
ref
|
|
53862
54203
|
} = props;
|
|
@@ -53905,7 +54246,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
53905
54246
|
* shared sheet is registered here too — a page may render a Textarea without
|
|
53906
54247
|
* any Input.
|
|
53907
54248
|
*/
|
|
53908
|
-
const css$
|
|
54249
|
+
const css$o = /* css */`
|
|
53909
54250
|
.navi_input.navi_textarea {
|
|
53910
54251
|
.navi_control_input {
|
|
53911
54252
|
min-height: calc(var(--textarea-min-rows, 1.5) * 1lh);
|
|
@@ -53992,7 +54333,7 @@ const Textarea = ({
|
|
|
53992
54333
|
width = "35ch",
|
|
53993
54334
|
...props
|
|
53994
54335
|
}) => {
|
|
53995
|
-
import.meta.css = [inputCss + css$
|
|
54336
|
+
import.meta.css = [inputCss + css$o, "@jsenv/navi/src/control/input/textarea.jsx"];
|
|
53996
54337
|
const defaultRef = useRef(null);
|
|
53997
54338
|
props.ref = props.ref || defaultRef;
|
|
53998
54339
|
usePlaceholderHeight(props.ref, props.placeholder);
|
|
@@ -54066,7 +54407,7 @@ const TextareaCharCount = ({
|
|
|
54066
54407
|
maxLength,
|
|
54067
54408
|
...rest
|
|
54068
54409
|
}) => {
|
|
54069
|
-
import.meta.css = [css$
|
|
54410
|
+
import.meta.css = [css$o, "@jsenv/navi/src/control/input/textarea.jsx"];
|
|
54070
54411
|
const resolvedValue = signal ? signal.value : value;
|
|
54071
54412
|
const length = typeof resolvedValue === "string" ? resolvedValue.length : 0;
|
|
54072
54413
|
return jsx(Box, {
|
|
@@ -54456,7 +54797,7 @@ const isTextInputElement = (el) => {
|
|
|
54456
54797
|
);
|
|
54457
54798
|
};
|
|
54458
54799
|
|
|
54459
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
54800
|
+
installImportMetaCssBuild(import.meta);const css$n = /* css */`
|
|
54460
54801
|
.navi_input_duration {
|
|
54461
54802
|
--duration-separator-spacing: 4px;
|
|
54462
54803
|
--loader-color: var(--navi-loader-color);
|
|
@@ -54523,7 +54864,7 @@ installImportMetaCssBuild(import.meta);const css$m = /* css */`
|
|
|
54523
54864
|
* "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
|
|
54524
54865
|
*/
|
|
54525
54866
|
const InputDuration = props => {
|
|
54526
|
-
import.meta.css = [css$
|
|
54867
|
+
import.meta.css = [css$n, "@jsenv/navi/src/control/input/input_duration.jsx"];
|
|
54527
54868
|
const defaultRef = useRef();
|
|
54528
54869
|
props.ref = props.ref || defaultRef;
|
|
54529
54870
|
props.max = props.max || "23h59";
|
|
@@ -55025,7 +55366,7 @@ const InputDurationPart = ({
|
|
|
55025
55366
|
});
|
|
55026
55367
|
};
|
|
55027
55368
|
|
|
55028
|
-
installImportMetaCssBuild(import.meta);const css$
|
|
55369
|
+
installImportMetaCssBuild(import.meta);const css$m = /* css */`
|
|
55029
55370
|
.navi_radio_group {
|
|
55030
55371
|
border-style: solid;
|
|
55031
55372
|
|
|
@@ -55045,7 +55386,7 @@ const RadioGroup = props => {
|
|
|
55045
55386
|
return radioGroup;
|
|
55046
55387
|
};
|
|
55047
55388
|
const RadioGroupInterface = props => {
|
|
55048
|
-
import.meta.css = [css$
|
|
55389
|
+
import.meta.css = [css$m, "@jsenv/navi/src/control/input/radio_group.jsx"];
|
|
55049
55390
|
const {
|
|
55050
55391
|
ref
|
|
55051
55392
|
} = props;
|
|
@@ -55074,6 +55415,139 @@ const RadioGroupInterface = props => {
|
|
|
55074
55415
|
});
|
|
55075
55416
|
};
|
|
55076
55417
|
|
|
55418
|
+
installImportMetaCssBuild(import.meta);/**
|
|
55419
|
+
* A native `<select>` that is a navi control: its value enters the state of the
|
|
55420
|
+
* `<Form>` around it, and it takes `signal`, `uiAction`, `action`, `command`,
|
|
55421
|
+
* `value`/`defaultValue`, `readOnly`, `disabled`, `required` like every other
|
|
55422
|
+
* control.
|
|
55423
|
+
*
|
|
55424
|
+
* Native on purpose, and not a Picker: on a phone a `<select>` opens the
|
|
55425
|
+
* system's own full-screen list — the thing the thumb handles best and the user
|
|
55426
|
+
* already knows — with no popup, no positioning and no focus trap to get wrong.
|
|
55427
|
+
* That is the right control for a short closed list (a gender, an age bracket,
|
|
55428
|
+
* "who sees this"). A long, searchable list with rich content in its options is
|
|
55429
|
+
* Picker's problem, not this one.
|
|
55430
|
+
*
|
|
55431
|
+
* The options are the children, written as HTML: an `<optgroup>`, a `disabled`
|
|
55432
|
+
* option, an `<hr>` between two groups need no support from this component.
|
|
55433
|
+
*
|
|
55434
|
+
* Two things the component absorbs, both traps met before:
|
|
55435
|
+
* - a native `<select>` ignores `defaultValue` and reads `selected` off its
|
|
55436
|
+
* options instead; here `value`/`defaultValue` mean what they mean everywhere
|
|
55437
|
+
* else in navi, and the element is told what to show.
|
|
55438
|
+
* - a native `<select>` has no `readonly`; `readOnly` here refuses the
|
|
55439
|
+
* interaction (the list does not open, a change from the keyboard is put
|
|
55440
|
+
* back) and says so with `aria-readonly`.
|
|
55441
|
+
*
|
|
55442
|
+
* Styled as a `.navi_input` box so a select and an input sitting next to each
|
|
55443
|
+
* other are the same box. `appearance: none` only changes how the closed
|
|
55444
|
+
* control is drawn — the list it opens stays the platform's own, which is the
|
|
55445
|
+
* whole point of using a select.
|
|
55446
|
+
*/
|
|
55447
|
+
const css$l = /* css */`
|
|
55448
|
+
.navi_input.navi_select {
|
|
55449
|
+
.navi_control_input {
|
|
55450
|
+
/* Room for the chevron, which sits over the padding rather than beside
|
|
55451
|
+
the control — anything beside it would be a click that misses. */
|
|
55452
|
+
padding-right: calc(var(--x-padding-right) + 1em);
|
|
55453
|
+
/* A form control keeps a line of its own whatever the page is written in,
|
|
55454
|
+
and lh units elsewhere in the box are resolved against a real number. */
|
|
55455
|
+
line-height: normal;
|
|
55456
|
+
/* The closed control is drawn by us so it matches the other fields; the
|
|
55457
|
+
list it opens is untouched and stays the system's. */
|
|
55458
|
+
appearance: none;
|
|
55459
|
+
cursor: pointer;
|
|
55460
|
+
}
|
|
55461
|
+
&[data-readonly] .navi_control_input,
|
|
55462
|
+
&[data-disabled] .navi_control_input {
|
|
55463
|
+
cursor: inherit;
|
|
55464
|
+
}
|
|
55465
|
+
|
|
55466
|
+
.navi_select_arrow {
|
|
55467
|
+
position: absolute;
|
|
55468
|
+
top: 50%;
|
|
55469
|
+
right: var(--x-padding-right);
|
|
55470
|
+
display: flex;
|
|
55471
|
+
color: var(--color-dimmed);
|
|
55472
|
+
translate: 0 -50%;
|
|
55473
|
+
/* The arrow is drawn on top of the control it belongs to: a click on it
|
|
55474
|
+
must reach the select and open the list. */
|
|
55475
|
+
pointer-events: none;
|
|
55476
|
+
}
|
|
55477
|
+
}
|
|
55478
|
+
`;
|
|
55479
|
+
|
|
55480
|
+
/**
|
|
55481
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
55482
|
+
* value?: string,
|
|
55483
|
+
* defaultValue?: string,
|
|
55484
|
+
* signal?: import("@preact/signals").Signal<string>,
|
|
55485
|
+
* name?: string,
|
|
55486
|
+
* width?: string,
|
|
55487
|
+
* [key: string]: any,
|
|
55488
|
+
* }>}
|
|
55489
|
+
* @param {string} [value] The choice the control is GIVEN — what is already
|
|
55490
|
+
* saved. A form holding it considers that field as already sent.
|
|
55491
|
+
* @param {string} [defaultValue] The choice the control PROPOSES, and what a
|
|
55492
|
+
* reset goes back to. Sending it back is an answer, so a form counts it as
|
|
55493
|
+
* something to send.
|
|
55494
|
+
* @param {string} [width] The control's width. Left out, the box takes the
|
|
55495
|
+
* width of its widest option.
|
|
55496
|
+
*/
|
|
55497
|
+
const Select = ({
|
|
55498
|
+
width,
|
|
55499
|
+
multiple,
|
|
55500
|
+
...props
|
|
55501
|
+
}) => {
|
|
55502
|
+
import.meta.css = [inputCss + css$l, "@jsenv/navi/src/control/input/select.jsx"];
|
|
55503
|
+
const defaultRef = useRef(null);
|
|
55504
|
+
props.ref = props.ref || defaultRef;
|
|
55505
|
+
seedDefaultValueFromSignal(props);
|
|
55506
|
+
const [rootProps, hostProps] = useControlProps(props, {
|
|
55507
|
+
controlType: "select"
|
|
55508
|
+
});
|
|
55509
|
+
const {
|
|
55510
|
+
basePseudoState
|
|
55511
|
+
} = hostProps;
|
|
55512
|
+
// `type` on a <select> is the browser's own read-only "select-one".
|
|
55513
|
+
delete hostProps.type;
|
|
55514
|
+
const loading = basePseudoState[":-navi-loading"];
|
|
55515
|
+
if (width !== undefined) {
|
|
55516
|
+
// On the select, not on the box around it: the box is fit-content and the
|
|
55517
|
+
// control is what has a width to give.
|
|
55518
|
+
hostProps.width = width;
|
|
55519
|
+
}
|
|
55520
|
+
return jsxs(Box, {
|
|
55521
|
+
as: "span",
|
|
55522
|
+
inline: true,
|
|
55523
|
+
flex: true,
|
|
55524
|
+
baseClassName: "navi_input",
|
|
55525
|
+
className: "navi_select",
|
|
55526
|
+
...rootProps,
|
|
55527
|
+
basePseudoState: basePseudoState,
|
|
55528
|
+
styleCSSVars: InputStyleCSSVars,
|
|
55529
|
+
pseudoStateSelector: ".navi_control_input",
|
|
55530
|
+
pseudoClasses: InputPseudoClasses,
|
|
55531
|
+
pseudoElements: InputPseudoElements,
|
|
55532
|
+
"data-callout-anchor": ".navi_control_input",
|
|
55533
|
+
children: [jsx(LoadingOutline, {
|
|
55534
|
+
loading: loading,
|
|
55535
|
+
color: "var(--loader-color)",
|
|
55536
|
+
inset: -1
|
|
55537
|
+
}), jsx(Box, {
|
|
55538
|
+
...hostProps,
|
|
55539
|
+
as: "select",
|
|
55540
|
+
baseClassName: "navi_control_input"
|
|
55541
|
+
}), jsx("span", {
|
|
55542
|
+
className: "navi_select_arrow",
|
|
55543
|
+
children: jsx(Icon, {
|
|
55544
|
+
lineOverflow: "allow",
|
|
55545
|
+
children: jsx(ChevronDownSvg$1, {})
|
|
55546
|
+
})
|
|
55547
|
+
})]
|
|
55548
|
+
});
|
|
55549
|
+
};
|
|
55550
|
+
|
|
55077
55551
|
/**
|
|
55078
55552
|
* applySearch — matches value against searchText.
|
|
55079
55553
|
*
|
|
@@ -63656,5 +64130,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
63656
64130
|
})
|
|
63657
64131
|
});
|
|
63658
64132
|
|
|
63659
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
|
|
64133
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
|
|
63660
64134
|
//# sourceMappingURL=jsenv_navi.js.map
|