tsgrid-ui 2.5.0 → 2.6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  All notable changes to **TsGrid UI** will be documented in this file.
4
4
 
5
+ ## v2.6.0 — 2026-05-13
6
+
7
+ ### Refactor
8
+
9
+ - **`date()` extraction — `DateDeps` deps-injection pattern**: Extracted the `TsUtils.date()` method body (22 LOC, the sole `this.lang('Yesterday')` coupling in `tsutils.ts`) into `_date()` in `src/tsutils-datetime.ts` via a new minimal `DateDeps` interface (`{ lang: (phrase: string) => string }`). Class method `date()` becomes a one-line delegator: `return _date(dateStr, this.settings, { lang: this.lang.bind(this) })`. Public signature `TsUtils.date(dateStr: unknown): string` is **byte-identical** to v2.5.0. `vi.spyOn(TsUtils, 'date')` continues to work (prototype delegator). Follows the v2.3 `MessageDeps` / `NotifyDeps` canonical pattern.
10
+ - `DateDeps` is an **internal** interface backing the deps-injection delegation pattern. It is NOT exported through `src/index.ts` barrel (INV-7 byte-identical). Consumers of `TsUtils.date()` are unaffected — the public method signature and runtime behavior are identical to v2.5.0 (same string outputs for all date inputs).
11
+ - **`TsTimeResult` dedup**: Promoted `interface TsTimeResult` in `src/tsutils-datetime.ts` from non-exported to `export interface TsTimeResult`. Deleted the local duplicate declaration in `src/tsutils.ts` (lines 102-107); replaced with `import type { TsTimeResult } from './tsutils-datetime.js'` (for internal use by `isTime()`) and `export type { TsTimeResult } from './tsutils-datetime.js'` (barrel re-export). Single source-of-truth now in `tsutils-datetime.ts`. Mirrors the `TsColorRgb` precedent (`tsutils.ts:104`). `dist/tsgrid-ui.d.ts` continues to declare `TsTimeResult` exactly once (INV-LOC-1 `grep -c` = 1, line 425). Existing `import type { TsTimeResult } from 'tsgrid-ui'` consumers continue to work unchanged — same d.ts reachability, different source file.
12
+
13
+ ### Tests
14
+
15
+ - Added 7-case safety-net suite for `_date()` in `test/unit/tsutils-datetime.test.ts` committed as Phase 4 RED before extraction (INV-TDD PASS):
16
+ - T-DATE-1 — empty string → `''`
17
+ - T-DATE-2 — `null` → `''`
18
+ - T-DATE-3 — invalid date string → `''`
19
+ - T-DATE-4 — today (frozen clock, `vi.useFakeTimers()`) → regex match on `<span title="...">H:MM am|pm</span>`
20
+ - T-DATE-5 — yesterday (frozen clock) → regex match on `<span title="May 12, 2026 ...">Yesterday</span>` (proves `deps.lang` is called)
21
+ - T-DATE-6 — Unix timestamp (older) → `<span title="...">` with month-day-year
22
+ - T-DATE-7 — older date string → `<span title="...">` with month-day-year
23
+ - `vi.useFakeTimers()` / `vi.useRealTimers()` wrap the entire `_date()` describe block (INV-DATE-TIMEFREEZE PASS). Total Vitest tests: **288/288 GREEN** (v2.5.0 baseline: 280/280).
24
+
25
+ ### Bundle
26
+
27
+ Delta vs v2.5.0 baseline:
28
+
29
+ | Artifact | v2.5.0 | v2.6.0 | Δ bytes | Δ % |
30
+ |----------|--------|--------|---------|-----|
31
+ | `dist/tsgrid-ui.js` (CJS) | 946,611 B | 946,686 B | +75 B | +0.0079% |
32
+ | `dist/tsgrid-ui.es6.js` (ESM) | 944,804 B | 944,878 B | +74 B | +0.0078% |
33
+ | `dist/tsgrid-ui.min.js` (CJS min) | 508,902 B | 508,954 B | +52 B | +0.0102% |
34
+ | `dist/tsgrid-ui.es6.min.js` (ESM min) | 507,768 B | 507,819 B | +51 B | +0.0100% |
35
+ | `dist/tsgrid-ui.d.ts` | 93,022 B | 94,446 B | +1,424 B | +1.5308% |
36
+
37
+ All within ±2% gate. PASSED. The JS bundles are essentially byte-stable (≤0.011%) — the mechanical extraction added nothing meaningful at runtime. The `d.ts` grew by +1,424 B because `TsTimeResult` is now emitted as a named export from `tsutils-datetime.ts` (its source-of-truth file) rather than transitively via `isTime()` from `tsutils.ts`; additionally `DateDeps` is declared in `tsutils-datetime.ts` and included in the rolled d.ts.
38
+
39
+ **Note on P6 dist commit**: `pnpm build` was run at P6-DEDUP to enable the INV-LOC-1 audit-trail gate (`grep -c` = 1 in committed dist). P8 re-emits dist with the `v2.6.0` version banner baked into the JS headers. This is intentional and mirrors the v2.5.0 release commit (`51c00836`) pattern.
40
+
41
+ ### BC
42
+
43
+ - `TsUtils.date(dateStr: unknown): string` — signature and runtime behavior **BYTE-IDENTICAL** to v2.5.0. All existing call sites work unchanged.
44
+ - `TsUtils.isTime(val: unknown, retTime?: boolean): boolean | TsTimeResult` — signature **BYTE-IDENTICAL** to v2.5.0.
45
+ - `TsTimeResult` — shape `{ hours: number; minutes: number; seconds: number }` unchanged. Continues to be reachable in `dist/tsgrid-ui.d.ts` at the same surface. Existing `import type { TsTimeResult } from 'tsgrid-ui'` consumers unaffected.
46
+ - `DateDeps` — NEW **internal** interface. Not exported through `src/index.ts` barrel (INV-7 byte-identical). Consumers do not depend on it; `_date()` is not a public export. Spec requirement A-4 (emit `DateDeps` in d.ts as a named export) is NOT honored because honoring it would require modifying `src/index.ts`, violating INV-7. The type IS present in the rolled d.ts (tsutils-datetime.ts is included by tsup), but is not consumer-reachable via a top-level import path. This is a documented spec divergence: INV-7 (backwards compat, blocking) takes precedence over A-4 (additive surface, non-blocking for existing consumers). Future revision: export `DateDeps` from `src/index.ts` if consumers need it.
47
+ - `src/index.ts` barrel — **byte-identical** to v2.5.0 (INV-7 PASS).
48
+ - SEMVER MINOR. BC verdict: NONE.
49
+
50
+ ### Internal
51
+
52
+ - v2.5 SUGG-5 (INV-8 over `src/tsutils-color.ts`) **CLOSED by verification**: ESLint `no-restricted-syntax` glob `src/tsutils-*.ts` already covers `tsutils-color.ts`. The two `arguments` occurrences at lines 110 and 145 of that file are inside explanatory comments (`// ... example: arguments[0]...`), not AST nodes — the rule does not fire on comment text. Confirmed by INV-LINT-INV8 canary: injecting `void arguments.length;` into `src/tsutils-datetime.ts` triggers `pnpm lint` EXIT 1 with the expected diagnostic; reverting restores EXIT 0. No code change required. No carry-forward to v2.7+ SUGG list.
53
+
54
+ ---
55
+
5
56
  ## v2.5.0 — 2026-05-13
