@jsenv/navi 0.27.82 → 0.27.83

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.
@@ -18968,6 +18968,17 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-single-space");
18968
18968
  */
18969
18969
 
18970
18970
 
18971
+ // Our own compact/custom duration notation interpolates raw numbers
18972
+ // directly (unlike Intl.DurationFormat, which groups thousands on its own,
18973
+ // e.g. "5 400 secondes") — this keeps that consistent without reimplementing
18974
+ // locale-aware grouping. Falls back to the raw value as-is for a
18975
+ // non-numeric mid-edit value (e.g. "2a"), which Intl.NumberFormat can't
18976
+ // format anyway.
18977
+ const formatCompactNumber = (value, lang) => {
18978
+ const n = Number(value);
18979
+ return Number.isFinite(n) ? new Intl.NumberFormat(lang).format(n) : value;
18980
+ };
18981
+
18971
18982
  /**
18972
18983
  * Formats a date as a human-readable day string.
18973
18984
  *
@@ -19221,15 +19232,20 @@ const formatTime = (date, lang) => {
19221
19232
  * "compact" uses our own notation that omits the minute symbol when hours are present.
19222
19233
  *
19223
19234
  * @param {number} minutes
19224
- * @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact", alwaysShowHours?: boolean }} [options]
19225
- * @param {boolean} [options.alwaysShowHours=false] - Normally a zero-hours
19226
- * component is dropped entirely (a real 5-minute duration should print as
19227
- * "5 minutes", not "0 hours 5 minutes") — set this to keep it (e.g. "0 h
19228
- * et 5 min"/"0h 5min"/"0h05") instead. Only meaningful when `minutes` is
19229
- * itself less than 60, i.e. the hours component would otherwise be zero;
19230
- * used by `<Time type="time">` for a time-of-day at midnight, where
19231
- * dropping the hour would make it indistinguishable from an actual
19232
- * duration (see time.jsx's own TimeTime).
19235
+ * @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact", clockStyle?: boolean }} [options]
19236
+ * @param {boolean} [options.clockStyle=false] - Set this when `minutes`
19237
+ * represents a time-of-day rather than a real duration (used by
19238
+ * `<Time type="time">`, see time.jsx's own TimeTime) — affects two
19239
+ * things at once, both consequences of a clock's "0" being a meaningful
19240
+ * hour rather than "no hours":
19241
+ * - a zero-hours component is normally dropped entirely (a real 5-minute
19242
+ * duration should print as "5 minutes", not "0 hours 5 minutes"); this
19243
+ * keeps it instead (e.g. "0 h et 5 min"/"0h 5min"/"00h05") so midnight
19244
+ * doesn't collapse to something indistinguishable from an actual
19245
+ * 5-minute duration.
19246
+ * - `format: "compact"` also zero-pads a single-digit hour to 2 digits
19247
+ * (e.g. "5h30" → "05h30"), so it reads closer to a "05:30" clock.
19248
+ * Must not be set for plain duration formatting.
19233
19249
  *
19234
19250
  * @example
19235
19251
  * formatMinuteDuration(90, { lang: "fr" }) // "1 heure 30 minutes" (long, default)
@@ -19237,20 +19253,21 @@ const formatTime = (date, lang) => {
19237
19253
  * formatMinuteDuration(90, { lang: "fr", format: "narrow" }) // "1h 30min" (Intl narrow)
19238
19254
  * formatMinuteDuration(90, { lang: "fr", format: "compact" }) // "1h30" (custom, no minute symbol)
19239
19255
  * formatMinuteDuration(45, { lang: "en", format: "compact" }) // "45min"
19240
- * formatMinuteDuration(5, { lang: "fr", format: "narrow", alwaysShowHours: true }) // "0h 5min"
19256
+ * formatMinuteDuration(5, { lang: "fr", format: "narrow", clockStyle: true }) // "0h 5min"
19257
+ * formatMinuteDuration(330, { lang: "fr", format: "compact", clockStyle: true }) // "05h30"
19241
19258
  */
