@trackunit/react-components 2.6.5 → 2.7.1

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.
Files changed (31) hide show
  1. package/index.cjs.js +32 -343
  2. package/index.esm.js +23 -336
  3. package/migrations/utils/jsx-utils.js +1 -1
  4. package/package.json +6 -7
  5. package/src/hooks/localStorage/useLocalStorage.d.ts +1 -1
  6. package/src/hooks/localStorage/useLocalStorageReducer.d.ts +1 -1
  7. package/src/hooks/localStorage/useSessionStorage.d.ts +1 -1
  8. package/src/hooks/localStorage/useSessionStorageReducer.d.ts +1 -1
  9. package/src/hooks/localStorage/useStorageSyncEffect.d.ts +1 -1
  10. package/src/hooks/localStorage/useWebStorage.d.ts +4 -2
  11. package/src/hooks/localStorage/useWebStorageReducer.d.ts +1 -1
  12. package/src/index.d.ts +1 -2
  13. package/migrations/entry.js.map +0 -1
  14. package/migrations/utils/jsx-utils.js.map +0 -1
  15. package/migrations/v2-0-0/breadcrumb-remove-deprecated-props.js.map +0 -1
  16. package/migrations/v2-0-0/cardheader-onclickclose-to-actions.js.map +0 -1
  17. package/migrations/v2-0-0/iconbutton-add-required-name.js.map +0 -1
  18. package/migrations/v2-0-0/kpi-tooltiplabel-to-wrapper.js.map +0 -1
  19. package/migrations/v2-0-0/notice-tooltiplabel-to-wrapper.js.map +0 -1
  20. package/migrations/v2-0-0/pagination-add-required-titles.js.map +0 -1
  21. package/migrations/v2-0-0/tag-add-removetaglabel.js.map +0 -1
  22. package/migrations/v2-0-0/togglegroup-remove-item-style.js.map +0 -1
  23. package/migrations/v3-0-0/menulist-rename-to-menucontent.js.map +0 -1
  24. package/src/hooks/localStorage/readFromStorage.d.ts +0 -39
  25. package/src/hooks/localStorage/runMigrations.d.ts +0 -13
  26. package/src/hooks/localStorage/salvageState.d.ts +0 -25
  27. package/src/hooks/localStorage/storageSerializer.d.ts +0 -4
  28. package/src/hooks/localStorage/storageVersionEnvelope.d.ts +0 -30
  29. package/src/hooks/localStorage/types.d.ts +0 -31
  30. package/src/hooks/localStorage/validateState.d.ts +0 -22
  31. package/src/hooks/localStorage/writeToStorage.d.ts +0 -11
package/index.esm.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
3
  import { twMerge } from 'tailwind-merge';
4
- import { objectKeys, uuidv4, parseTailwindArbitraryValue, objectEntries, nonNullable, objectValues, filterByMultiple } from '@trackunit/shared-utils';
4
+ import { objectKeys, uuidv4, parseTailwindArbitraryValue, objectEntries, nonNullable, objectValues, validateState, writeToStorage, readFromStorage, storageSerializer, filterByMultiple } from '@trackunit/shared-utils';
5
+ export { storageSerializer } from '@trackunit/shared-utils';
5
6
  import { useRef, useMemo, useEffect, useSyncExternalStore, useState, useLayoutEffect, createContext, useContext, isValidElement, cloneElement, createElement, useCallback, Fragment as Fragment$1, memo, forwardRef, useId, useReducer, Children } from 'react';
6
7
  import { rentalStatusPalette, sitesPalette, utilizationPalette, activityPalette, criticalityPalette, generalPalette, intentPalette, themeScreenSizeAsNumber, themeContainerSize, color } from '@trackunit/ui-design-tokens';
7
8
  import { iconNames } from '@trackunit/ui-icons';
@@ -20,8 +21,6 @@ import { isTypeableElement } from '@floating-ui/react/utils';
20
21
  import { HelmetProvider, Helmet } from 'react-helmet-async';
21
22
  import { Trigger, Content as Content$1, List as List$1, Root } from '@radix-ui/react-tabs';
22
23
  import { gzipSync, gunzipSync } from 'fflate';
23
- import superjson from 'superjson';
24
- import { z } from 'zod';
25
24
  import { dequal } from 'dequal';
26
25
 
27
26
  var defaultTranslations = {
@@ -2361,7 +2360,7 @@ const Tag = ({ className, "data-testid": dataTestId, children, size = "medium",
2361
2360
  color,
2362
2361
  layout,
2363
2362
  border: color === "white" ? "default" : "none",
2364
- }), "data-testid": dataTestId, onMouseEnter: onMouseEnter, ref: ref, style: style, children: [icon !== null && icon !== undefined && size === "medium" ? (jsx("div", { className: cvaTagIconContainer(), "data-slot": "icon", children: icon })) : null, jsx("span", { className: cvaTagText({ truncate: shouldTruncate }), ref: textRef, children: children }), Boolean(onClickClose) && isSupportedDismissColor && size === "medium" && !disabled ? (jsx("div", { className: cvaTagIconContainer(), "data-slot": "dismiss", onClick: onClickClose, children: jsx(Tooltip, { disabled: disabled, label: removeTagLabel, placement: "bottom", children: jsx(Icon, { className: cvaTagIcon(), "data-testid": dataTestId + "Icon", name: "XCircle", size: "small", style: { WebkitTransition: "-webkit-transform 0.150s" }, type: "solid" }) }) })) : null] }));
2363
+ }), "data-testid": dataTestId, onMouseEnter: onMouseEnter, ref: ref, style: style, children: [icon !== null && icon !== undefined && size === "medium" ? (jsx("div", { className: cvaTagIconContainer(), "data-slot": "icon", children: icon })) : null, jsx("span", { className: cvaTagText({ truncate: shouldTruncate }), ref: textRef, children: children }), Boolean(onClickClose) && isSupportedDismissColor && size === "medium" && !disabled ? (jsx("div", { className: cvaTagIconContainer(), "data-slot": "dismiss", onClick: onClickClose, children: jsx(Tooltip, { disabled: disabled, label: removeTagLabel, placement: "bottom", children: jsx(Icon, { className: cvaTagIcon(), "data-testid": `${dataTestId}Icon`, name: "XCircle", size: "small", style: { WebkitTransition: "-webkit-transform 0.150s" }, type: "solid" }) }) })) : null] }));
2365
2364
  };
