@jsenv/navi 0.28.11 → 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.
@@ -35088,6 +35088,9 @@ installImportMetaCssBuild(import.meta);/**
35088
35088
  * a solid surface. variant="discrete-border" does the same but keeps the border.
35089
35089
  * - variant="discrete" + backgroundColor: the color applies at rest and hover,
35090
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.
35091
35094
  */
35092
35095
  const inputCss = /* css */`
35093
35096
  @layer navi {
@@ -35184,7 +35187,12 @@ const inputCss = /* css */`
35184
35187
  cursor: inherit;
35185
35188
  pointer-events: auto;
35186
35189
 
35187
- .navi_control_input {
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 {
35188
35196
  box-sizing: content-box;
35189
35197
  min-width: 1ch;
35190
35198
  margin-top: calc(-1 * var(--x-padding-top));
@@ -35196,6 +35204,10 @@ const inputCss = /* css */`
35196
35204
  flex-grow: 1;
35197
35205
  color: inherit;
35198
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;
35199
35211
  text-align: inherit;
35200
35212
  background: none;
35201
35213
  border: none;
@@ -35228,6 +35240,34 @@ const inputCss = /* css */`
35228
35240
  }
35229
35241
  }
35230
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
+
35231
35271
  .navi_input_slot {
35232
35272
  margin-right: var(--slot-spacing, calc(2px + 0.1em));
35233
35273
  margin-left: var(--slot-spacing, calc(2px + 0.1em));
@@ -35337,6 +35377,16 @@ const inputCss = /* css */`
35337
35377
  }
35338
35378
  }
35339
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
+
35340
35390
  &[data-variant="underline"] {
35341
35391
  border: none;
35342
35392
  border-radius: 0;
@@ -35462,12 +35512,9 @@ const InputTextualUI = props => {
35462
35512
  // when remainingProps contains expandX which would try to set width to 100%
35463
35513
  delete inputControlRootProps.width;
35464
35514
  if (width === "maxLength") {
35465
- const {
35466
- maxLength
35467
- } = inputControlHostProps;
35468
- if (maxLength !== undefined) {
35469
- const isNumeric = props.inputMode === "numeric";
35470
- 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;
35471
35518
  }
35472
35519
  } else if (width === "content") {
35473
35520
  inputControlHostProps.fieldSizing = "content";
@@ -35510,6 +35557,96 @@ const InputTextualUI = props => {
35510
35557
  }), childrenWithContext]
35511
35558
  });
35512
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
+ };
35513
35650
  const InputTextualFirstResolver = props => {
35514
35651
  const Next = useNextResolver();
35515
35652
  const defaultRef = useRef(null);
@@ -35518,7 +35655,7 @@ const InputTextualFirstResolver = props => {
35518
35655
  ...props
35519
35656
  });
35520
35657
  };
35521
- const InputTextual = createComponentResolver([InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
35658
+ const InputTextual = createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
35522
35659
  const RealInput = ({
35523
35660
  maxLength,
35524
35661
  ...domProps
@@ -48356,7 +48493,49 @@ installImportMetaCssBuild(import.meta);/**
48356
48493
  * slide that is about to leave.
48357
48494
  */
48358
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
+
48359
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
+ );
48360
48539
  /* What the loading outline is drawn around. */
48361
48540
  position: relative;
48362
48541
  /* Written in the control font, like the picker it wraps: the day and its
@@ -48437,17 +48616,19 @@ const css$p = /* css */`
48437
48616
  width: min(100%, var(--picker-spin-picker-width, 12ch));
48438
48617
  translate: -50% 0;
48439
48618
  }
48440
- /* The value takes it as padding; the two chevrons take it as their own
48441
- --button-padding-y (a button's padding lives on its content, see
48442
- button_ui.jsx), which is why it is said as a variable rather than applied
48443
- here. Same number on all three: it is what makes them one line rather than
48444
- three boxes. */
48445
- .navi_picker_spin [data-slide] {
48446
- padding-block: var(--picker-spin-padding-y);
48447
- }
48448
- /* 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,
48449
48626
  and a value that starts where the last one ended reads as a jump. */
48450
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);
48451
48632
  text-align: center;
48452
48633
  overflow: hidden;
48453
48634
  }
@@ -48474,8 +48655,12 @@ const css$p = /* css */`
48474
48655
  around it, so "one line" means the same on both sides. */
48475
48656
  .navi_picker_spin > .navi_picker_spin_way_out {
48476
48657
  box-sizing: border-box;
48477
- height: calc(1lh + 2 * var(--picker-spin-padding-y));
48478
- padding-block: var(--picker-spin-padding-y);
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);
48479
48664
  color: inherit;
48480
48665
  background: none;
48481
48666
  border: none;
@@ -48556,8 +48741,13 @@ const css$p = /* css */`
48556
48741
  * @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
48557
48742
  * Long by default, since this is a control one reads rather than a column
48558
48743
  * one scans; `short` where the room is not there.
48559
- * @param {string} [paddingY="xs"] Above and below, on the day AND on the two
48560
- * chevrons: it is what makes the three the same height.
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.
48561
48751
  * @param {number} [maxLines] How many lines the day may take before it is cut
48562
48752
  * with an ellipsis — `maxLines={1}` keeps it on one line. Without it a day
48563
48753
  * too long for the box wraps, and the box grows.
@@ -48583,7 +48773,6 @@ const DaySpin = ({
48583
48773
  duration = 250,
48584
48774
  lang,
48585
48775
  format = "long",
48586
- paddingY = "xs",
48587
48776
  vertical,
48588
48777
  readOnly,
48589
48778
  disabled,
@@ -48727,13 +48916,10 @@ const DaySpin = ({
48727
48916
  ,
48728
48917
 
48729
48918
  pseudoClasses: PICKER_SPIN_PSEUDO_CLASSES,
48919
+ styleCSSVars: PICKER_SPIN_STYLE_CSS_VARS,
48730
48920
  "data-vertical": vertical ? "" : undefined,
48731
48921
  "data-readonly": readOnlyResolved ? "" : undefined,
48732
48922
  "data-disabled": disabledResolved ? "" : undefined,
48733
- style: {
48734
- "--picker-spin-padding-y": resolveSpacingSize(paddingY),
48735
- ...rest.style
48736
- },
48737
48923
  children: [jsx(LoadingOutline, {
48738
48924
  loading: loading,
48739
48925
  color: "var(--navi-loader-color)",
@@ -48915,6 +49101,20 @@ const WayOut = ({
48915
49101
  });
48916
49102
  const PICKER_SPIN_PSEUDO_CLASSES = [":hover", ":focus-visible"];
48917
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
+
48918
49118
  // The date, and what it is to today when that is something one has a word for:
48919
49119
  // "samedi 8 août (demain)" says both where one is and how far that is, and only
48920
49120
  // the second is read at a glance.