@camstack/ui-library 1.2.7 → 1.2.9

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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Leaf formatting for the device State panel — in particular, turning epoch
3
+ * timestamps into readable local dates.
4
+ *
5
+ * ## Why this needs a rule rather than a `typeof`
6
+ *
7
+ * `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
8
+ * required to carry one. But "large number" is not "timestamp": `bitrate`,
9
+ * `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
10
+ * bitrate as a date in 1970 is worse than leaving it raw.
11
+ *
12
+ * So a value is treated as a time point only when BOTH hold:
13
+ *
14
+ * 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
15
+ * `lastSeen`, `expires…`; and
16
+ * 2. the NUMBER falls in a plausible epoch range.
17
+ *
18
+ * Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
19
+ * "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
20
+ * Durations are deliberately NOT converted — this formats instants, not spans.
21
+ *
22
+ * ## The raw value is never lost
23
+ *
24
+ * This is a debugging panel, so the exact number still matters. The formatted text
25
+ * is what is shown; `raw` is returned alongside for a `title` tooltip, and search
26
+ * matches against both. Replacing the value outright would trade one kind of
27
+ * unreadability for another.
28
+ */
29
+ export declare function isTimeKey(key: string): boolean;
30
+ /** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
31
+ export declare function epochMsOf(value: number): number | null;
32
+ /**
33
+ * Locale-formatted instant. `undefined` locale means the viewer's own locale,
34
+ * which is the point — an operator in Rome should not read UTC.
35
+ */
36
+ export declare function formatLocalDate(epochMs: number): string;
37
+ export interface FormattedLeaf {
38
+ /** What is displayed. */
39
+ readonly text: string;
40
+ /**
41
+ * The untouched original, present ONLY when `text` differs from it — so a
42
+ * caller can show a tooltip without one on every unremarkable value.
43
+ */
44
+ readonly raw: string | null;
45
+ }
46
+ /**
47
+ * Format one leaf of the state tree, given the key it sits under.
48
+ *
49
+ * Handles two timestamp shapes:
50
+ * - a NUMBER under a time-shaped key → local date, raw kept
51
+ * - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
52
+ * string is unambiguous, and a Moleculer round-trip turns every `Date` into
53
+ * one, so these appear whether or not the key looks temporal.
54
+ */
55
+ export declare function formatLeafValue(key: string, value: unknown): FormattedLeaf;
56
+ /**
57
+ * Strict ISO-8601 with a time part. Requires the `T` and at least
58
+ * `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
59
+ * label than a date, and `Date.parse` would silently read it as UTC midnight and
60
+ * shift it a day in a negative-offset locale.
61
+ */
62
+ export declare function isIsoDateString(value: string): boolean;
package/dist/index.cjs CHANGED
@@ -44922,6 +44922,137 @@ function StatusBadge$1({ status }) {
44922
44922
  }
44923
44923
  }
44924
44924
  //#endregion