2366
2365
 
2367
2366
  // Mirrors the slug logic in @trackunit/storybook-docs-components SectionName so anchors line up
@@ -4766,7 +4765,7 @@ function stylesToCss(styles, parentSelector, indent = "") {
4766
4765
  }
4767
4766
  else if (key.startsWith("@")) {
4768
4767
  // At-rule (like @container): wrap in the at-rule
4769
- const nestedCss = stylesToCss(value, parentSelector, indent + " ");
4768
+ const nestedCss = stylesToCss(value, parentSelector, `${indent} `);
4770
4769
  lines.push(`${indent}${key} {\n${nestedCss}\n${indent}}`);
4771
4770
  }
4772
4771
  }
@@ -11863,334 +11862,6 @@ const useCustomEncoding = () => {
11863
11862
  return useMemo(() => ({ encode, decode }), [encode, decode]);
11864
11863
  };
11865
11864
 
11866
- /**
11867
- * Internal envelope used to tag superjson-serialized data in web storage.
11868
- *
11869
- * `__serializer` is a **reserved internal key** — consumer state objects must
11870
- * not include it as a top-level key. The double-underscore prefix is a
11871
- * deliberate signal that this is a private implementation detail.
11872
- *
11873
- * A runtime warning is emitted (via `writeToStorage`) if reserved keys are
11874
- * detected in the value being written.
11875
- */
11876
- const taggedSuperjsonEnvelopeSchema = z.object({
11877
- __serializer: z.literal("superjson"),
11878
- json: z.custom(),
11879
- meta: z.custom().optional(),
11880
- });
11881
- const storageSerializer = {
11882
- serialize: (value) => {
11883
- const serialized = superjson.serialize(value);
11884
- return JSON.stringify({ __serializer: "superjson", ...serialized });
11885
- },
11886
- deserialize: (value) => {
11887
- const parsed = JSON.parse(value);
11888
- const result = taggedSuperjsonEnvelopeSchema.safeParse(parsed);
11889
- if (result.success) {
11890
- return superjson.deserialize({ json: result.data.json, meta: result.data.meta });
11891
- }
11892
- return parsed;
11893
- },
11894
- };
11895
-
11896
- /**
11897
- * Runs a sequential migration pipeline on the provided data, applying
11898
- * each migration whose version is in the range (fromVersion, toVersion].
11899
- *
11900
- * Throws if the filtered migrations have non-contiguous versions (gaps).
11901
- */
11902
- const runMigrations = ({ data, fromVersion, toVersion, migrations, }) => {
11903
- if (toVersion <= fromVersion) {
11904
- return data;
11905
- }
11906
- const applicable = migrations
11907
- .toSorted((a, b) => a.version - b.version)
11908
- .filter(m => m.version > fromVersion && m.version <= toVersion);
11909
- if (applicable.length === 0) {
11910
- return data;
11911
- }
11912
- for (let i = 1; i < applicable.length; i++) {
11913
- const prev = applicable[i - 1];
11914
- const curr = applicable[i];
11915
- if (prev && curr && curr.version !== prev.version + 1) {
11916
- throw new Error(`Migration gap detected: found version ${prev.version} and ${curr.version} but no migration for version ${prev.version + 1}.`);
11917
- }
11918
- }
11919
- return applicable.reduce((acc, migration) => migration.migrate(acc), data);
11920
- };
11921
-
11922
- /**
11923
- * Recursively builds a salvaging schema from a ZodObject by injecting `.catch(defaultValue[key])`
11924
- * for each field. This means invalid field values fall back to defaultState values rather than
11925
- * failing the entire parse.
11926
- *
11927
- * Unwraps ZodOptional, ZodNullable, and ZodDefault to reach the inner schema.
11928
- * ZodCatch is intentionally NOT unwrapped — consumer-defined catches take precedence.
11929
- * Non-ZodObject schemas (unions, discriminated unions, refinements, etc.) are returned unchanged
11930
- * so they behave as atomic units.
11931
- */
11932
- function buildSalvagingSchema(schema, defaultValue) {
11933
- if (schema instanceof z.ZodOptional) {
11934
- return buildSalvagingSchema(schema.unwrap(), defaultValue).optional();
11935
- }
11936
- if (schema instanceof z.ZodNullable) {
11937
- return buildSalvagingSchema(schema.unwrap(), defaultValue).nullable();
11938
- }
11939
- if (schema instanceof z.ZodDefault) {
11940
- return buildSalvagingSchema(schema._def.innerType, defaultValue);
11941
- }
11942
- if (!(schema instanceof z.ZodObject)) {
11943
- return schema;
11944
- }
11945
- const defaultRecordResult = z.record(z.string(), z.unknown()).safeParse(defaultValue);
11946
- if (!defaultRecordResult.success) {
11947
- return schema;
11948
- }
11949
- const defaultRecord = defaultRecordResult.data;
11950
- const salvagingShape = {};
11951
- for (const key of Object.keys(schema.shape)) {
11952
- const fieldSchema = schema.shape[key];
11953
- if (fieldSchema === undefined)
11954
- continue;
11955
- const fieldDefault = defaultRecord[key];
11956
- salvagingShape[key] = buildSalvagingSchema(fieldSchema, fieldDefault).catch(fieldDefault);
11957
- }
11958
- return z.object(salvagingShape);
11959
- }
11960
- /**
11961
- * Attempts to salvage partial state from raw data when full schema validation has failed.
11962
- *
11963
- * For ZodObject schemas, rebuilds the schema with per-field `.catch(defaultState[key])` fallbacks
11964
- * and reparses. Valid field values from the stored data are kept; invalid fields fall back to their
11965
- * corresponding values in `defaultState`. Works recursively for nested ZodObject fields.
11966
- *
11967
- * Non-object schemas (discriminated unions, unions with `.refine()`, etc.) cannot be partially
11968
- * salvaged and return `null`.
11969
- *
11970
- * Consumer-defined `.catch()` wrappers take precedence over the injected salvage catches, since
11971
- * Zod processes catches inside-out.
11972
- *
11973
- * Note: when every field is invalid, the salvaged result will be deeply equal to `defaultState`.
11974
- * Callers should compare the result against `defaultState` to distinguish a genuine partial
11975
- * salvage from a total loss where no stored data survived.
11976
- *
11977
- * @template TState - The type of the stored state.
11978
- * @param schema - Zod schema for validation.
11979
- * @param rawData - The raw deserialized value that failed validation.
11980
- * @param defaultState - The authoritative fallback value; used as per-field catch defaults.
11981
- * @returns {TState | null} The salvaged state, or `null` if salvaging is not possible or also fails.
11982
- */
11983
- const salvageState = (schema, rawData, defaultState) => {
11984
- try {
11985
- const salvagingSchema = buildSalvagingSchema(schema, defaultState);
11986
- const intermediate = salvagingSchema.safeParse(rawData);
11987
- if (!intermediate.success) {
11988
- return null;
11989
- }
11990
- // Re-validate through the original schema to ensure correctness and get the proper TState type.
11991
- // By construction this should always succeed: each field value is either the valid stored value
11992
- // or defaultState[key], both of which satisfy the original schema.
11993
- const result = schema.safeParse(intermediate.data);
11994
- return result.success ? result.data : null;
11995
- }
11996
- catch {
11997
- return null;
11998
- }
11999
- };
12000
-
12001
- /**
12002
- * Internal envelope that pairs stored data with a schema version number.
12003
- *
12004
- * `__version` and `__data` are **reserved internal keys** — consumer state
12005
- * objects must not include them as top-level keys. The double-underscore
12006
- * prefix is a deliberate signal that these are private implementation details.
12007
- *
12008
- * A runtime warning is emitted (via `writeToStorage`) if reserved keys are
12009
- * detected in the value being written.
12010
- */
12011
- const storageVersionEnvelopeSchema = z
12012
- .object({
12013
- __version: z.number(),
12014
- __data: z.unknown(),
12015
- })
12016
- .refine((x) => "__data" in x, {
12017
- message: "__data is required",
12018
- });
12019
- /** Wraps data and version into a versioned storage envelope. */
12020
- const createStorageVersionEnvelope = (data, version) => ({
12021
- __version: version,
12022
- __data: data,
12023
- });
12024
-
12025
- /**
12026
- * Validates raw data against the schema, attempting partial salvage on failure.
12027
- *
12028
- * On success: returns the parsed data.
12029
- * On failure with a salvageable ZodObject schema where at least one stored field
12030
- * survived: returns the salvaged state and calls `onValidationSalvaged`.
12031
- * On failure where salvage produces a result identical to `defaultState` (i.e. every
12032
- * field was invalid and caught to its default): treats this as a total failure and
12033
- * calls `onValidationFailed` instead of `onValidationSalvaged`.
12034
- * On failure with no salvage possible: returns `defaultState` and calls `onValidationFailed`.
12035
- */
12036
- const validateOrSalvage = ({ rawValue, schema, defaultState, key, onValidationFailed, onValidationSalvaged, }) => {
12037
- const parseResult = schema.safeParse(rawValue);
12038
- if (parseResult.success) {
12039
- return parseResult.data;
12040
- }
12041
- const salvaged = salvageState(schema, rawValue, defaultState);
12042
- if (salvaged !== null && !isEqual(salvaged, defaultState)) {
12043
- // eslint-disable-next-line no-console
12044
- console.warn(`Partially invalid data in storage key "${key}" — salvaged valid fields, reset invalid fields to defaults.`, parseResult.error);
12045
- onValidationSalvaged?.(parseResult.error, salvaged);
12046
- return salvaged;
12047
- }
12048
- // eslint-disable-next-line no-console
12049
- console.error(`Failed to parse and validate the state from storage key "${key}". Returning default state.`, parseResult.error);
12050
- onValidationFailed?.(parseResult.error);
12051
- return defaultState;
12052
- };
12053
- /**
12054
- * Reads and deserializes a value from web storage, validating it against the provided schema.
12055
- * Falls back to defaultState if the key is missing or data is corrupt.
12056
- *
12057
- * On partial validation failure for ZodObject schemas, salvages valid fields rather than
12058
- * resetting the entire state to `defaultState`. See `salvageState` for salvage mechanics.
12059
- * If the salvaged result is deeply equal to `defaultState` (i.e. every field was invalid),
12060
- * the salvage is considered a total loss and `onValidationFailed` is called instead of
12061
- * `onValidationSalvaged`.
12062
- *
12063
- * Deserialization failures (unparseable/corrupt raw data) are routed through
12064
- * `onValidationFailed` so consumers are always notified when stored data is unusable.
12065
- *
12066
- * When `migration` is provided, detects versioned envelopes, runs the
12067
- * migration pipeline, and validates the migrated result. Non-versioned consumers are
12068
- * unaffected — the migration path is fully opt-in.
12069
- *
12070
- * @template TState - The type of the stored state.
12071
- * @param params - Storage, key, default state, Zod schema, optional migration config, and optional callbacks.
12072
- * @param params.storage - The web Storage instance.
12073
- * @param params.key - The storage key.
12074
- * @param params.defaultState - Fallback value when no stored data exists or data is corrupt.
12075
- * @param params.schema - Zod schema to validate the deserialized data.
12076
- * @param params.migration - Optional migration configuration (version, steps, fromKey).
12077
- * @param params.onValidationFailed - Called when validation fails and no salvage is possible, or when
12078
- * deserialization of the raw storage value fails entirely.
12079
- * @param params.onValidationSalvaged - Called when validation fails but at least one stored field
12080
- * was recovered (salvaged result differs from defaultState).
12081
- * @returns {TState} The validated or salvaged state, or defaultState.
12082
- */
12083
- const readFromStorage = ({ storage, key, defaultState, schema, migration, onValidationFailed, onValidationSalvaged, }) => {
12084
- const version = migration?.version;
12085
- const steps = migration?.steps;
12086
- const fromKey = migration?.fromKey;
12087
- let raw = storage.getItem(key);
12088
- if (raw === null && fromKey !== undefined) {
12089
- raw = storage.getItem(fromKey);
12090
- }
12091
- if (raw === null) {
12092
- return defaultState;
12093
- }
12094
- try {
12095
- const deserialized = storageSerializer.deserialize(raw);
12096
- if (version === undefined) {
12097
- return validateOrSalvage({
12098
- rawValue: deserialized,
12099
- schema,
12100
- defaultState,
12101
- key,
12102
- onValidationFailed,
12103
- onValidationSalvaged,
12104
- });
12105
- }
12106
- let storedVersion;
12107
- let data;
12108
- const envelopeResult = storageVersionEnvelopeSchema.safeParse(deserialized);
12109
- if (envelopeResult.success) {
12110
- storedVersion = envelopeResult.data.__version;
12111
- data = envelopeResult.data.__data;
12112
- }
12113
- else {
12114
- storedVersion = 0;
12115
- data = deserialized;
12116
- }
12117
- if (steps !== undefined && storedVersion < version) {
12118
- try {
12119
- data = runMigrations({ data, fromVersion: storedVersion, toVersion: version, migrations: steps });
12120
- }
12121
- catch (migrationError) {
12122
- // eslint-disable-next-line no-console
12123
- console.error(`Migration failed for storage key "${key}" (v${storedVersion} → v${version}). Returning default state.`, migrationError);
12124
- return defaultState;
12125
- }
12126
- }
12127
- return validateOrSalvage({ rawValue: data, schema, defaultState, key, onValidationFailed, onValidationSalvaged });
12128
- }
12129
- catch (deserializationError) {
12130
- // eslint-disable-next-line no-console
12131
- console.error(`Failed to deserialize storage key "${key}". Returning default state.`, deserializationError);
12132
- onValidationFailed?.(deserializationError);
12133
- return defaultState;
12134
- }
12135
- };
12136
-
12137
- /**
12138
- * Validates the state using a Zod schema. Returns the parsed state on success,
12139
- * or defaultState on failure (with error logging and optional callbacks).
12140
- *
12141
- * @template TState - The type of the state.
12142
- * @param params - The state, schema, defaultState, and optional callbacks.
12143
- * @param params.state - The raw state to validate.
12144
- * @param params.schema - The Zod schema for validation.
12145
- * @param params.defaultState - The fallback value on failure.
12146
- * @param params.onValidationFailed - Optional error callback.
12147
- * @param params.onValidationSuccessful - Optional success callback.
12148
- * @returns {TState} The validated state or defaultState.
12149
- */
12150
- const validateState = ({ state, schema, onValidationFailed, onValidationSuccessful, defaultState, }) => {
12151
- const result = schema.safeParse(state);
12152
- if (result.success) {
12153
- onValidationSuccessful?.(result.data);
12154
- return result.data;
12155
- }
12156
- // eslint-disable-next-line no-console
12157
- console.error("Failed to parse and validate the state from storage.", result.error);
12158
- onValidationFailed?.(result.error);
12159
- return defaultState;
12160
- };
12161
-
12162
- /**
12163
- * Keys reserved for internal storage envelope metadata.
12164
- * Consumer state objects must not use these as top-level keys.
12165
- */
12166
- const RESERVED_STORAGE_KEYS = ["__data", "__version", "__serializer"];
12167
- const warnIfReservedKeys = (value) => {
12168
- if (value === null || typeof value !== "object" || Array.isArray(value))
12169
- return;
12170
- for (const key of RESERVED_STORAGE_KEYS) {
12171
- if (key in value) {
12172
- // eslint-disable-next-line no-console
12173
- console.warn(`[useWebStorage] "${key}" is a reserved internal storage key and must not be used in state objects. ` +
12174
- `It will conflict with the storage serialization envelope.`);
12175
- }
12176
- }
12177
- };
12178
- /**
12179
- * Serializes and writes a value to web storage.
12180
- * When a version is provided, wraps the data in a versioned envelope
12181
- * before serializing so the migration pipeline can detect it on read.
12182
- *
12183
- * @param storage - The Storage instance (localStorage / sessionStorage).
12184
- * @param key - The storage key.
12185
- * @param value - The value to serialize and store.
12186
- * @param version - Optional schema version to stamp onto the stored payload.
12187
- */
12188
- const writeToStorage = (storage, key, value, version) => {
12189
- warnIfReservedKeys(value);
12190
- const payload = version === undefined ? value : createStorageVersionEnvelope(value, version);
12191
- storage.setItem(key, storageSerializer.serialize(payload));
12192
- };
12193
-
12194
11865
  /**
12195
11866
  * Effect that synchronizes React state to web storage on every state change,
12196
11867
  * validating through the schema before writing.
@@ -12246,20 +11917,32 @@ const useStorageSyncEffect = ({ storage, key, state, defaultState, schema, versi
12246
11917
  * @param options.defaultState - Fallback value when no stored data exists.
12247
11918
  * @param options.schema - Zod schema for validation.
12248
11919
  * @param options.migration - Optional migration configuration (version, steps, fromKey).
11920
+ * @param options.legacyRawString - Narrow, opt-in recovery for keys known to hold a plain-text
11921
+ * value that was never JSON-encoded. See `WebStorageOptions.legacyRawString`.
12249
11922
  * @param options.onValidationFailed - Called when validation fails and no salvage is possible.
12250
11923
  * @param options.onValidationSuccessful - Called when validation succeeds during the write-sync.
12251
11924
  * @param options.onValidationSalvaged - Called when validation fails but partial state was recovered.
12252
11925
  * @returns {Array} A tuple of [state, setState, reset].
12253
11926
  */