19242
19259
  const formatMinuteDuration = (
19243
19260
  minutes,
19244
- { lang = languagesSignal.value, format = "long", alwaysShowHours = false } = {},
19261
+ { lang = languagesSignal.value, format = "long", clockStyle = false } = {},
19245
19262
  ) => {
19246
19263
  const h = Math.floor(minutes / 60);
19247
19264
  const m = minutes % 60;
19248
19265
  if (format !== "compact" && typeof Intl.DurationFormat !== "undefined") {
19249
19266
  const fmt = new Intl.DurationFormat(lang, {
19250
19267
  style: format, // "long", "short", or "narrow"
19251
- ...(alwaysShowHours ? { hoursDisplay: "always" } : {}),
19268
+ ...(clockStyle ? { hoursDisplay: "always" } : {}),
19252
19269
  });
19253
- if (h === 0 && !alwaysShowHours) {
19270
+ if (h === 0 && !clockStyle) {
19254
19271
  return fmt.format({ minutes: m });
19255
19272
  }
19256
19273
  if (m === 0) {
@@ -19261,13 +19278,16 @@ const formatMinuteDuration = (
19261
19278
  // format="compact": "1h30", "45min", "2h" — no minute symbol when hours are present
19262
19279
  const hSym = naviI18n("time.duration.hour_symbol", undefined, { lang });
19263
19280
  const mSym = naviI18n("time.duration.minute_symbol", undefined, { lang });
19264
- if (h === 0 && !alwaysShowHours) {
19281
+ const hStr = clockStyle
19282
+ ? String(h).padStart(2, "0")
19283
+ : formatCompactNumber(h, lang);
19284
+ if (h === 0 && !clockStyle) {
19265
19285
  return `${m}${mSym}`;
19266
19286
  }
19267
19287
  if (m === 0) {
19268
- return `${h}${hSym}`;
19288
+ return `${hStr}${hSym}`;
19269
19289
  }
19270
- return `${h}${hSym}${String(m).padStart(2, "0")}`;
19290
+ return `${hStr}${hSym}${String(m).padStart(2, "0")}`;
19271
19291
  };
19272
19292
 
19273
19293
  /**
@@ -19322,7 +19342,9 @@ const formatSecondDuration = (
19322
19342
  const mSym = naviI18n("time.duration.minute_symbol", undefined, { lang });
19323
19343
  const sSym = naviI18n("time.duration.second_symbol", undefined, { lang });
19324
19344
  const parts = [];
19325
- if (h > 0) parts.push(`${h}${hSym}`);
19345
+ // m/s are always 0-59 by construction (never need grouping); h can be
19346
+ // arbitrarily large for a long duration.
19347
+ if (h > 0) parts.push(`${formatCompactNumber(h, lang)}${hSym}`);
19326
19348
  if (m > 0) parts.push(`${m}${mSym}`);
19327
19349
  if (s > 0 || parts.length === 0) parts.push(`${s}${sSym}`);
19328
19350
  return parts.join("");
@@ -19423,36 +19445,40 @@ const formatDuration = (
19423
19445
  const parts = [];
19424
19446
 
19425
19447
  if (hasNonZero("years")) {
19426
- parts.push(`${duration.years}${sym("year")}`);
19448
+ parts.push(`${formatCompactNumber(duration.years, lang)}${sym("year")}`);
19427
19449
  }
19428
19450
  if (hasNonZero("months")) {
19429
- parts.push(`${duration.months}${sym("month")}`);
19451
+ parts.push(`${formatCompactNumber(duration.months, lang)}${sym("month")}`);
19430
19452
  }
19431
19453
  if (hasNonZero("weeks")) {
19432
- parts.push(`${duration.weeks}${sym("week")}`);
19454
+ parts.push(`${formatCompactNumber(duration.weeks, lang)}${sym("week")}`);
19433
19455
  }
19434
19456
  if (hasNonZero("days")) {
19435
- parts.push(`${duration.days}${sym("day")}`);
19457
+ parts.push(`${formatCompactNumber(duration.days, lang)}${sym("day")}`);
19436
19458
  }
19437
19459
 
19438
- // Hours + minutes: when both present, pad minutes to 2 digits after the h symbol
19460
+ // Hours + minutes: when both present, pad minutes to 2 digits after the h
19461
+ // symbol — minutes stays a plain 2-digit pad (it's always 0-59 by
19462
+ // convention), only hours goes through grouping.
19439
19463
  const hSym = sym("hour");
19440
19464
  const mSym = sym("minute");
19441
19465
  if (hasNonZero("hours") && hasNonZero("minutes")) {
19442
19466
  parts.push(
19443
- `${duration.hours}${hSym}${String(duration.minutes).padStart(2, "0")}`,
19467
+ `${formatCompactNumber(duration.hours, lang)}${hSym}${String(duration.minutes).padStart(2, "0")}`,
19444
19468
  );
19445
19469
  } else if (hasNonZero("hours")) {
19446
- parts.push(`${duration.hours}${hSym}`);
19470
+ parts.push(`${formatCompactNumber(duration.hours, lang)}${hSym}`);
19447
19471
  } else if (hasNonZero("minutes")) {
19448
- parts.push(`${duration.minutes}${mSym}`);
19472
+ parts.push(`${formatCompactNumber(duration.minutes, lang)}${mSym}`);
19449
19473
  }
19450
19474
 
19451
19475
  if (hasNonZero("seconds")) {
19452
- parts.push(`${duration.seconds}${sym("second")}`);
19476
+ parts.push(`${formatCompactNumber(duration.seconds, lang)}${sym("second")}`);
19453
19477
  }
19454
19478
  if (hasNonZero("milliseconds")) {
19455
- parts.push(`${duration.milliseconds}${sym("millisecond")}`);
19479
+ parts.push(
19480
+ `${formatCompactNumber(duration.milliseconds, lang)}${sym("millisecond")}`,
19481
+ );
19456
19482
  }
19457
19483
  return parts.join("") || "0";
19458
19484
  };
@@ -33397,7 +33423,21 @@ const InputTextualWithSuggestions = props => {
33397
33423
  // Nice side effect is that input_group.jsx will see all input is selected
33398
33424
  // and arrow left/right will always nav between inputs.
33399
33425
  // (Otherwise we would prevent left/right + show calllout about readonly)
33426
+ //
33427
+ // Not on touch: .select() there triggers the native mobile text-selection UI
33428
+ // (handles + magnifier), which makes no sense for a picker (opens a
33429
+ // popover/dialog on tap, not meant for text selection) and isn't wanted on a
33430
+ // plain readonly text input either. onPointerDown tracks the pointer type
33431
+ // that initiated the interaction (same convention as button_ui.jsx's own
33432
+ // `e.pointerType !== "touch"` check) so the focus handler — which fires
33433
+ // right after, but as a plain FocusEvent with no pointerType of its own —
33434
+ // can also skip select() for that same interaction.
33400
33435
  const useAutoSelectReadOnly = (props) => {
33436
+ const lastPointerTypeRef = useRef(null);
33437
+ const onPointerDown = (e) => {
33438
+ props.onPointerDown?.(e);
33439
+ lastPointerTypeRef.current = e.pointerType;
33440
+ };
33401
33441
  const onFocus = (e) => {
33402
33442
  props.onFocus(e);
33403
33443
  if (e.defaultPrevented) {
@@ -33406,6 +33446,9 @@ const useAutoSelectReadOnly = (props) => {
33406
33446
  if (!e.target.readOnly) {
33407
33447
  return;
33408
33448
  }
33449
+ if (lastPointerTypeRef.current === "touch") {
33450
+ return;
33451
+ }
33409
33452
  e.preventDefault();
33410
33453
  e.target.select();
33411
33454
  };
@@ -33417,11 +33460,14 @@ const useAutoSelectReadOnly = (props) => {
33417
33460
  if (!e.target.readOnly) {
33418
33461
  return;
33419
33462
  }
33463
+ if (lastPointerTypeRef.current === "touch") {
33464
+ return;
33465
+ }
33420
33466
  e.preventDefault();
33421
33467
  e.target.select();
33422
33468
  };
33423
33469
 
33424
- return { onFocus, onMouseDown };
33470
+ return { onFocus, onMouseDown, onPointerDown };
33425
33471
  };
33426
33472
 
33427
33473
  installImportMetaCssBuild(import.meta);/**
@@ -39974,22 +40020,27 @@ const TimeTime = ({
39974
40020
  // other hour keeps at least its own "N hour(s)" wording as a hint that
39975
40021
  // this is a time-of-day, not a duration — only hour 0 loses that hint
39976
40022
  // entirely.
40023
+ // clockStyle: this is always a time-of-day here, never a duration — keeps
40024
+ // a zero hour instead of dropping it (midnight would otherwise be
40025
+ // indistinguishable from an actual 5-minute duration), and in
40026
+ // format="compact" also zero-pads a single-digit hour so "5h30"/"0h05"
40027
+ // read as "05h30"/"00h05", closer to a "HH:MM" clock.
39977
40028
  let text;
39978
40029
  if (date.getHours() !== 0) {
39979
40030
  text = formatMinuteDuration(totalMinutes, {
39980
40031
  lang,
39981
- format
40032
+ format,
40033
+ clockStyle: true
39982
40034
  });
39983
40035
  } else if (format !== "long") {
39984
40036
  // short/narrow/compact: keep the "0 h"/"0h" hour part instead of
39985
- // dropping it (formatMinuteDuration's own alwaysShowHours) — e.g.
39986
- // "0 h et 5 min"/"0h 5min"/"0h05" rather than substituting a
39987
- // translated "midnight" word, which would look out of place squeezed
39988
- // into these otherwise terse, symbol-based formats.
40037
+ // dropping it — e.g. "0 h et 5 min"/"0h 5min"/"00h05" — rather than
40038
+ // substituting a translated "midnight" word, which would look out of
40039
+ // place squeezed into these otherwise terse, symbol-based formats.
39989
40040
  text = formatMinuteDuration(totalMinutes, {
39990
40041
  lang,
39991
40042
  format,
39992
- alwaysShowHours: true
40043
+ clockStyle: true
39993
40044
  });
39994
40045
  } else {
39995
40046
  const midnightWord = naviI18n("time.midnight", undefined, {
@@ -40004,7 +40055,7 @@ const TimeTime = ({
40004
40055
  text = formatMinuteDuration(totalMinutes, {
40005
40056
  lang,
40006
40057
  format,
40007
- alwaysShowHours: true
40058
+ clockStyle: true
40008
40059
  });
40009
40060
  } else {
40010
40061
  // Swap just the "0 heure(s)" part of the Intl-generated duration