44925
+ //#region src/composites/format-state-value.ts
44926
+ /**
44927
+ * Leaf formatting for the device State panel — in particular, turning epoch
44928
+ * timestamps into readable local dates.
44929
+ *
44930
+ * ## Why this needs a rule rather than a `typeof`
44931
+ *
44932
+ * `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
44933
+ * required to carry one. But "large number" is not "timestamp": `bitrate`,
44934
+ * `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
44935
+ * bitrate as a date in 1970 is worse than leaving it raw.
44936
+ *
44937
+ * So a value is treated as a time point only when BOTH hold:
44938
+ *
44939
+ * 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
44940
+ * `lastSeen`, `expires…`; and
44941
+ * 2. the NUMBER falls in a plausible epoch range.
44942
+ *
44943
+ * Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
44944
+ * "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
44945
+ * Durations are deliberately NOT converted — this formats instants, not spans.
44946
+ *
44947
+ * ## The raw value is never lost
44948
+ *
44949
+ * This is a debugging panel, so the exact number still matters. The formatted text
44950
+ * is what is shown; `raw` is returned alongside for a `title` tooltip, and search
44951
+ * matches against both. Replacing the value outright would trade one kind of
44952
+ * unreadability for another.
44953
+ */
44954
+ /**
44955
+ * Keys that name an instant. Anchored to the END of the key so `updatedAt` and
44956
+ * `lastPushAtMs` match while `timeoutMs`, `batchIntervalMs`, `retentionDays` and
44957
+ * `uptimeSeconds` do not.
44958
+ */
44959
+ var TIME_KEY_PATTERNS = [
44960
+ /at$/i,
44961
+ /atms$/i,
44962
+ /^timestamp$/i,
44963
+ /timestamp$/i,
44964
+ /since$/i,
44965
+ /^lastseen$/i,
44966
+ /lastseen$/i,
44967
+ /^expires?$/i,
44968
+ /expiry$/i
44969
+ ];
44970
+ /**
44971
+ * Plausible epoch windows, roughly 2001 → 2096. Deliberately narrow: it is what
44972
+ * separates a timestamp from a byte count that happens to be large, and from `0`
44973
+ * (which in this codebase means "never", and must stay `0` rather than becoming
44974
+ * 1 January 1970).
44975
+ */
44976
+ var MS_EPOCH_MIN = 0xe8d4a51000;
44977
+ var MS_EPOCH_MAX = 4e12;
44978
+ var SEC_EPOCH_MIN = 1e9;
44979
+ var SEC_EPOCH_MAX = 4e9;
44980
+ function isTimeKey(key) {
44981
+ return TIME_KEY_PATTERNS.some((pattern) => pattern.test(key));
44982
+ }
44983
+ /** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
44984
+ function epochMsOf(value) {
44985
+ if (!Number.isFinite(value)) return null;
44986
+ if (value >= MS_EPOCH_MIN && value <= MS_EPOCH_MAX) return value;
44987
+ if (value >= SEC_EPOCH_MIN && value <= SEC_EPOCH_MAX) return value * 1e3;
44988
+ return null;
44989
+ }
44990
+ /**
44991
+ * Locale-formatted instant. `undefined` locale means the viewer's own locale,
44992
+ * which is the point — an operator in Rome should not read UTC.
44993
+ */
44994
+ function formatLocalDate(epochMs) {
44995
+ const date = new Date(epochMs);
44996
+ if (Number.isNaN(date.getTime())) return String(epochMs);
44997
+ return date.toLocaleString(void 0, {
44998
+ year: "numeric",
44999
+ month: "2-digit",
45000
+ day: "2-digit",
45001
+ hour: "2-digit",
45002
+ minute: "2-digit",
45003
+ second: "2-digit"
45004
+ });
45005
+ }
45006
+ function plain(v) {
45007
+ if (v === null) return "null";
45008
+ if (v === void 0) return "undefined";
45009
+ if (typeof v === "string") return v;
45010
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
45011
+ try {
45012
+ return JSON.stringify(v);
45013
+ } catch {
45014
+ return String(v);
45015
+ }
45016
+ }
45017
+ /**
45018
+ * Format one leaf of the state tree, given the key it sits under.
45019
+ *
45020
+ * Handles two timestamp shapes:
45021
+ * - a NUMBER under a time-shaped key → local date, raw kept
45022
+ * - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
45023
+ * string is unambiguous, and a Moleculer round-trip turns every `Date` into
45024
+ * one, so these appear whether or not the key looks temporal.
45025
+ */
45026
+ function formatLeafValue(key, value) {
45027
+ if (typeof value === "number" && isTimeKey(key)) {
45028
+ const epochMs = epochMsOf(value);
45029
+ if (epochMs !== null) return {
45030
+ text: formatLocalDate(epochMs),
45031
+ raw: String(value)
45032
+ };
45033
+ }
45034
+ if (typeof value === "string" && isIsoDateString(value)) {
45035
+ const parsed = Date.parse(value);
45036
+ if (!Number.isNaN(parsed)) return {
45037
+ text: formatLocalDate(parsed),
45038
+ raw: value
45039
+ };
45040
+ }
45041
+ return {
45042
+ text: plain(value),
45043
+ raw: null
45044
+ };
45045
+ }
45046
+ /**
45047
+ * Strict ISO-8601 with a time part. Requires the `T` and at least
45048
+ * `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
45049
+ * label than a date, and `Date.parse` would silently read it as UTC midnight and
45050
+ * shift it a day in a negative-offset locale.
45051
+ */
45052
+ function isIsoDateString(value) {
45053
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?$/.test(value);
45054
+ }
45055
+ //#endregion
44925
45056
  //#region src/composites/state-values-stream.tsx