12254
- const useWebStorage = (storage, { key, defaultState, schema, migration, onValidationFailed, onValidationSuccessful, onValidationSalvaged, }) => {
11927
+ const useWebStorage = (storage, { key, defaultState, schema, migration, legacyRawString, onValidationFailed, onValidationSuccessful, onValidationSalvaged, }) => {
12255
11928
  if (!key) {
12256
11929
  throw new Error("useWebStorage: key must be a non-empty string");
12257
11930
  }
12258
- const [state, setState] = useState(() => readFromStorage({ storage, key, defaultState, schema, migration, onValidationFailed, onValidationSalvaged }));
11931
+ const [state, setState] = useState(() => readFromStorage({
11932
+ storage,
11933
+ key,
11934
+ defaultState,
11935
+ schema,
11936
+ migration,
11937
+ legacyRawString,
11938
+ onValidationFailed,
11939
+ onValidationSalvaged,
11940
+ }));
12259
11941
  const prevKeyRef = useRef(key);
12260
11942
  const defaultStateRef = useRef(defaultState);
12261
11943
  const schemaRef = useRef(schema);
12262
11944
  const migrationRef = useRef(migration);
11945
+ const legacyRawStringRef = useRef(legacyRawString);
12263
11946
  const onValidationFailedRef = useRef(onValidationFailed);
12264
11947
  const onValidationSalvagedRef = useRef(onValidationSalvaged);
12265
11948
  useEffect(() => {
@@ -12271,6 +11954,9 @@ const useWebStorage = (storage, { key, defaultState, schema, migration, onValida
12271
11954
  useEffect(() => {
12272
11955
  migrationRef.current = migration;
12273
11956
  }, [migration]);
11957
+ useEffect(() => {
11958
+ legacyRawStringRef.current = legacyRawString;
11959
+ }, [legacyRawString]);
12274
11960
  useEffect(() => {
12275
11961
  onValidationFailedRef.current = onValidationFailed;
12276
11962
  }, [onValidationFailed]);
@@ -12288,6 +11974,7 @@ const useWebStorage = (storage, { key, defaultState, schema, migration, onValida
12288
11974
  defaultState: defaultStateRef.current,
12289
11975
  schema: schemaRef.current,
12290
11976
  migration: migrationRef.current,
11977
+ legacyRawString: legacyRawStringRef.current,
12291
11978
  onValidationFailed: onValidationFailedRef.current,
12292
11979
  onValidationSalvaged: onValidationSalvagedRef.current,
12293
11980
  }));
@@ -14584,4 +14271,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
14584
14271
  */
14585
14272
  setupLibraryTranslations();
14586
14273
 
14587
- export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, storageSerializer, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
14274
+ export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
@@ -197,7 +197,7 @@ exports.logSummary = logSummary;
197
197
  const insertAttributeIntoOpeningTag = (content, openingElement, attributeText) => {
198
198
  const tagName = openingElement.tagName;
199
199
  const insertPos = tagName.getEnd();
200
- return content.slice(0, insertPos) + ` ${attributeText}` + content.slice(insertPos);
200
+ return `${content.slice(0, insertPos)} ${attributeText}${content.slice(insertPos)}`;
201
201
  };
202
202
  exports.insertAttributeIntoOpeningTag = insertAttributeIntoOpeningTag;
203
203
  //# sourceMappingURL=jsx-utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "2.6.5",
3
+ "version": "2.7.1",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "migrations": "./migrations.json",
@@ -14,17 +14,16 @@
14
14
  "@floating-ui/react": "^0.26.25",
15
15
  "string-ts": "^2.0.0",
16
16
  "tailwind-merge": "^2.0.0",
17
- "@trackunit/ui-design-tokens": "1.14.4",
18
- "@trackunit/css-class-variance-utilities": "1.14.4",
19
- "@trackunit/shared-utils": "1.16.4",
20
- "@trackunit/ui-icons": "1.14.4",
17
+ "@trackunit/ui-design-tokens": "1.14.6",
18
+ "@trackunit/css-class-variance-utilities": "1.14.6",
19
+ "@trackunit/shared-utils": "1.16.6",
20
+ "@trackunit/ui-icons": "1.14.6",
21
21
  "es-toolkit": "^1.39.10",
22
22
  "@tanstack/react-virtual": "^3.14.3",
23
23
  "dequal": "^2.0.3",
24
24
  "fflate": "^0.8.2",
25
- "superjson": "^2.2.6",
26
25
  "zod": "^3.25.76",
27
- "@trackunit/i18n-library-translation": "2.3.6"
26
+ "@trackunit/i18n-library-translation": "2.3.8"
28
27
  },
29
28
  "peerDependencies": {
30
29
  "react": "^19.0.0",
@@ -1,5 +1,5 @@
1
+ import type { WebStorageCallbacks, WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import type { Dispatch, SetStateAction } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Works like useState, but persists to localStorage with Zod schema validation
5
5
  * and superjson serialization (supports Date, Map, Set, BigInt, etc.).
@@ -1,5 +1,5 @@
1
+ import type { WebStorageCallbacks, WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import type { Dispatch } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Works like useReducer, but persists to localStorage with Zod schema validation
5
5
  * and superjson serialization (supports Date, Map, Set, BigInt, etc.).
@@ -1,5 +1,5 @@
1
+ import type { WebStorageCallbacks, WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import type { Dispatch, SetStateAction } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Works like useState, but persists to sessionStorage with Zod schema validation
5
5
  * and superjson serialization (supports Date, Map, Set, BigInt, etc.).
@@ -1,5 +1,5 @@
1
+ import type { WebStorageCallbacks, WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import type { Dispatch } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Works like useReducer, but persists to sessionStorage with Zod schema validation
5
5
  * and superjson serialization (supports Date, Map, Set, BigInt, etc.).
@@ -1,6 +1,6 @@
1
+ import { type WebStorageCallbacks } from "@trackunit/shared-utils";
1
2
  import { type MutableRefObject } from "react";
2
3
  import type { z } from "zod";
3
- import type { WebStorageCallbacks } from "./types";
4
4
  type UseStorageSyncEffectOptions<TState> = {
5
5
  readonly storage: Storage;
6
6
  readonly key: string;
@@ -1,5 +1,5 @@
1
+ import { type WebStorageCallbacks, type WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import { type Dispatch, type SetStateAction } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Internal storage-agnostic hook that backs useLocalStorage and useSessionStorage.
5
5
  *
@@ -10,9 +10,11 @@ import type { WebStorageCallbacks, WebStorageOptions } from "./types";
10
10
  * @param options.defaultState - Fallback value when no stored data exists.
11
11
  * @param options.schema - Zod schema for validation.
12
12
  * @param options.migration - Optional migration configuration (version, steps, fromKey).
13
+ * @param options.legacyRawString - Narrow, opt-in recovery for keys known to hold a plain-text
14
+ * value that was never JSON-encoded. See `WebStorageOptions.legacyRawString`.
13
15
  * @param options.onValidationFailed - Called when validation fails and no salvage is possible.
14
16
  * @param options.onValidationSuccessful - Called when validation succeeds during the write-sync.
15
17
  * @param options.onValidationSalvaged - Called when validation fails but partial state was recovered.
16
18
  * @returns {Array} A tuple of [state, setState, reset].
17
19
  */
18
- export declare const useWebStorage: <TState>(storage: Storage, { key, defaultState, schema, migration, onValidationFailed, onValidationSuccessful, onValidationSalvaged, }: WebStorageOptions<TState> & WebStorageCallbacks<TState>) => readonly [TState, Dispatch<SetStateAction<TState>>, () => void];
20
+ export declare const useWebStorage: <TState>(storage: Storage, { key, defaultState, schema, migration, legacyRawString, onValidationFailed, onValidationSuccessful, onValidationSalvaged, }: WebStorageOptions<TState> & WebStorageCallbacks<TState>) => readonly [TState, Dispatch<SetStateAction<TState>>, () => void];
@@ -1,5 +1,5 @@
1
+ import { type WebStorageCallbacks, type WebStorageOptions } from "@trackunit/shared-utils";
1
2
  import { type Dispatch } from "react";
2
- import type { WebStorageCallbacks, WebStorageOptions } from "./types";
3
3
  /**
4
4
  * Internal storage-agnostic reducer hook that backs useLocalStorageReducer and useSessionStorageReducer.
5
5
  *
package/src/index.d.ts CHANGED
@@ -114,8 +114,7 @@ export * from "./components/ValueBar/ValueBarTypes";
114
114
  export * from "./components/ZStack/ZStack";
115
115
  export * from "./components/ZStack/ZStack.variants";
116
116
  export * from "./hooks/encoding/useCustomEncoding";
117
- export * from "./hooks/localStorage/storageSerializer";
118
- export * from "./hooks/localStorage/types";
117
+ export { storageSerializer, type MigrationConfig, type StorageMigration, type WebStorageCallbacks, type WebStorageOptions, } from "@trackunit/shared-utils";
119
118
  export * from "./hooks/localStorage/useLocalStorage";
120
119
  export * from "./hooks/localStorage/useLocalStorageReducer";
121
120
  export * from "./hooks/localStorage/useSessionStorage";
@@ -1 +0,0 @@
1
- {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/components/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["// Migration entry point for @nx/js:tsc build target.\n// Migrations are registered in ../migrations.json and resolved by NX at runtime.\nexport {};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"jsx-utils.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/utils/jsx-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger, type Tree, visitNotIgnoredFiles } from \"@nx/devkit\";\nimport { tsquery } from \"@phenomnomnominal/tsquery\";\nimport * as ts from \"typescript\";\n\nconst TSX_EXTENSIONS = [\".tsx\", \".jsx\"] as const;\nconst TS_EXTENSIONS = [\".ts\", \".tsx\", \".jsx\"] as const;\n\nconst isTsxFile = (filePath: string): boolean => TSX_EXTENSIONS.some(ext => filePath.endsWith(ext));\nconst isTsFile = (filePath: string): boolean => TS_EXTENSIONS.some(ext => filePath.endsWith(ext));\n\nconst isUnderNodeModules = (filePath: string): boolean =>\n filePath.includes(\"/node_modules/\") || filePath.startsWith(\"node_modules/\");\n\nconst isUnderDist = (filePath: string): boolean =>\n filePath.includes(\"/dist/\") || filePath.startsWith(\"dist/\") || filePath.includes(\"/.nx/\");\n\n/**\n * Visits every `.tsx`/`.jsx` file under the workspace root and invokes the\n * callback with the file path and its current contents. Files that don't\n * include the marker substring are skipped, which makes large workspaces\n * cheap to scan.\n */\nexport const visitTsxFiles = (\n tree: Tree,\n marker: string,\n callback: (filePath: string, content: string) => string | null | undefined\n): number => {\n let touched = 0;\n visitNotIgnoredFiles(tree, \"/\", filePath => {\n if (!isTsxFile(filePath)) return;\n if (isUnderNodeModules(filePath) || isUnderDist(filePath)) return;\n const content = tree.read(filePath, \"utf-8\");\n if (content === null) return;\n if (!content.includes(marker)) return;\n const updated = callback(filePath, content);\n if (updated !== null && updated !== undefined && updated !== content) {\n tree.write(filePath, updated);\n touched += 1;\n }\n });\n return touched;\n};\n\n/**\n * Same as {@link visitTsxFiles} but also includes plain `.ts` files. Use this\n * when the codemod also rewrites non-JSX files such as helpers or hooks.\n */\nexport const visitTsFiles = (\n tree: Tree,\n marker: string,\n callback: (filePath: string, content: string) => string | null | undefined\n): number => {\n let touched = 0;\n visitNotIgnoredFiles(tree, \"/\", filePath => {\n if (!isTsFile(filePath)) return;\n if (isUnderNodeModules(filePath) || isUnderDist(filePath)) return;\n const content = tree.read(filePath, \"utf-8\");\n if (content === null) return;\n if (!content.includes(marker)) return;\n const updated = callback(filePath, content);\n if (updated !== null && updated !== undefined && updated !== content) {\n tree.write(filePath, updated);\n touched += 1;\n }\n });\n return touched;\n};\n\n/**\n * Mapping of imported component aliases to their original names for a given\n * package. For `import { IconButton as Btn } from \"@trackunit/react-components\"`\n * this returns `{ Btn: \"IconButton\" }`.\n *\n * Returns `null` when the file imports nothing from the package, which lets\n * callers short-circuit before parsing JSX.\n */\nexport const getImportedAliases = (sourceFile: ts.SourceFile, packageName: string): Record<string, string> | null => {\n const result: Record<string, string> = {};\n let found = false;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier)) continue;\n if (moduleSpecifier.text !== packageName) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n for (const element of namedBindings.elements) {\n const localName = element.name.text;\n const importedName = element.propertyName?.text ?? localName;\n result[localName] = importedName;\n found = true;\n }\n }\n\n return found ? result : null;\n};\n\n/**\n * Returns the local alias used in this file for `originalName` when imported\n * from `packageName`, or `null` if the component is not imported.\n */\nexport const getLocalAliasFor = (\n sourceFile: ts.SourceFile,\n packageName: string,\n originalName: string\n): string | null => {\n const aliases = getImportedAliases(sourceFile, packageName);\n if (aliases === null) return null;\n for (const [local, original] of Object.entries(aliases)) {\n if (original === originalName) return local;\n }\n return null;\n};\n\nexport type JsxElementMatch = {\n /** The opening JSX element (the one carrying the attributes). */\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement;\n /** The full element including children, or the self-closing element. */\n element: ts.JsxElement | ts.JsxSelfClosingElement;\n};\n\n/**\n * Finds every JSX element whose tag name resolves to one of `tagNames`\n * (typically the local aliases returned by {@link getImportedAliases}).\n *\n * The result intentionally includes both opening and full elements so callers\n * can manipulate attributes (via `openingElement`) or wrap/replace the whole\n * element (via `element`).\n */\nexport const findJsxElements = (\n sourceFile: ts.SourceFile,\n tagNames: ReadonlyArray<string>\n): ReadonlyArray<JsxElementMatch> => {\n const matches: Array<JsxElementMatch> = [];\n const tagSet = new Set(tagNames);\n\n const visit = (node: ts.Node): void => {\n if (ts.isJsxSelfClosingElement(node)) {\n if (ts.isIdentifier(node.tagName) && tagSet.has(node.tagName.text)) {\n matches.push({ openingElement: node, element: node });\n }\n } else if (ts.isJsxElement(node)) {\n const opening = node.openingElement;\n if (ts.isIdentifier(opening.tagName) && tagSet.has(opening.tagName.text)) {\n matches.push({ openingElement: opening, element: node });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return matches;\n};\n\n/**\n * Looks up a named JSX attribute on the opening element. Returns `null` when\n * the attribute is missing or part of a spread attribute (which we cannot\n * statically inspect).\n */\nexport const findJsxAttribute = (\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement,\n attributeName: string\n): ts.JsxAttribute | null => {\n for (const attr of openingElement.attributes.properties) {\n if (!ts.isJsxAttribute(attr)) continue;\n if (!ts.isIdentifier(attr.name)) continue;\n if (attr.name.text === attributeName) return attr;\n }\n return null;\n};\n\n/**\n * `true` when the element forwards a spread expression such as `{...rest}`,\n * which means we can't be sure which attributes are actually set.\n */\nexport const hasSpreadAttribute = (openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement): boolean => {\n return openingElement.attributes.properties.some(prop => ts.isJsxSpreadAttribute(prop));\n};\n\n/**\n * Parses the file as TSX so JSX is recognised. We do not need a full program\n * here; tsquery's `ast` helper produces a script-kind source file which loses\n * JSX context, so we go through `createSourceFile` directly.\n */\nexport const parseTsx = (content: string, fileName = \"file.tsx\"): ts.SourceFile =>\n ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n\n/** Re-exported for codemods that need direct AST queries. */\nexport { tsquery };\n\n/**\n * Helper for codemods to log a one-line summary at the end of a run. Migration\n * runners aggregate logs per migration, so this keeps output focused.\n */\nexport const logSummary = (migrationName: string, touched: number, manualReviewNeeded?: number): void => {\n if (touched === 0) {\n logger.info(` ${migrationName}: no files changed`);\n return;\n }\n const reviewSuffix =\n manualReviewNeeded !== undefined && manualReviewNeeded > 0\n ? ` (${manualReviewNeeded} location(s) need manual review)`\n : \"\";\n logger.info(` ${migrationName}: updated ${touched} file(s)${reviewSuffix}`);\n};\n\n/**\n * Inserts an attribute string immediately after the tag name (so the new\n * attribute appears first). Returns the resulting source content.\n *\n * The added attribute is rendered verbatim, so callers are responsible for\n * including the leading space (e.g. ` title=\"Close\"`).\n */\nexport const insertAttributeIntoOpeningTag = (\n content: string,\n openingElement: ts.JsxOpeningElement | ts.JsxSelfClosingElement,\n attributeText: string\n): string => {\n const tagName = openingElement.tagName;\n const insertPos = tagName.getEnd();\n return content.slice(0, insertPos) + ` ${attributeText}` + content.slice(insertPos);\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"breadcrumb-remove-deprecated-props.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/breadcrumb-remove-deprecated-props.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, logger } from \"@nx/devkit\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAMES = [\"Breadcrumb\", \"BreadcrumbContainer\", \"BreadcrumbForMediumScreen\"] as const;\nconst PROPS_TO_REMOVE = [\"backButtonTitle\", \"expandCollapsedItemsTitle\"] as const;\n\nconst stripDeprecatedProps = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const localTagNames: Array<string> = [];\n for (const [local, original] of Object.entries(aliases)) {\n if (COMPONENT_NAMES.some(name => name === original)) {\n localTagNames.push(local);\n }\n }\n if (localTagNames.length === 0) return null;\n\n const matches = findJsxElements(sourceFile, localTagNames);\n if (matches.length === 0) return null;\n\n // Collect every (start, end) range we want to delete. We process them in\n // reverse so earlier offsets stay valid while we splice.\n const ranges: Array<{ start: number; end: number }> = [];\n for (const { openingElement } of matches) {\n for (const propName of PROPS_TO_REMOVE) {\n const attr = findJsxAttribute(openingElement, propName);\n if (attr === null) continue;\n\n // Drop trailing whitespace + the attribute itself so we don't leave a\n // dangling space inside the opening tag. e.g. `<X foo=\"a\" bar=\"b\" />`\n // becomes `<X foo=\"a\" />` and `<X bar=\"b\" />` becomes `<X />`.\n let start = attr.getFullStart();\n const end = attr.getEnd();\n // getFullStart includes leading whitespace which is what we want.\n // Guard against negative ranges if multiple props collapse later.\n if (start < 0) start = attr.getStart();\n ranges.push({ start, end });\n }\n }\n\n if (ranges.length === 0) return null;\n\n ranges.sort((a, b) => b.start - a.start);\n\n let updated = content;\n for (const { start, end } of ranges) {\n updated = updated.slice(0, start) + updated.slice(end);\n }\n\n return updated;\n};\n\n/**\n * Removes the no-longer-supported `backButtonTitle` and\n * `expandCollapsedItemsTitle` props from `<Breadcrumb>`,\n * `<BreadcrumbContainer>`, and `<BreadcrumbForMediumScreen>`. Their accessible\n * names are now sourced from the `react-components` library translations.\n */\nexport const breadcrumbRemoveDeprecatedProps = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"Breadcrumb\", stripDeprecatedProps);\n logSummary(\"breadcrumb-remove-deprecated-props\", touched);\n logger.info(\n \" Ensure the host application registers the @trackunit/react-components library translations so the back/expand buttons render localized labels.\"\n );\n};\n\nexport default breadcrumbRemoveDeprecatedProps;\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cardheader-onclickclose-to-actions.js","sourceRoot":"","sources":["../../../../../../libs/react/components/migrations/v2-0-0/cardheader-onclickclose-to-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree } from \"@nx/devkit\";\nimport * as ts from \"typescript\";\nimport {\n findJsxAttribute,\n findJsxElements,\n getImportedAliases,\n logSummary,\n parseTsx,\n visitTsxFiles,\n} from \"../utils/jsx-utils\";\n\nconst PACKAGE_NAME = \"@trackunit/react-components\";\nconst COMPONENT_NAME = \"CardHeader\";\n\nconst CLOSE_BUTTON_NAMES = [\"IconButton\", \"Icon\"] as const;\n\nconst renderCloseButton = (handlerExpr: string, iconAlias: string, iconButtonAlias: string): string =>\n `<${iconButtonAlias} icon={<${iconAlias} name=\"XMark\" size=\"small\" />} onClick={${handlerExpr}} title=\"Close\" variant=\"ghost-neutral\" />`;\n\nconst ensureNamedImport = (content: string, packageName: string, namesToAdd: ReadonlyArray<string>): string => {\n const sourceFile = parseTsx(content, \"source.tsx\");\n const aliases = getImportedAliases(sourceFile, packageName) ?? {};\n\n const missing = namesToAdd.filter(name => !Object.values(aliases).includes(name));\n if (missing.length === 0) return content;\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpecifier = stmt.moduleSpecifier;\n if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName) continue;\n\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings === undefined || !ts.isNamedImports(namedBindings)) continue;\n\n const elementsText = namedBindings.elements.map(el => el.getText()).join(\", \");\n const merged = [elementsText, ...missing].join(\", \");\n const before = content.slice(0, namedBindings.getStart());\n const after = content.slice(namedBindings.getEnd());\n return `${before}{ ${merged} }${after}`;\n }\n\n // No existing import from the package; add a fresh line near the top.\n const importLine = `import { ${missing.join(\", \")} } from \"${packageName}\";\\n`;\n return importLine + content;\n};\n\nconst transformCardHeaderUsage = (filePath: string, content: string): string | null => {\n const sourceFile = parseTsx(content, filePath);\n\n const aliases = getImportedAliases(sourceFile, PACKAGE_NAME);\n if (aliases === null) return null;\n\n const cardHeaderAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];\n if (cardHeaderAlias === undefined) return null;\n\n const matches = findJsxElements(sourceFile, [cardHeaderAlias]);\n if (matches.length === 0) return null;\n\n type Edit = { start: number; end: number; replacement: string };\n const edits: Array<Edit> = [];\n\n for (const { openingElement } of matches) {\n const onClickCloseAttr = findJsxAttribute(openingElement, \"onClickClose\");\n if (onClickCloseAttr === null) continue;\n\n const initializer = onClickCloseAttr.initializer;\n if (initializer === undefined) continue;\n\n let handlerExpr: string;\n if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {\n handlerExpr = initializer.expression.getText();\n } else if (ts.isStringLiteral(initializer)) {\n handlerExpr = `\"${initializer.text}\"`;\n } else {\n continue;\n }\n\n const actionsAttr = findJsxAttribute(openingElement, \"actions\");\n if (actionsAttr !== null) {\n // Don't try to merge with an existing actions prop; flag and skip.\n continue;\n }\n\n const replacement = `actions={${renderCloseButton(handlerExpr, \"Icon\", \"IconButton\")}}`;\n edits.push({\n start: onClickCloseAttr.getStart(),\n end: onClickCloseAttr.getEnd(),\n replacement,\n });\n }\n\n if (edits.length === 0) return null;\n\n edits.sort((a, b) => b.start - a.start);\n\n let updated = content;\n for (const { start, end, replacement } of edits) {\n updated = updated.slice(0, start) + replacement + updated.slice(end);\n }\n\n updated = ensureNamedImport(updated, PACKAGE_NAME, [...CLOSE_BUTTON_NAMES]);\n\n return updated;\n};\n\n/**\n * Replaces the removed `onClickClose` prop on `<CardHeader>` with the\n * equivalent action rendered through the `actions` slot using `IconButton` +\n * the `XMark` icon.\n *\n * Cards that already declared `actions` are skipped to avoid clobbering\n * existing markup.\n */\nexport const cardHeaderOnClickCloseToActions = (tree: Tree): void => {\n const touched = visitTsxFiles(tree, \"CardHeader\", transformCardHeaderUsage);\n logSummary(\"cardheader-onclickclose-to-actions\", touched);\n};\n\nexport default cardHeaderOnClickCloseToActions;\n"]}