6
57
 
7
58
  ### Refactor
@@ -1,4 +1,4 @@
1
- /* tsgrid-ui 1.0.x (nightly) (5/13/2026, 9:13:59 PM) (c) 2014 vitmalina@gmail.com, (c) 2026 DaverSoGT — MIT */
1
+ /* tsgrid-ui 1.0.x (nightly) (5/13/2026, 10:54:27 PM) (c) 2014 vitmalina@gmail.com, (c) 2026 DaverSoGT — MIT */
2
2
  /**
3
3
  * TODO:
4
4
  * - remove default styling, only keep tsg-* styles
@@ -9,7 +9,7 @@
9
9
  */
10
10
  @font-face {
11
11
  font-family: "tsgrid-font";
12
- src: url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAApYAAsAAAAAD0wAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAQQAAAFZdKW6ZY21hcAAAAYgAAACiAAACNBnCLmJnbHlmAAACLAAABd8AAAfo+edccWhlYWQAAAgMAAAAMAAAADYzdiY9aGhlYQAACDwAAAAYAAAAJA3eCBJobXR4AAAIVAAAABAAAABAeA8AAGxvY2EAAAhkAAAAIgAAACIO+gzSbWF4cAAACIgAAAAfAAAAIAEgAGBuYW1lAAAIqAAAATAAAAI6ubjYZ3Bvc3QAAAnYAAAAgAAAAKn1lm/4eJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGRvZJzAwMrAwCrCIsXAwHAJQjNdYPBkbAbSDKzMDFhBQJprCoMDgyODP+sdILedbRWDGZBmBMkBAGnVCIcAAAB4nL3R1w3DMAwE0JMlF1kui2SGAE7v3iNfGTJ7OUfhNkgQAo+AqAKBBFAC8LSiALgXHCyerLpc92hzPWCdz4Rcn5eFecrZ2U3mgmcDX6xQo0HkvYQOPQaM3K7wfbjCh7Kqm9imrh/GH7z43+hyfmtl/5/EerqRgrZis9mJdXkvNreDWGePUtNJGjpLpItwnrhKopvY7+7S00MGmmX8AHS1EV0AAHichVRrbBRVFL5nZmfWCtl2ujuzu2x3tjPbbm27bGd39tHN0pZiQSulkJSWNfJMWgokRIK0Bk1ogkbCw/CwGBuKGKsJaFApjRpbjMYfxUeEEH/wwwLRKAbjI8T46O5cPDO7gK/Eydx7vzmve893zxzCEHz4z7gdpIy4CIFQUFF5UXBJuhJLJoQ4qwgKP3ZfOrcrvWJF2rY7vWJLrsX2Idde15XJTWdWrszY0pncZtujGAbMWPYu7mHCEVLC2EuAH81fMH47y0yx33HZ/Itw8w1jsS1gmnGWbZBrJyXESfxkPkmau/OlwEvuEnDaHeAGZ00EuGSoAeV2FqprcEF1AKRkCpIhcEAEmkEG+GJem7hZ5OcxT9OPzx2VPQ54RLjf0cLMhwZ1TI2CE3rg2GG5tcz4tVSUEZQy95aJxuWyhQvEAA72Jjrv8rZJxg3mdRhscyyUDZ758bmAJ4DeLDQo+UnmWPdDpS45z6P3UozC/iG35iXZ8g8sJKzF47NcByI7mYO56ELwzhi8epXxXbvGVFy9ynXMfsQ1FcZtzvgt3AMkiB+SC0mTXEiCWoPsqZh5PFWCUwskY0gLTnxWEGgGzpaXdwlaOc3SLC5dggDjtFMQuso1AU7CeyizfVWO+tW0T4iiuBxGaL/pExVgBF4uyOhKOCMIhbvgf+JmimcPkgV4FkVU3ObQxSDyXBOqxCux82VuGdxSZaoZUskyUGtMTdz6ilkal2XFPMZW5q/tZxzGzcM//MBWpy/sOUAvHthzIZ3JmBi0A89cTGeM8qEd205r0ah2etsOeheyJ/evHepbyxy94/B35/xv//QoQEyjUM+/cl8TH6nELCKAJVVjdwPvjoouKQOxFLhTNXjiEAQT4Obfrdqwvio/Pj1qdJSzPtfPUikzMTo9nq/yenPxE8xbJ7hXERYthHk3XL7bBhvWz868xLx5gtgIufWJXeXnklLiJTGyFPeNJ3EryQ8uvh7UUAKUBNZBQhF0Vv03jxaLbIHMMktUaanLLFP2eDgQC4StCcJ0W28vhHvPjx09eGVBS8uCKwePwpLhQwV8aHjs6Z0DE1Fdj04M7IR77mKbVowQDhhrzAB0m62juQm96XvDh2aaWlqaZg4NwxKM2tQ82/Mvf/q7hc1ckd9h/jXiJlWYa6NZKfEIqA4Q/945QtUxGVwOUCMQbwbA1MWCrgn0Eki5kfs+mo2t2rgqRrPOigqn7TzOxl5v2OsN6/Vebz2zPPf8VIWLOeLynTO20ks9l2G4h5tJrU3hK4uzF0VZFjlNlDdWRBobfL6GxkjF7BbbJyOSLEsjucvw5Jp34Kk1xZp4hTuMp3fieVNQAyxy7UxBENhqSICdzZ5ilncb45cCyxbtPgWzBuUNtgNmKd/PLO5mOruNMzS8aFkANvWbwvyEgQvlThb/3xFugIQwssvO2/mgIhRvXVDUUBOWgq4IhXoQBQUn/gWen+tRaaOm9gQ1mlY1TYVpLdijanBe1Zj3nU5xjoc2WvLzmroK5dNBTQvStKbe6bN2bohU44dq7mkPloCChdMMMZlxOZigGPtLadkGXz6475E10k1ohwfpT44H0+1Tn5870tl55Fz/M08MvB3T9djbA09wu5a2D+0+TvfC48v2NbabatPqACoHd8LcnYNoSAr/2K0c/wv3JaklzYRU409mT6aSibi1WaG+XVYB67FUMoUNHZt0MxPCdhEyu0XSbBaS2Svw5PwH9Liv0duqVoXrR/s2fbO5d7Q+jLB38/b+dauXgscDkXlti3WHlN/Ul+2OxuPR7uynCLAuu7PXIOLxMB2r1/Vv39RXcMQYFqxSW72NPjpaITn0xW2wVS/63Q1gXV2x9w1hPfuITlJkOelCVhPxEF4h7zfbYKFvYN0qCS6hi/DfKuX/dOxYrf97fy29Dl4LsB25Y5PM4Tr/DX9tfoLtqEVQB156nX47Rev/aZufoNcnOcXtr631u6fc/ro6XPChtACLiruSSY8p8UziQ8ifQVEESwB4nGNgZGBgAGLPd8sK4vltvjJws94BijA803pXgaD/n+JgZFsF5HIwMIFEAWU9DFN4nGNgZGBgvcMABByMUBJMIwEBAB1IAQZ4nGNgYGDgYCQfAwAREQCIAAAAAAAmADwApADAAQIBaAFoAaICGAJyAqAC2gMaA4AD9AAAeJxjYGRgYBBgCGFgYwABJiDmAkIGhv9gPgMAErgBgQB4nHWPzUrDQBSFT/ontiCi4E6YlQjSpK27Lly2OxdddJ+2M2lKmgmTaaHgU/gEPoWP4Mqn8ClcehrvIkidwPDd7547mQFwiQ8EOK4AvWo/rgbOWP1yk3Ql3CLfCrfJ98Id8qNwFw94Eu7hGilPCFrnNDd4EW7gAq/CTfo34Rb5XbhN/hTukL+Eu5jjW7iHu+DZl4lLV31jcz/TyS6LXc3UcK5dmdpcDcNBzU51rl3s9UotDqrcJyPvjTLObtWEXZ1lVhXObvTSh2vvi3EUGfHh0m7hUSKB4ytX6MPAIqebQdPukCFm73TmtJ1z0rGTVrXCECEG/2SnzOZVPmal2VNY4MC9xJ75UfUXw9owY7ElTWRW824ZWaGoehuaJX2IdTVVYIyIn/mTD5niST+me2hWeJxtx1EOgjAQRdE+aAuKCizERdUyCrF0mk5JZPdq/PX83FxVqZ9e/TegQg0NA4sGLQ44osMJZ1zQY8Co6hu/jJ/JP63nwFmaT7Y1ivGZRfSUORlaU9k72un63URZL/HOJjkpZBNFvwSdwiY2U2A3WSGX/dwKlbLEhyj1BqYnJNk=") format("woff");
12
+ src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff");
13
13
  font-weight: normal;
14
14
  font-style: normal;
15
15
  }