44926
45057
  /**
44927
45058
  * StateValuesStream — live current-state tree for ui-library.
@@ -45443,24 +45574,28 @@ function CapNode({ capName, slice, updatedAt, searchNeedle, isPathOpen, onToggle
45443
45574
  function TreeNode({ path, nodeKey, value, depth, searchNeedle, isPathOpen, onToggle }) {
45444
45575
  const branch = asBranch(value);
45445
45576
  const indentStyle = { paddingLeft: `${12 + depth * 14}px` };
45446
- if (!branch) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
45447
- className: "flex items-start gap-1 px-3 py-0.5",
45448
- style: indentStyle,
45449
- children: [
45450
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45451
- className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
45452
- children: nodeKey
45453
- }),
45454
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45455
- className: "text-foreground-subtle",
45456
- children: ":"
45457
- }),
45458
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45459
- className: cn("break-all", highlightCls(formatValue(value), searchNeedle, valueCls(value))),
45460
- children: formatValue(value)
45461
- })
45462
- ]
45463
- });
45577
+ if (!branch) {
45578
+ const leaf = formatLeafValue(nodeKey, value);
45579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
45580
+ className: "flex items-start gap-1 px-3 py-0.5",
45581
+ style: indentStyle,
45582
+ children: [
45583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45584
+ className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
45585
+ children: nodeKey
45586
+ }),
45587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45588
+ className: "text-foreground-subtle",
45589
+ children: ":"
45590
+ }),
45591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45592
+ className: cn("break-all", highlightCls(leaf.text, searchNeedle, valueCls(value))),
45593
+ ...leaf.raw === null ? {} : { title: leaf.raw },
45594
+ children: leaf.text
45595
+ })
45596
+ ]
45597
+ });
45598
+ }
45464
45599
  const open = isPathOpen(path, depth);
45465
45600
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
45466
45601
  className: "flex items-center gap-1 px-3 py-0.5 hover:bg-surface-hover/30 cursor-pointer select-none",
@@ -45525,17 +45660,6 @@ function asBranch(value) {
45525
45660
  }
45526
45661
  return null;
45527
45662
  }
45528
- function formatValue(v) {
45529
- if (v === null) return "null";
45530
- if (v === void 0) return "undefined";
45531
- if (typeof v === "string") return v;
45532
- if (typeof v === "number" || typeof v === "boolean") return String(v);
45533
- try {
45534
- return JSON.stringify(v);
45535
- } catch {
45536
- return String(v);
45537
- }
45538
- }
45539
45663
  /** Tailwind colour for a leaf value by JS type. */
