@jsenv/navi 0.28.10 → 0.29.0
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
CHANGED
|
@@ -5356,7 +5356,7 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
5356
5356
|
const existingEntry = globalSignalRegistry.get(signalIdString);
|
|
5357
5357
|
{
|
|
5358
5358
|
throw new Error(
|
|
5359
|
-
`Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${existingEntry.options.getDefaultValue()})
|
|
5359
|
+
`Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${existingEntry.options.getDefaultValue()}). If this is the same stateSignal() call site running twice, the module was evaluated twice — check the network tab for the same file requested both bare and with "?hot=".`,
|
|
5360
5360
|
);
|
|
5361
5361
|
}
|
|
5362
5362
|
}
|
|
@@ -33946,10 +33946,13 @@ installImportMetaCssBuild(import.meta);const css$E = /* css */`
|
|
|
33946
33946
|
black
|
|
33947
33947
|
);
|
|
33948
33948
|
/* Readonly */
|
|
33949
|
+
/* Fading toward the surface, not toward white: "washed out" means
|
|
33950
|
+
closer to the paper behind the button, whatever color that paper is
|
|
33951
|
+
(white here IS the surface only in the light theme). */
|
|
33949
33952
|
--button-border-color-readonly: color-mix(
|
|
33950
33953
|
in srgb,
|
|
33951
33954
|
var(--button-border-color) 30%,
|
|
33952
|
-
|
|
33955
|
+
var(--navi-surface-color)
|
|
33953
33956
|
);
|
|
33954
33957
|
--button-background-color-readonly: var(--button-background-color);
|
|
33955
33958
|
--button-color-readonly: color-mix(
|
|
@@ -35085,6 +35088,9 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
35085
35088
|
* a solid surface. variant="discrete-border" does the same but keeps the border.
|
|
35086
35089
|
* - variant="discrete" + backgroundColor: the color applies at rest and hover,
|
|
35087
35090
|
* and the field goes transparent while focused.
|
|
35091
|
+
*
|
|
35092
|
+
* variant="text" is the odd one: it renders no <input> at all, just the value
|
|
35093
|
+
* as text — see InputTextualAsText below for what it is for and what it drops.
|
|
35088
35094
|
*/
|
|
35089
35095
|
const inputCss = /* css */`
|
|
35090
35096
|
@layer navi {
|
|
@@ -35181,7 +35187,12 @@ const inputCss = /* css */`
|
|
|
35181
35187
|
cursor: inherit;
|
|
35182
35188
|
pointer-events: auto;
|
|
35183
35189
|
|
|
35184
|
-
|
|
35190
|
+
/* The text of variant="text" is measured with the real thing rather than
|
|
35191
|
+
beside it: same padding, same margins, same room — so the two are the
|
|
35192
|
+
same box and a field's style can move without one of them staying
|
|
35193
|
+
behind. */
|
|
35194
|
+
.navi_control_input,
|
|
35195
|
+
.navi_input_text {
|
|
35185
35196
|
box-sizing: content-box;
|
|
35186
35197
|
min-width: 1ch;
|
|
35187
35198
|
margin-top: calc(-1 * var(--x-padding-top));
|
|
@@ -35193,6 +35204,10 @@ const inputCss = /* css */`
|
|
|
35193
35204
|
flex-grow: 1;
|
|
35194
35205
|
color: inherit;
|
|
35195
35206
|
font-size: inherit;
|
|
35207
|
+
/* A form control does not inherit the font on its own — the browser has
|
|
35208
|
+
one of its own for it, monospace for a <textarea> — so the box's font
|
|
35209
|
+
(--navi-control-font-family) is handed down by hand. */
|
|
35210
|
+
font-family: inherit;
|
|
35196
35211
|
text-align: inherit;
|
|
35197
35212
|
background: none;
|
|
35198
35213
|
border: none;
|
|
@@ -35225,6 +35240,34 @@ const inputCss = /* css */`
|
|
|
35225
35240
|
}
|
|
35226
35241
|
}
|
|
35227
35242
|
|
|
35243
|
+
.navi_input_text {
|
|
35244
|
+
/* Nothing to read yet is still a line: the box may not collapse just
|
|
35245
|
+
because the value is empty. */
|
|
35246
|
+
min-height: 1lh;
|
|
35247
|
+
/* A form control keeps a line of its own whatever line-height the page
|
|
35248
|
+
is written in; the text that stands in for one has to say the same
|
|
35249
|
+
number, or the box it is meant to match changes height. */
|
|
35250
|
+
line-height: normal;
|
|
35251
|
+
}
|
|
35252
|
+
/* The value is cut by a box of its own, and that is the whole reason it
|
|
35253
|
+
exists: a field ends its text at the content edge, while an overflow set
|
|
35254
|
+
on the padded box would let it run through the padding — the same value
|
|
35255
|
+
would then lose a character on one side and not on the other. This box
|
|
35256
|
+
IS the content edge, so both stop on the same letter.
|
|
35257
|
+
Cut rather than wrapped (a second line would be taller than the field),
|
|
35258
|
+
and cut rather than ellipsed (an ellipsis costs a character the field
|
|
35259
|
+
does not lose). */
|
|
35260
|
+
.navi_input_text_value {
|
|
35261
|
+
display: block;
|
|
35262
|
+
/* The whole content box, short value or not: a field's text is laid out
|
|
35263
|
+
in the full width whatever it holds, so text-align lands in the same
|
|
35264
|
+
place on both. */
|
|
35265
|
+
width: 100%;
|
|
35266
|
+
text-overflow: clip;
|
|
35267
|
+
white-space: nowrap;
|
|
35268
|
+
overflow: hidden;
|
|
35269
|
+
}
|
|
35270
|
+
|
|
35228
35271
|
.navi_input_slot {
|
|
35229
35272
|
margin-right: var(--slot-spacing, calc(2px + 0.1em));
|
|
35230
35273
|
margin-left: var(--slot-spacing, calc(2px + 0.1em));
|
|
@@ -35334,6 +35377,16 @@ const inputCss = /* css */`
|
|
|
35334
35377
|
}
|
|
35335
35378
|
}
|
|
35336
35379
|
|
|
35380
|
+
/* The box of a field, without a field in it: the border is kept and made
|
|
35381
|
+
invisible rather than dropped, and the background goes with it, so what
|
|
35382
|
+
is left is text sitting exactly where the value would be — swap one for
|
|
35383
|
+
the other and nothing on the page moves. */
|
|
35384
|
+
&[data-variant="text"] {
|
|
35385
|
+
--x-background-color: transparent;
|
|
35386
|
+
--x-border-color: transparent;
|
|
35387
|
+
cursor: inherit;
|
|
35388
|
+
}
|
|
35389
|
+
|
|
35337
35390
|
&[data-variant="underline"] {
|
|
35338
35391
|
border: none;
|
|
35339
35392
|
border-radius: 0;
|
|
@@ -35459,12 +35512,9 @@ const InputTextualUI = props => {
|
|
|
35459
35512
|
// when remainingProps contains expandX which would try to set width to 100%
|
|
35460
35513
|
delete inputControlRootProps.width;
|
|
35461
35514
|
if (width === "maxLength") {
|
|
35462
|
-
const
|
|
35463
|
-
|
|
35464
|
-
|
|
35465
|
-
if (maxLength !== undefined) {
|
|
35466
|
-
const isNumeric = props.inputMode === "numeric";
|
|
35467
|
-
inputControlHostProps.width = isNumeric ? `${maxLength}ch` : `calc(${maxLength} * 1.5ch)`;
|
|
35515
|
+
const widthFromMaxLength = resolveWidthFromMaxLength(inputControlHostProps.maxLength, props.inputMode);
|
|
35516
|
+
if (widthFromMaxLength !== undefined) {
|
|
35517
|
+
inputControlHostProps.width = widthFromMaxLength;
|
|
35468
35518
|
}
|
|
35469
35519
|
} else if (width === "content") {
|
|
35470
35520
|
inputControlHostProps.fieldSizing = "content";
|
|
@@ -35507,6 +35557,96 @@ const InputTextualUI = props => {
|
|
|
35507
35557
|
}), childrenWithContext]
|
|
35508
35558
|
});
|
|
35509
35559
|
};
|
|
35560
|
+
// How wide a field is when its width is left to what it accepts: a value that
|
|
35561
|
+
// cannot exceed maxLength characters needs no more room than that. Shared with
|
|
35562
|
+
// the text variant, which must land on the same number or the two would not be
|
|
35563
|
+
// the same box.
|
|
35564
|
+
const resolveWidthFromMaxLength = (maxLength, inputMode) => {
|
|
35565
|
+
if (maxLength === undefined) {
|
|
35566
|
+
return undefined;
|
|
35567
|
+
}
|
|
35568
|
+
if (inputMode === "numeric") {
|
|
35569
|
+
return `${maxLength}ch`;
|
|
35570
|
+
}
|
|
35571
|
+
return `calc(${maxLength} * 1.5ch)`;
|
|
35572
|
+
};
|
|
35573
|
+
|
|
35574
|
+
/**
|
|
35575
|
+
* variant="text" — the value, written where the field would be and taking
|
|
35576
|
+
* exactly its room: same paddings, same font, same line, and the border kept
|
|
35577
|
+
* but made invisible, so a value one only reads and the same value being
|
|
35578
|
+
* edited are one box. What it is for: an information that is sometimes known
|
|
35579
|
+
* (a name already on the profile) and sometimes asked for. Swapping the field
|
|
35580
|
+
* for its text must move nothing under it.
|
|
35581
|
+
*
|
|
35582
|
+
* It is text, and nothing else: no <input>, so nothing to focus, nothing in
|
|
35583
|
+
* the tab order, and nothing sent when the form is submitted — a value the
|
|
35584
|
+
* form must carry goes in an <Input type="hidden"> beside this one. Not a
|
|
35585
|
+
* disabled control either: `disabled`/`aria-disabled` would announce a field
|
|
35586
|
+
* one cannot use, where there is no field at all.
|
|
35587
|
+
*
|
|
35588
|
+
* The field's own props are dropped rather than half-honoured (placeholder,
|
|
35589
|
+
* the guards, the slots): they all describe an edition that does not happen
|
|
35590
|
+
* here. What is kept is what decides the box.
|
|
35591
|
+
*/
|
|
35592
|
+
// Everything a field takes that a text does not: what the value is said with,
|
|
35593
|
+
// what the box is measured from (read below, then dropped too), and all the
|
|
35594
|
+
// rest — the guards, the slots, the constraints — which describe an edition
|
|
35595
|
+
// that does not happen here. What survives is what a Box understands: width,
|
|
35596
|
+
// spacing, colors, className, style.
|
|
35597
|
+
const INPUT_ONLY_PROPS = ["value", "defaultValue", "signal", "id", "maxLength", "inputMode", "width", "variant", "type", "name", "placeholder", "required", "readOnly", "disabled", "loading", "error", "min", "max", "step", "pattern", "autoComplete", "autoCorrect", "spellcheck", "charGuard", "maxLengthGuard", "list", "suggestions", "headless", "fieldSizing", "action", "uiAction", "ui", "children"];
|
|
35598
|
+
const InputTextualAsText = props => {
|
|
35599
|
+
import.meta.css = [inputCss, "@jsenv/navi/src/control/input/input_textual.jsx"];
|
|
35600
|
+
// The id a Field handed down, which is what its Label points at.
|
|
35601
|
+
const controlId = useContext(ControlIdContext);
|
|
35602
|
+
const {
|
|
35603
|
+
value,
|
|
35604
|
+
defaultValue,
|
|
35605
|
+
signal,
|
|
35606
|
+
id,
|
|
35607
|
+
maxLength,
|
|
35608
|
+
inputMode,
|
|
35609
|
+
width = "maxLength"
|
|
35610
|
+
} = props;
|
|
35611
|
+
const valueShown = signal ? signal.value : value ?? defaultValue;
|
|
35612
|
+
const textWidth = width === "maxLength" ? resolveWidthFromMaxLength(maxLength, inputMode) : width === "content" ? undefined : width;
|
|
35613
|
+
const boxProps = {
|
|
35614
|
+
...props
|
|
35615
|
+
};
|
|
35616
|
+
for (const inputOnlyProp of INPUT_ONLY_PROPS) {
|
|
35617
|
+
delete boxProps[inputOnlyProp];
|
|
35618
|
+
}
|
|
35619
|
+
return jsx(Box, {
|
|
35620
|
+
as: "span",
|
|
35621
|
+
inline: true,
|
|
35622
|
+
flex: true,
|
|
35623
|
+
baseClassName: "navi_input",
|
|
35624
|
+
"data-variant": "text",
|
|
35625
|
+
styleCSSVars: InputStyleCSSVars,
|
|
35626
|
+
...boxProps,
|
|
35627
|
+
children: jsx(Box, {
|
|
35628
|
+
as: "span",
|
|
35629
|
+
baseClassName: "navi_input_text",
|
|
35630
|
+
id: id || controlId,
|
|
35631
|
+
width: textWidth,
|
|
35632
|
+
children: jsx("span", {
|
|
35633
|
+
className: "navi_input_text_value",
|
|
35634
|
+
children: valueShown
|
|
35635
|
+
})
|
|
35636
|
+
})
|
|
35637
|
+
});
|
|
35638
|
+
};
|
|
35639
|
+
const InputTextualAsTextResolver = props => {
|
|
35640
|
+
const Next = useNextResolver();
|
|
35641
|
+
if (props.variant === "text") {
|
|
35642
|
+
return jsx(InputTextualAsText, {
|
|
35643
|
+
...props
|
|
35644
|
+
});
|
|
35645
|
+
}
|
|
35646
|
+
return jsx(Next, {
|
|
35647
|
+
...props
|
|
35648
|
+
});
|
|
35649
|
+
};
|
|
35510
35650
|
const InputTextualFirstResolver = props => {
|
|
35511
35651
|
const Next = useNextResolver();
|
|
35512
35652
|
const defaultRef = useRef(null);
|
|
@@ -35515,7 +35655,7 @@ const InputTextualFirstResolver = props => {
|
|
|
35515
35655
|
...props
|
|
35516
35656
|
});
|
|
35517
35657
|
};
|
|
35518
|
-
const InputTextual = createComponentResolver([InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
35658
|
+
const InputTextual = createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
35519
35659
|
const RealInput = ({
|
|
35520
35660
|
maxLength,
|
|
35521
35661
|
...domProps
|
|
@@ -43668,6 +43808,10 @@ const lengthValue = value => typeof value === "number" ? `${value}px` : value;
|
|
|
43668
43808
|
*
|
|
43669
43809
|
* getTrackedItemByIndex(index): synchronous O(1) lookup of a visible item by
|
|
43670
43810
|
* its visible rank. Returns undefined when index is out of range.
|
|
43811
|
+
*
|
|
43812
|
+
* peekItems(): the items as they stand right now, without waiting for the
|
|
43813
|
+
* deferred notification — what a sibling rendering after the items must read
|
|
43814
|
+
* to paint them in the same commit.
|
|
43671
43815
|
*/
|
|
43672
43816
|
|
|
43673
43817
|
const useItemTracker = ({ onChange } = {}) => {
|
|
@@ -43770,7 +43914,8 @@ const createItemTracker = (onChange) => {
|
|
|
43770
43914
|
visibleItemsSignal.value = visibleItems;
|
|
43771
43915
|
someChange = true;
|
|
43772
43916
|
}
|
|
43773
|
-
const noMatchCountModified =
|
|
43917
|
+
const noMatchCountModified =
|
|
43918
|
+
noMatchCountSignal.peek() !== newNoMatchCount;
|
|
43774
43919
|
if (noMatchCountModified) {
|
|
43775
43920
|
noMatchCountSignal.value = newNoMatchCount;
|
|
43776
43921
|
someChange = true;
|
|
@@ -43964,9 +44109,32 @@ const createItemTracker = (onChange) => {
|
|
|
43964
44109
|
return registrations.get(key);
|
|
43965
44110
|
};
|
|
43966
44111
|
|
|
44112
|
+
// The items as they stand right now, notification pending or not — same
|
|
44113
|
+
// content as itemsSignal, minus the wait.
|
|
44114
|
+
//
|
|
44115
|
+
// Items register during their own render, while the signal is only updated
|
|
44116
|
+
// on a deferred microtask (see notify): a sibling rendering after them would
|
|
44117
|
+
// otherwise paint from an empty list and correct itself a frame later. That
|
|
44118
|
+
// frame is visible whenever the painted size feeds a layout decision — a
|
|
44119
|
+
// dialog sizing itself on its content measures the empty version and shifts
|
|
44120
|
+
// once the real one lands. Reading this instead makes the first paint the
|
|
44121
|
+
// right one. Callers must still subscribe to itemsSignal to re-render on
|
|
44122
|
+
// LATER changes; this is the value to display, not the notification.
|
|
44123
|
+
const peekItems = () => {
|
|
44124
|
+
if (!notifyScheduled) {
|
|
44125
|
+
return itemsSignal.peek();
|
|
44126
|
+
}
|
|
44127
|
+
const items = [];
|
|
44128
|
+
for (const key of allOrderedKeys) {
|
|
44129
|
+
items.push(allRegistrations.get(key));
|
|
44130
|
+
}
|
|
44131
|
+
return items;
|
|
44132
|
+
};
|
|
44133
|
+
|
|
43967
44134
|
return {
|
|
43968
44135
|
useTrackItem,
|
|
43969
44136
|
getTrackedItemByIndex,
|
|
44137
|
+
peekItems,
|
|
43970
44138
|
itemsSignal,
|
|
43971
44139
|
visibleItemsSignal,
|
|
43972
44140
|
countSignal,
|
|
@@ -48325,7 +48493,49 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
48325
48493
|
* slide that is about to leave.
|
|
48326
48494
|
*/
|
|
48327
48495
|
const css$p = /* css */`
|
|
48496
|
+
@layer navi {
|
|
48497
|
+
.navi_picker_spin {
|
|
48498
|
+
/* A picker one steps through is still a picker: what themes every picker
|
|
48499
|
+
padding themes this one too. */
|
|
48500
|
+
--picker-spin-padding-x-default: var(--navi-picker-padding-x-default);
|
|
48501
|
+
--picker-spin-padding-y-default: var(--navi-picker-padding-y-default);
|
|
48502
|
+
}
|
|
48503
|
+
}
|
|
48504
|
+
|
|
48328
48505
|
.navi_picker_spin {
|
|
48506
|
+
/* The padding is written on what is inside the box rather than on the box
|
|
48507
|
+
— the day takes all four sides, the two chevrons only the vertical ones
|
|
48508
|
+
— so the four sides are resolved once here, in the side-then-axis-then
|
|
48509
|
+
-shorthand order a Box resolves them in. What writes them: the padding
|
|
48510
|
+
props, through PICKER_SPIN_STYLE_CSS_VARS below. */
|
|
48511
|
+
--x-picker-spin-padding-top: var(
|
|
48512
|
+
--picker-spin-padding-top,
|
|
48513
|
+
var(
|
|
48514
|
+
--picker-spin-padding-y,
|
|
48515
|
+
var(--picker-spin-padding, var(--picker-spin-padding-y-default))
|
|
48516
|
+
)
|
|
48517
|
+
);
|
|
48518
|
+
--x-picker-spin-padding-right: var(
|
|
48519
|
+
--picker-spin-padding-right,
|
|
48520
|
+
var(
|
|
48521
|
+
--picker-spin-padding-x,
|
|
48522
|
+
var(--picker-spin-padding, var(--picker-spin-padding-x-default))
|
|
48523
|
+
)
|
|
48524
|
+
);
|
|
48525
|
+
--x-picker-spin-padding-bottom: var(
|
|
48526
|
+
--picker-spin-padding-bottom,
|
|
48527
|
+
var(
|
|
48528
|
+
--picker-spin-padding-y,
|
|
48529
|
+
var(--picker-spin-padding, var(--picker-spin-padding-y-default))
|
|
48530
|
+
)
|
|
48531
|
+
);
|
|
48532
|
+
--x-picker-spin-padding-left: var(
|
|
48533
|
+
--picker-spin-padding-left,
|
|
48534
|
+
var(
|
|
48535
|
+
--picker-spin-padding-x,
|
|
48536
|
+
var(--picker-spin-padding, var(--picker-spin-padding-x-default))
|
|
48537
|
+
)
|
|
48538
|
+
);
|
|
48329
48539
|
/* What the loading outline is drawn around. */
|
|
48330
48540
|
position: relative;
|
|
48331
48541
|
/* Written in the control font, like the picker it wraps: the day and its
|
|
@@ -48406,17 +48616,19 @@ const css$p = /* css */`
|
|
|
48406
48616
|
width: min(100%, var(--picker-spin-picker-width, 12ch));
|
|
48407
48617
|
translate: -50% 0;
|
|
48408
48618
|
}
|
|
48409
|
-
/*
|
|
48410
|
-
|
|
48411
|
-
|
|
48412
|
-
|
|
48413
|
-
|
|
48414
|
-
|
|
48415
|
-
|
|
48416
|
-
}
|
|
48417
|
-
/* Centred, always: the middle holds three values of three different lengths,
|
|
48619
|
+
/* Where the padding lands: all four sides on the value, the two vertical
|
|
48620
|
+
ones on the chevrons below — the same number above and below is what makes
|
|
48621
|
+
the three one line rather than three boxes, while sideways it is the room
|
|
48622
|
+
between the value and the chevron it would otherwise touch. Not on the box
|
|
48623
|
+
itself: that would push the chevrons off the corners they are rounded by.
|
|
48624
|
+
|
|
48625
|
+
Centred, always: the middle holds three values of three different lengths,
|
|
48418
48626
|
and a value that starts where the last one ended reads as a jump. */
|
|
48419
48627
|
.navi_picker_spin [data-slide] {
|
|
48628
|
+
padding-top: var(--x-picker-spin-padding-top);
|
|
48629
|
+
padding-right: var(--x-picker-spin-padding-right);
|
|
48630
|
+
padding-bottom: var(--x-picker-spin-padding-bottom);
|
|
48631
|
+
padding-left: var(--x-picker-spin-padding-left);
|
|
48420
48632
|
text-align: center;
|
|
48421
48633
|
overflow: hidden;
|
|
48422
48634
|
}
|
|
@@ -48443,8 +48655,12 @@ const css$p = /* css */`
|
|
|
48443
48655
|
around it, so "one line" means the same on both sides. */
|
|
48444
48656
|
.navi_picker_spin > .navi_picker_spin_way_out {
|
|
48445
48657
|
box-sizing: border-box;
|
|
48446
|
-
height: calc(
|
|
48447
|
-
|
|
48658
|
+
height: calc(
|
|
48659
|
+
1lh + var(--x-picker-spin-padding-top) +
|
|
48660
|
+
var(--x-picker-spin-padding-bottom)
|
|
48661
|
+
);
|
|
48662
|
+
padding-top: var(--x-picker-spin-padding-top);
|
|
48663
|
+
padding-bottom: var(--x-picker-spin-padding-bottom);
|
|
48448
48664
|
color: inherit;
|
|
48449
48665
|
background: none;
|
|
48450
48666
|
border: none;
|
|
@@ -48525,8 +48741,13 @@ const css$p = /* css */`
|
|
|
48525
48741
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
48526
48742
|
* Long by default, since this is a control one reads rather than a column
|
|
48527
48743
|
* one scans; `short` where the room is not there.
|
|
48528
|
-
* @param {string} [
|
|
48529
|
-
*
|
|
48744
|
+
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
48745
|
+
* and the four sides included. It goes on what is inside the box rather than
|
|
48746
|
+
* on the box: above and below it is taken by the day AND by the two chevrons,
|
|
48747
|
+
* which is what makes the three the same height; sideways it is the room
|
|
48748
|
+
* between the day and the chevron beside it. Left unsaid it is the padding
|
|
48749
|
+
* every picker takes (`--navi-picker-padding-x-default` and its `-y` twin),
|
|
48750
|
+
* so a theme that spaces its fields spaces this one with them.
|
|
48530
48751
|
* @param {number} [maxLines] How many lines the day may take before it is cut
|
|
48531
48752
|
* with an ellipsis — `maxLines={1}` keeps it on one line. Without it a day
|
|
48532
48753
|
* too long for the box wraps, and the box grows.
|
|
@@ -48552,7 +48773,6 @@ const DaySpin = ({
|
|
|
48552
48773
|
duration = 250,
|
|
48553
48774
|
lang,
|
|
48554
48775
|
format = "long",
|
|
48555
|
-
paddingY = "xs",
|
|
48556
48776
|
vertical,
|
|
48557
48777
|
readOnly,
|
|
48558
48778
|
disabled,
|
|
@@ -48696,13 +48916,10 @@ const DaySpin = ({
|
|
|
48696
48916
|
,
|
|
48697
48917
|
|
|
48698
48918
|
pseudoClasses: PICKER_SPIN_PSEUDO_CLASSES,
|
|
48919
|
+
styleCSSVars: PICKER_SPIN_STYLE_CSS_VARS,
|
|
48699
48920
|
"data-vertical": vertical ? "" : undefined,
|
|
48700
48921
|
"data-readonly": readOnlyResolved ? "" : undefined,
|
|
48701
48922
|
"data-disabled": disabledResolved ? "" : undefined,
|
|
48702
|
-
style: {
|
|
48703
|
-
"--picker-spin-padding-y": resolveSpacingSize(paddingY),
|
|
48704
|
-
...rest.style
|
|
48705
|
-
},
|
|
48706
48923
|
children: [jsx(LoadingOutline, {
|
|
48707
48924
|
loading: loading,
|
|
48708
48925
|
color: "var(--navi-loader-color)",
|
|
@@ -48884,6 +49101,20 @@ const WayOut = ({
|
|
|
48884
49101
|
});
|
|
48885
49102
|
const PICKER_SPIN_PSEUDO_CLASSES = [":hover", ":focus-visible"];
|
|
48886
49103
|
|
|
49104
|
+
// A padding written on this box would sit between its border and the chevrons,
|
|
49105
|
+
// which are meant to reach the corners they are rounded by — so each padding
|
|
49106
|
+
// prop becomes a variable instead, and the CSS above hands it to the day and to
|
|
49107
|
+
// the chevrons themselves. Same trick, and same var chain, as Picker's.
|
|
49108
|
+
const PICKER_SPIN_STYLE_CSS_VARS = {
|
|
49109
|
+
padding: "--picker-spin-padding",
|
|
49110
|
+
paddingX: "--picker-spin-padding-x",
|
|
49111
|
+
paddingY: "--picker-spin-padding-y",
|
|
49112
|
+
paddingTop: "--picker-spin-padding-top",
|
|
49113
|
+
paddingRight: "--picker-spin-padding-right",
|
|
49114
|
+
paddingBottom: "--picker-spin-padding-bottom",
|
|
49115
|
+
paddingLeft: "--picker-spin-padding-left"
|
|
49116
|
+
};
|
|
49117
|
+
|
|
48887
49118
|
// The date, and what it is to today when that is something one has a word for:
|
|
48888
49119
|
// "samedi 8 août (demain)" says both where one is and how far that is, and only
|
|
48889
49120
|
// the second is read at a glance.
|
|
@@ -50733,12 +50964,16 @@ const css$k = /* css */`
|
|
|
50733
50964
|
as rows scroll under the pointer. */
|
|
50734
50965
|
user-select: none;
|
|
50735
50966
|
-webkit-tap-highlight-color: transparent;
|
|
50736
|
-
/*
|
|
50737
|
-
|
|
50738
|
-
|
|
50739
|
-
|
|
50740
|
-
|
|
50741
|
-
|
|
50967
|
+
/* NO content-visibility here, ever. content-visibility: auto was tried
|
|
50968
|
+
(to skip painting clipped rows) and it breaks the first opening of any
|
|
50969
|
+
popup holding a wheel: rows born inside a closed dialog are "skipped"
|
|
50970
|
+
with no last-remembered size, so they measure as empty (padding-only) on
|
|
50971
|
+
the open's own layout — the dialog positions itself against that width
|
|
50972
|
+
and visibly slides once the real width lands. The un-skip only happens
|
|
50973
|
+
at the next update-the-rendering step: no forced reflow can read the
|
|
50974
|
+
true size any earlier, making the bug unfixable from the dialog side.
|
|
50975
|
+
There is also nothing left to win: the window renders only
|
|
50976
|
+
visibleCount + 2 recycled rows, all in or against the viewport. */
|
|
50742
50977
|
}
|
|
50743
50978
|
|
|
50744
50979
|
/* Orientation-specific sizing/layout. The mask worn by the base layer: the
|
|
@@ -51706,23 +51941,16 @@ function WheelUI(props) {
|
|
|
51706
51941
|
const trackedItems = tracker.itemsSignal.value;
|
|
51707
51942
|
const trackedItemsRef = useRef(trackedItems);
|
|
51708
51943
|
trackedItemsRef.current = trackedItems;
|
|
51709
|
-
const itemCount = trackedItems.length;
|
|
51710
51944
|
|
|
51711
51945
|
// Virtualization: render only visibleCount + 2 rows (one buffer each side to
|
|
51712
51946
|
// cover the partial rows a scroll reveals + a re-render's one-frame lag) and
|
|
51713
|
-
// recycle them
|
|
51714
|
-
//
|
|
51715
|
-
//
|
|
51716
|
-
//
|
|
51717
|
-
//
|
|
51718
|
-
//
|
|
51719
|
-
//
|
|
51720
|
-
// the transform stays consistent with it.
|
|
51721
|
-
// visibleCount + 2, but never more rows than there are values.
|
|
51722
|
-
let windowSize = visibleCount + 2;
|
|
51723
|
-
if (windowSize > itemCount) {
|
|
51724
|
-
windowSize = itemCount;
|
|
51725
|
-
}
|
|
51947
|
+
// recycle them. `centerRowSignal` holds the row currently at the center
|
|
51948
|
+
// (round(pos / itemSize)); it changes only when the wheel crosses a row (not
|
|
51949
|
+
// every frame), so the window re-renders on demand, not per frame. WheelWindow
|
|
51950
|
+
// owns both its size and its top slot (base), derived from that row and from the
|
|
51951
|
+
// items it reads off the tracker itself — see its own comment. The per-frame
|
|
51952
|
+
// transform is imperative (--wheel-offset); renderedBaseRef mirrors the base the
|
|
51953
|
+
// DOM shows so the transform stays consistent with it.
|
|
51726
51954
|
const centerRowSignal = useSignal(0);
|
|
51727
51955
|
const renderedBaseRef = useRef(0);
|
|
51728
51956
|
// Row size in px, measured once from a rendered slot (rows are uniform).
|
|
@@ -52426,8 +52654,8 @@ function WheelUI(props) {
|
|
|
52426
52654
|
className: "navi_wheel_list",
|
|
52427
52655
|
children: jsx(WheelWindow, {
|
|
52428
52656
|
centerRowSignal: centerRowSignal,
|
|
52429
|
-
|
|
52430
|
-
|
|
52657
|
+
visibleCount: visibleCount,
|
|
52658
|
+
tracker: tracker,
|
|
52431
52659
|
isLoop: isLoop,
|
|
52432
52660
|
onBaseCommit: commitRenderedBase
|
|
52433
52661
|
})
|
|
@@ -52440,8 +52668,8 @@ function WheelUI(props) {
|
|
|
52440
52668
|
className: "navi_wheel_list",
|
|
52441
52669
|
children: jsx(WheelWindow, {
|
|
52442
52670
|
centerRowSignal: centerRowSignal,
|
|
52443
|
-
|
|
52444
|
-
|
|
52671
|
+
visibleCount: visibleCount,
|
|
52672
|
+
tracker: tracker,
|
|
52445
52673
|
isLoop: isLoop
|
|
52446
52674
|
})
|
|
52447
52675
|
})
|
|
@@ -52458,21 +52686,38 @@ const WHEEL_PSEUDO_CLASSES = [":focus-within", ":focus-visible", ":read-only", "
|
|
|
52458
52686
|
|
|
52459
52687
|
// The recycled window: a fixed set of `windowSize` <li> slots (one per visible
|
|
52460
52688
|
// row + 2 buffer), keyed by slot position so each DOM node is reused and only its
|
|
52461
|
-
// content re-renders. `base` (the top slot's value index)
|
|
52462
|
-
//
|
|
52463
|
-
//
|
|
52464
|
-
//
|
|
52465
|
-
//
|
|
52466
|
-
//
|
|
52467
|
-
//
|
|
52689
|
+
// content re-renders. `base` (the top slot's value index) and windowSize are both
|
|
52690
|
+
// derived here rather than upstream, from the items read straight off the tracker:
|
|
52691
|
+
// this component renders right after the Wheel.Item children, which register
|
|
52692
|
+
// during their own render, so it can draw the real rows in the very first commit.
|
|
52693
|
+
// Taking them from the parent instead would mean drawing an empty wheel first (the
|
|
52694
|
+
// parent renders before its children, and the tracker's signal only lands on a
|
|
52695
|
+
// microtask) — a wheel with no width, which is enough to misplace whatever sizes
|
|
52696
|
+
// itself on it, e.g. a dialog centered on its content.
|
|
52697
|
+
// Each slot shows items[base + slot] (wrapped when looping). It reads
|
|
52698
|
+
// centerRowSignal, so it re-renders ONLY when the wheel crosses a row, not every
|
|
52699
|
+
// frame — the per-frame motion is the imperative transform in applyOffset. On each
|
|
52700
|
+
// new base it calls onBaseCommit so the transform re-syncs to the freshly rendered
|
|
52701
|
+
// rows same-frame.
|
|
52468
52702
|
const WheelWindow = ({
|
|
52469
52703
|
centerRowSignal,
|
|
52470
|
-
|
|
52471
|
-
|
|
52704
|
+
visibleCount,
|
|
52705
|
+
tracker,
|
|
52472
52706
|
isLoop,
|
|
52473
52707
|
onBaseCommit
|
|
52474
52708
|
}) => {
|
|
52709
|
+
// Subscribing is what re-renders this window when items are added or removed
|
|
52710
|
+
// later; what to draw right now is read synchronously below.
|
|
52711
|
+
// eslint-disable-next-line no-unused-expressions
|
|
52712
|
+
tracker.itemsSignal.value;
|
|
52713
|
+
const trackedItems = tracker.peekItems();
|
|
52475
52714
|
const count = trackedItems.length;
|
|
52715
|
+
// visibleCount + 2 (one buffer row each side), but never more rows than there
|
|
52716
|
+
// are values.
|
|
52717
|
+
let windowSize = visibleCount + 2;
|
|
52718
|
+
if (windowSize > count) {
|
|
52719
|
+
windowSize = count;
|
|
52720
|
+
}
|
|
52476
52721
|
let base = centerRowSignal.value - Math.floor(windowSize / 2);
|
|
52477
52722
|
// Non-loop: keep the window inside the value range so every slot maps to a real
|
|
52478
52723
|
// value; the transform then leaves the blank runway above the first / below the
|
|
@@ -57972,9 +58217,9 @@ const css$2 = /* css */`
|
|
|
57972
58217
|
.navi_card_layout {
|
|
57973
58218
|
--layout-margin: 30px;
|
|
57974
58219
|
--layout-padding: 20px;
|
|
57975
|
-
--layout-background:
|
|
58220
|
+
--layout-background: var(--navi-surface-color);
|
|
57976
58221
|
--layout-border-width: 2px;
|
|
57977
|
-
--layout-border-color:
|
|
58222
|
+
--layout-border-color: var(--navi-popup-border-color);
|
|
57978
58223
|
--layout-border-radius: 10px;
|
|
57979
58224
|
--layout-min-width: 300px;
|
|
57980
58225
|
--layout-min-height: auto;
|
|
@@ -58093,7 +58338,9 @@ installImportMetaCssBuild(import.meta);const css$1 = /* css */`
|
|
|
58093
58338
|
@layer navi {
|
|
58094
58339
|
.navi_viewport_layout {
|
|
58095
58340
|
--layout-padding: 40px;
|
|
58096
|
-
|
|
58341
|
+
/* The page's own paper: the shared surface token, so a dark theme gets a
|
|
58342
|
+
dark page without every screen overriding it. */
|
|
58343
|
+
--layout-background: var(--navi-surface-color);
|
|
58097
58344
|
}
|
|
58098
58345
|
}
|
|
58099
58346
|
|
|
@@ -58300,14 +58547,17 @@ const css = /* css */`
|
|
|
58300
58547
|
z-index: 1;
|
|
58301
58548
|
background-color: var(--navi-popup-background-color);
|
|
58302
58549
|
}
|
|
58550
|
+
/* The separator token, not the popup border: these lines split two
|
|
58551
|
+
regions of the panel's own surface (the head/foot from what scrolls
|
|
58552
|
+
under them), they are not the panel's edge. */
|
|
58303
58553
|
.navi_side_panel_head {
|
|
58304
58554
|
top: 0;
|
|
58305
|
-
border-bottom: 1px solid var(--navi-
|
|
58555
|
+
border-bottom: 1px solid var(--navi-separator-color-default);
|
|
58306
58556
|
}
|
|
58307
58557
|
.navi_side_panel_foot {
|
|
58308
58558
|
bottom: 0;
|
|
58309
58559
|
padding: 12px 16px;
|
|
58310
|
-
border-top: 1px solid var(--navi-
|
|
58560
|
+
border-top: 1px solid var(--navi-separator-color-default);
|
|
58311
58561
|
}
|
|
58312
58562
|
}
|
|
58313
58563
|
`;
|