@@ -393,6 +393,41 @@ interface TsLockOptions {
393
393
  onClick?: () => void;
394
394
  }
395
395
 
396
+ /**
397
+ * TsUtils date-time sub-module — Phase 5b of v2.5 SDD.
398
+ * DAG position: leaf module (no tsbase/tsutils imports).
399
+ *
400
+ * Imports:
401
+ * ./tsutils-type-guards.js — isInt as _isInt (needed by isDate, isTime, formatDate, formatTime)
402
+ * ./tsutils.js — type-only import type { TsUISettings } (TS erases at emit)
403
+ * Precedent: tsutils-type-guards.ts:9, tsutils-message.ts:26
404
+ *
405
+ * INV-4: MUST NOT import from tsbase.ts or tsutils.ts at runtime.
406
+ * INV-8: No arguments.length usage.
407
+ * INV-9: No this.X in exported function bodies.
408
+ *
409
+ * 4-space indent convention.
410
+ *
411
+ * OQ-2 (TsTimeResult): local non-exported interface `TsTimeResult` defined inline
412
+ * here (structurally identical to tsutils.ts copy). Avoids back-import of a
413
+ * non-exported type; the class delegator in tsutils.ts casts via `as boolean | TsTimeResult`.
414
+ *
415
+ * R-DT-3 (settings reference): `settings` is passed as a reference to `this.settings`
416
+ * from delegators — never cloned. TsLocale mutations to fullmonths/shortmonths/dateFormat
417
+ * etc. flow through without restart.
418
+ *
419
+ * R-DT-2 / R-DT-8 (intra-cluster calls): _isDateTime calls _isDate + _isTime directly
420
+ * as module-level function refs. _formatDateTime calls _formatDate + _formatTime directly.
421
+ * _formatTime calls _isTime directly. Zero this.X inside any extracted body.
422
+ */
423
+
424
+ /** Return value from _isTime() / TsUtils.isTime() when retTime === true — single canonical declaration (v2.6 dedup) */
425
+ interface TsTimeResult {
426
+ hours: number;
427
+ minutes: number;
428
+ seconds: number;
429
+ }
430
+
396
431
  /**
397
432
  * Part of TsUi 2.0 library
398
433
  * - Dependencies: mQuery, TsUtils, TsBase, TsLocale
@@ -461,12 +496,6 @@ interface TsFormatterExtra {
461
496
  }
462
497
  /** Signature of a grid-cell formatter function */