45540
45664
  function valueCls(v) {
45541
45665
  if (v === null || v === void 0) return "text-foreground-subtle/60";
@@ -45549,18 +45673,27 @@ function highlightCls(text, needle, base) {
45549
45673
  if (needle && text.toLowerCase().includes(needle)) return cn(base, "bg-violet-500/20 rounded px-0.5");
45550
45674
  return base;
45551
45675
  }
45552
- /** Deep search a slice — true when `needle` appears in any key or any
45553
- * stringified leaf value anywhere in the tree. */
45554
- function sliceMatchesSearch(value, needle) {
45555
- if (Array.isArray(value)) return value.some((v) => sliceMatchesSearch(v, needle));
45676
+ /**
45677
+ * Deep search a slice — true when `needle` appears in any key or any leaf value
45678
+ * anywhere in the tree.
45679
+ *
45680
+ * `key` is threaded down so a leaf is matched the way it is DISPLAYED: a timestamp
45681
+ * shown as a local date has to be findable by typing part of that date. Both forms
45682
+ * match, so the raw epoch still works too — the operator should not have to know
45683
+ * which one the panel chose to render.
45684
+ */
45685
+ function sliceMatchesSearch(value, needle, key = "") {
45686
+ if (Array.isArray(value)) return value.some((v, i) => sliceMatchesSearch(v, needle, String(i)));
45556
45687
  if (value && typeof value === "object") {
45557
45688
  for (const [k, v] of Object.entries(value)) {
45558
45689
  if (k.toLowerCase().includes(needle)) return true;
45559
- if (sliceMatchesSearch(v, needle)) return true;
45690
+ if (sliceMatchesSearch(v, needle, k)) return true;
45560
45691
  }
45561
45692
  return false;
45562
45693
  }
45563
- return formatValue(value).toLowerCase().includes(needle);
45694
+ const leaf = formatLeafValue(key, value);
45695
+ if (leaf.text.toLowerCase().includes(needle)) return true;
45696
+ return leaf.raw !== null && leaf.raw.toLowerCase().includes(needle);
45564
45697
  }
45565
45698
  /** Human "Ns ago" / "Nm ago" age label for the per-cap freshness
45566
45699
  * indicator. */
package/dist/index.js CHANGED
@@ -44898,6 +44898,137 @@ function StatusBadge$1({ status }) {
44898
44898
  }
44899
44899
  }
44900
44900
  //#endregion
44901
+ //#region src/composites/format-state-value.ts
44902
+ /**
44903
+ * Leaf formatting for the device State panel — in particular, turning epoch
44904
+ * timestamps into readable local dates.
44905
+ *
44906
+ * ## Why this needs a rule rather than a `typeof`
44907
+ *
44908
+ * `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
44909
+ * required to carry one. But "large number" is not "timestamp": `bitrate`,
44910
+ * `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
44911
+ * bitrate as a date in 1970 is worse than leaving it raw.
44912
+ *
44913
+ * So a value is treated as a time point only when BOTH hold:
44914
+ *
44915
+ * 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
44916
+ * `lastSeen`, `expires…`; and
44917
+ * 2. the NUMBER falls in a plausible epoch range.
44918
+ *
44919
+ * Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
44920
+ * "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
44921
+ * Durations are deliberately NOT converted — this formats instants, not spans.
44922
+ *
44923
+ * ## The raw value is never lost
44924
+ *
44925
+ * This is a debugging panel, so the exact number still matters. The formatted text
44926
+ * is what is shown; `raw` is returned alongside for a `title` tooltip, and search
44927
+ * matches against both. Replacing the value outright would trade one kind of
44928
+ * unreadability for another.
44929
+ */
44930
+ /**
44931
+ * Keys that name an instant. Anchored to the END of the key so `updatedAt` and
44932
+ * `lastPushAtMs` match while `timeoutMs`, `batchIntervalMs`, `retentionDays` and
44933
+ * `uptimeSeconds` do not.
44934
+ */
44935
+ var TIME_KEY_PATTERNS = [
44936
+ /at$/i,
44937
+ /atms$/i,
44938
+ /^timestamp$/i,
44939
+ /timestamp$/i,
44940
+ /since$/i,
44941
+ /^lastseen$/i,
44942
+ /lastseen$/i,
44943
+ /^expires?$/i,
44944
+ /expiry$/i
44945
+ ];
44946
+ /**
44947
+ * Plausible epoch windows, roughly 2001 → 2096. Deliberately narrow: it is what
44948
+ * separates a timestamp from a byte count that happens to be large, and from `0`
44949
+ * (which in this codebase means "never", and must stay `0` rather than becoming
44950
+ * 1 January 1970).
44951
+ */
44952
+ var MS_EPOCH_MIN = 0xe8d4a51000;
44953
+ var MS_EPOCH_MAX = 4e12;
44954
+ var SEC_EPOCH_MIN = 1e9;
44955
+ var SEC_EPOCH_MAX = 4e9;
44956
+ function isTimeKey(key) {
44957
+ return TIME_KEY_PATTERNS.some((pattern) => pattern.test(key));
44958
+ }
44959
+ /** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
44960
+ function epochMsOf(value) {
44961
+ if (!Number.isFinite(value)) return null;
44962
+ if (value >= MS_EPOCH_MIN && value <= MS_EPOCH_MAX) return value;
44963
+ if (value >= SEC_EPOCH_MIN && value <= SEC_EPOCH_MAX) return value * 1e3;
44964
+ return null;
44965
+ }
44966
+ /**
44967
+ * Locale-formatted instant. `undefined` locale means the viewer's own locale,
44968
+ * which is the point — an operator in Rome should not read UTC.
44969
+ */
44970
+ function formatLocalDate(epochMs) {
44971
+ const date = new Date(epochMs);
44972
+ if (Number.isNaN(date.getTime())) return String(epochMs);
44973
+ return date.toLocaleString(void 0, {
44974
+ year: "numeric",
44975
+ month: "2-digit",
44976
+ day: "2-digit",
44977
+ hour: "2-digit",
44978
+ minute: "2-digit",
44979
+ second: "2-digit"
44980
+ });
44981
+ }
44982
+ function plain(v) {
44983
+ if (v === null) return "null";
44984
+ if (v === void 0) return "undefined";
44985
+ if (typeof v === "string") return v;
44986
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
44987
+ try {
44988
+ return JSON.stringify(v);
44989
+ } catch {
44990
+ return String(v);
44991
+ }
44992
+ }
44993
+ /**
44994
+ * Format one leaf of the state tree, given the key it sits under.
44995
+ *
44996
+ * Handles two timestamp shapes:
44997
+ * - a NUMBER under a time-shaped key → local date, raw kept
44998
+ * - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
44999
+ * string is unambiguous, and a Moleculer round-trip turns every `Date` into
45000
+ * one, so these appear whether or not the key looks temporal.
45001
+ */
45002
+ function formatLeafValue(key, value) {
45003
+ if (typeof value === "number" && isTimeKey(key)) {
45004
+ const epochMs = epochMsOf(value);
45005
+ if (epochMs !== null) return {
45006
+ text: formatLocalDate(epochMs),
45007
+ raw: String(value)
45008
+ };
45009
+ }
45010
+ if (typeof value === "string" && isIsoDateString(value)) {
45011
+ const parsed = Date.parse(value);
45012
+ if (!Number.isNaN(parsed)) return {
45013
+ text: formatLocalDate(parsed),
45014
+ raw: value
45015
+ };
45016
+ }
45017
+ return {
45018
+ text: plain(value),
45019
+ raw: null
45020
+ };
45021
+ }
45022
+ /**
45023
+ * Strict ISO-8601 with a time part. Requires the `T` and at least
45024
+ * `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
45025
+ * label than a date, and `Date.parse` would silently read it as UTC midnight and
45026
+ * shift it a day in a negative-offset locale.
45027
+ */
45028
+ function isIsoDateString(value) {
45029
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?$/.test(value);
45030
+ }
45031
+ //#endregion
44901
45032
  //#region src/composites/state-values-stream.tsx
44902
45033
  /**
44903
45034
  * StateValuesStream — live current-state tree for ui-library.
@@ -45419,24 +45550,28 @@ function CapNode({ capName, slice, updatedAt, searchNeedle, isPathOpen, onToggle
45419
45550
  function TreeNode({ path, nodeKey, value, depth, searchNeedle, isPathOpen, onToggle }) {
45420
45551
  const branch = asBranch(value);
45421
45552
  const indentStyle = { paddingLeft: `${12 + depth * 14}px` };
45422
- if (!branch) return /* @__PURE__ */ jsxs("div", {
45423
- className: "flex items-start gap-1 px-3 py-0.5",
45424
- style: indentStyle,
45425
- children: [
45426
- /* @__PURE__ */ jsx("span", {
45427
- className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
45428
- children: nodeKey
45429
- }),
45430
- /* @__PURE__ */ jsx("span", {
45431
- className: "text-foreground-subtle",
45432
- children: ":"
45433
- }),
45434
- /* @__PURE__ */ jsx("span", {
45435
- className: cn("break-all", highlightCls(formatValue(value), searchNeedle, valueCls(value))),
45436
- children: formatValue(value)
45437
- })
45438
- ]
45439
- });
45553
+ if (!branch) {
45554
+ const leaf = formatLeafValue(nodeKey, value);
45555
+ return /* @__PURE__ */ jsxs("div", {
45556
+ className: "flex items-start gap-1 px-3 py-0.5",
45557
+ style: indentStyle,
45558
+ children: [
45559
+ /* @__PURE__ */ jsx("span", {
45560
+ className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
45561
+ children: nodeKey
45562
+ }),
45563
+ /* @__PURE__ */ jsx("span", {
45564
+ className: "text-foreground-subtle",
45565
+ children: ":"
45566
+ }),
45567
+ /* @__PURE__ */ jsx("span", {
45568
+ className: cn("break-all", highlightCls(leaf.text, searchNeedle, valueCls(value))),
45569
+ ...leaf.raw === null ? {} : { title: leaf.raw },
45570
+ children: leaf.text
45571
+ })
45572
+ ]
45573
+ });
45574
+ }
45440
45575
  const open = isPathOpen(path, depth);
45441
45576
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
45442
45577
  className: "flex items-center gap-1 px-3 py-0.5 hover:bg-surface-hover/30 cursor-pointer select-none",
@@ -45501,17 +45636,6 @@ function asBranch(value) {
45501
45636
  }
45502
45637
  return null;
45503
45638
  }