463
498
  type TsFormatter = (record: TsFormatterExtra, extra?: TsFormatterExtra) => string;
464
- /** Return value from TsUtils.isTime() when retTime === true */
465
- interface TsTimeResult {
466
- hours: number;
467
- minutes: number;
468
- seconds: number;
469
- }
470
499
 
471
500
  /** A normalized menu item */
472
501
  interface TsMenuItem {
@@ -2987,6 +2987,25 @@ function _formatDateTime(dateStr, format, settings) {
2987
2987
  if (fmt[1] === "h24") fmt[1] = "h24:m";
2988
2988
  return _formatDate(dateStr, fmt[0], settings) + " " + _formatTime(dateStr, fmt[1], settings);
2989
2989
  }
2990
+ function _date(dateStr, settings, deps) {
2991
+ if (dateStr === "" || dateStr == null || typeof dateStr === "object" && !dateStr.getMonth) return "";
2992
+ let d1 = new Date(dateStr);
2993
+ if (isInt(dateStr)) d1 = new Date(Number(dateStr));
2994
+ if (String(d1) === "Invalid Date") return "";
2995
+ const months = settings.shortmonths;
2996
+ const d2 = /* @__PURE__ */ new Date();
2997
+ const d3 = /* @__PURE__ */ new Date();
2998
+ d3.setTime(d3.getTime() - 864e5);
2999
+ const dd1 = months[d1.getMonth()] + " " + d1.getDate() + ", " + d1.getFullYear();
3000
+ const dd2 = months[d2.getMonth()] + " " + d2.getDate() + ", " + d2.getFullYear();
3001
+ const dd3 = months[d3.getMonth()] + " " + d3.getDate() + ", " + d3.getFullYear();
3002
+ const time = d1.getHours() - (d1.getHours() > 12 ? 12 : 0) + ":" + (d1.getMinutes() < 10 ? "0" : "") + d1.getMinutes() + " " + (d1.getHours() >= 12 ? "pm" : "am");
3003
+ const time2 = d1.getHours() - (d1.getHours() > 12 ? 12 : 0) + ":" + (d1.getMinutes() < 10 ? "0" : "") + d1.getMinutes() + ":" + (d1.getSeconds() < 10 ? "0" : "") + d1.getSeconds() + " " + (d1.getHours() >= 12 ? "pm" : "am");
3004
+ let dsp = dd1;
3005
+ if (dd1 === dd2) dsp = time;
3006
+ if (dd1 === dd3) dsp = deps.lang("Yesterday");
3007
+ return '<span title="' + dd1 + " " + time2 + '">' + dsp + "</span>";
3008
+ }
2990
3009
 
2991
3010
  // src/tsutils.ts
2992
3011
  var query7 = query;
@@ -3203,23 +3222,7 @@ var Utils = class {
3203
3222
  return _interval(value);
3204
3223
  }
3205
3224
  date(dateStr) {
3206
- if (dateStr === "" || dateStr == null || typeof dateStr === "object" && !dateStr.getMonth) return "";
3207
- let d1 = new Date(dateStr);
3208
- if (this.isInt(dateStr)) d1 = new Date(Number(dateStr));
3209
- if (String(d1) === "Invalid Date") return "";
3210
- const months = this.settings.shortmonths;
3211
- const d2 = /* @__PURE__ */ new Date();
3212
- const d3 = /* @__PURE__ */ new Date();
3213
- d3.setTime(d3.getTime() - 864e5);
3214
- const dd1 = months[d1.getMonth()] + " " + d1.getDate() + ", " + d1.getFullYear();
3215
- const dd2 = months[d2.getMonth()] + " " + d2.getDate() + ", " + d2.getFullYear();
3216
- const dd3 = months[d3.getMonth()] + " " + d3.getDate() + ", " + d3.getFullYear();
3217
- const time = d1.getHours() - (d1.getHours() > 12 ? 12 : 0) + ":" + (d1.getMinutes() < 10 ? "0" : "") + d1.getMinutes() + " " + (d1.getHours() >= 12 ? "pm" : "am");
3218
- const time2 = d1.getHours() - (d1.getHours() > 12 ? 12 : 0) + ":" + (d1.getMinutes() < 10 ? "0" : "") + d1.getMinutes() + ":" + (d1.getSeconds() < 10 ? "0" : "") + d1.getSeconds() + " " + (d1.getHours() >= 12 ? "pm" : "am");
3219
- let dsp = dd1;
3220
- if (dd1 === dd2) dsp = time;
3221
- if (dd1 === dd3) dsp = this.lang("Yesterday");
3222
- return '<span title="' + dd1 + " " + time2 + '">' + dsp + "</span>";
3225
+ return _date(dateStr, this.settings, { lang: this.lang.bind(this) });
3223
3226
  }
3224
3227
  formatSize(sizeStr) {
3225
3228
  if (!this.isFloat(sizeStr) || sizeStr === "") return "";