45504
- function formatValue(v) {
45505
- if (v === null) return "null";
45506
- if (v === void 0) return "undefined";
45507
- if (typeof v === "string") return v;
45508
- if (typeof v === "number" || typeof v === "boolean") return String(v);
45509
- try {
45510
- return JSON.stringify(v);
45511
- } catch {
45512
- return String(v);
45513
- }
45514
- }
45515
45639
  /** Tailwind colour for a leaf value by JS type. */
45516
45640
  function valueCls(v) {
45517
45641
  if (v === null || v === void 0) return "text-foreground-subtle/60";
@@ -45525,18 +45649,27 @@ function highlightCls(text, needle, base) {
45525
45649
  if (needle && text.toLowerCase().includes(needle)) return cn(base, "bg-violet-500/20 rounded px-0.5");
45526
45650
  return base;
45527
45651
  }
45528
- /** Deep search a slice — true when `needle` appears in any key or any
45529
- * stringified leaf value anywhere in the tree. */
45530
- function sliceMatchesSearch(value, needle) {
45531
- if (Array.isArray(value)) return value.some((v) => sliceMatchesSearch(v, needle));
45652
+ /**
45653
+ * Deep search a slice — true when `needle` appears in any key or any leaf value
45654
+ * anywhere in the tree.
45655
+ *
45656
+ * `key` is threaded down so a leaf is matched the way it is DISPLAYED: a timestamp
45657
+ * shown as a local date has to be findable by typing part of that date. Both forms
45658
+ * match, so the raw epoch still works too — the operator should not have to know
45659
+ * which one the panel chose to render.
45660
+ */
45661
+ function sliceMatchesSearch(value, needle, key = "") {
45662
+ if (Array.isArray(value)) return value.some((v, i) => sliceMatchesSearch(v, needle, String(i)));
45532
45663
  if (value && typeof value === "object") {
45533
45664
  for (const [k, v] of Object.entries(value)) {
45534
45665
  if (k.toLowerCase().includes(needle)) return true;
45535
- if (sliceMatchesSearch(v, needle)) return true;
45666
+ if (sliceMatchesSearch(v, needle, k)) return true;
45536
45667
  }
45537
45668
  return false;
45538
45669
  }
45539
- return formatValue(value).toLowerCase().includes(needle);
45670
+ const leaf = formatLeafValue(key, value);
45671
+ if (leaf.text.toLowerCase().includes(needle)) return true;
45672
+ return leaf.raw !== null && leaf.raw.toLowerCase().includes(needle);
45540
45673
  }
45541
45674
  /** Human "Ns ago" / "Nm ago" age label for the per-cap freshness
45542
45675
  * indicator. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",