hazo_ui 6.0.0 → 6.3.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/CHANGE_LOG.md +71 -0
- package/README.md +24 -1
- package/dist/announce-DAnQDTld.d.cts +92 -0
- package/dist/announce-DAnQDTld.d.ts +92 -0
- package/dist/index.cjs +274 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -3
- package/dist/index.d.ts +160 -3
- package/dist/index.js +266 -2
- package/dist/index.js.map +1 -1
- package/dist/index.utils.cjs +72 -0
- package/dist/index.utils.cjs.map +1 -1
- package/dist/index.utils.d.cts +31 -1
- package/dist/index.utils.d.ts +31 -1
- package/dist/index.utils.js +64 -1
- package/dist/index.utils.js.map +1 -1
- package/package.json +3 -3
- package/dist/index.utils-DZVbQXFR.d.cts +0 -20
- package/dist/index.utils-DZVbQXFR.d.ts +0 -20
package/dist/index.utils.cjs
CHANGED
|
@@ -7,6 +7,78 @@ function cn(...inputs) {
|
|
|
7
7
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// src/lib/remembered_state.ts
|
|
11
|
+
function readRememberedState(key) {
|
|
12
|
+
if (typeof window === "undefined") return null;
|
|
13
|
+
try {
|
|
14
|
+
const raw = window.localStorage.getItem(key);
|
|
15
|
+
return raw ? JSON.parse(raw) : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function writeRememberedState(key, snapshot) {
|
|
21
|
+
if (typeof window === "undefined") return;
|
|
22
|
+
try {
|
|
23
|
+
window.localStorage.setItem(key, JSON.stringify(snapshot));
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function hasUrlParam(search, paramName) {
|
|
28
|
+
try {
|
|
29
|
+
return new URLSearchParams(search).has(paramName);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/lib/announce.ts
|
|
36
|
+
var DEFAULT_ANNOUNCE_EVENT = "hazo_ui:announce";
|
|
37
|
+
function announce(message, priority = "polite", options) {
|
|
38
|
+
if (typeof window === "undefined") return;
|
|
39
|
+
const trimmed = message.trim();
|
|
40
|
+
if (!trimmed) return;
|
|
41
|
+
const eventName = options?.eventName ?? DEFAULT_ANNOUNCE_EVENT;
|
|
42
|
+
window.dispatchEvent(
|
|
43
|
+
new CustomEvent(eventName, {
|
|
44
|
+
detail: { message: trimmed, priority }
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/lib/scroll_restore.ts
|
|
50
|
+
var DEFAULT_SCROLL_RESTORE_PREFIX = "hazo_ui:scrollPos";
|
|
51
|
+
function buildScrollRestoreKey(prefix, treeId, viewName) {
|
|
52
|
+
return `${prefix}.${treeId}.${viewName}`;
|
|
53
|
+
}
|
|
54
|
+
function saveScrollPos(key, scrollY) {
|
|
55
|
+
if (typeof window === "undefined") return;
|
|
56
|
+
try {
|
|
57
|
+
window.localStorage.setItem(key, String(Math.round(scrollY)));
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function loadScrollPos(key) {
|
|
62
|
+
if (typeof window === "undefined") return null;
|
|
63
|
+
try {
|
|
64
|
+
const raw = window.localStorage.getItem(key);
|
|
65
|
+
if (!raw) return null;
|
|
66
|
+
const n = Number.parseInt(raw, 10);
|
|
67
|
+
return Number.isFinite(n) ? n : null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
exports.DEFAULT_ANNOUNCE_EVENT = DEFAULT_ANNOUNCE_EVENT;
|
|
74
|
+
exports.DEFAULT_SCROLL_RESTORE_PREFIX = DEFAULT_SCROLL_RESTORE_PREFIX;
|
|
75
|
+
exports.announce = announce;
|
|
76
|
+
exports.buildScrollRestoreKey = buildScrollRestoreKey;
|
|
10
77
|
exports.cn = cn;
|
|
78
|
+
exports.hasUrlParam = hasUrlParam;
|
|
79
|
+
exports.loadScrollPos = loadScrollPos;
|
|
80
|
+
exports.readRememberedState = readRememberedState;
|
|
81
|
+
exports.saveScrollPos = saveScrollPos;
|
|
82
|
+
exports.writeRememberedState = writeRememberedState;
|
|
11
83
|
//# sourceMappingURL=index.utils.cjs.map
|
|
12
84
|
//# sourceMappingURL=index.utils.cjs.map
|
package/dist/index.utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/utils.ts"],"names":["twMerge","clsx"],"mappings":";;;;;AAWO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"index.utils.cjs","sourcesContent":["// Utility functions for hazo_ui component library\n// Provides className merging utility (cn function)\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class string\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Sanitizes a user/admin-supplied href against a scheme allowlist to prevent\n * `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an\n * `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but\n * not protocol-relative `//…`). Returns `undefined` for anything else, so the\n * anchor renders with no href rather than an active malicious one.\n * @param url - Candidate href (may be undefined)\n * @returns The original url if allowed, otherwise undefined\n */\nexport function safeHref(url?: string): string | undefined {\n if (!url) return undefined;\n const trimmed = url.trim();\n const lower = trimmed.toLowerCase();\n if (\n lower.startsWith(\"http://\") ||\n lower.startsWith(\"https://\") ||\n lower.startsWith(\"mailto:\")\n ) {\n return trimmed;\n }\n // Same-origin absolute path, but not protocol-relative \"//host\".\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return trimmed;\n }\n return undefined;\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/lib/remembered_state.ts","../src/lib/announce.ts","../src/lib/scroll_restore.ts"],"names":["twMerge","clsx"],"mappings":";;;;;AAWO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;;;ACIO,SAAS,oBAAuB,GAAA,EAAuB;AAC5D,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC3C,IAAA,OAAO,GAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAU,IAAA;AAAA,EACxC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAaO,SAAS,oBAAA,CAAwB,KAAa,QAAA,EAAmB;AACtE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,aAAa,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAC,CAAA;AAAA,EAC3D,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAeO,SAAS,WAAA,CAAY,QAAgB,SAAA,EAA4B;AACtE,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,eAAA,CAAgB,MAAM,CAAA,CAAE,IAAI,SAAS,CAAA;AAAA,EAClD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;;;ACtCO,IAAM,sBAAA,GAAyB;AAuB/B,SAAS,QAAA,CACd,OAAA,EACA,QAAA,GAA6B,QAAA,EAC7B,OAAA,EACM;AACN,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,MAAM,OAAA,GAAU,QAAQ,IAAA,EAAK;AAC7B,EAAA,IAAI,CAAC,OAAA,EAAS;AACd,EAAA,MAAM,SAAA,GAAY,SAAS,SAAA,IAAa,sBAAA;AACxC,EAAA,MAAA,CAAO,aAAA;AAAA,IACL,IAAI,YAA8B,SAAA,EAAW;AAAA,MAC3C,MAAA,EAAQ,EAAE,OAAA,EAAS,OAAA,EAAS,QAAA;AAAS,KACtC;AAAA,GACH;AACF;;;ACrDO,IAAM,6BAAA,GAAgC;AAStC,SAAS,qBAAA,CAAsB,MAAA,EAAgB,MAAA,EAAgB,QAAA,EAA0B;AAC9F,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAM,IAAI,QAAQ,CAAA,CAAA;AACxC;AAUO,SAAS,aAAA,CAAc,KAAa,OAAA,EAAuB;AAChE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,YAAA,CAAa,QAAQ,GAAA,EAAK,MAAA,CAAO,KAAK,KAAA,CAAM,OAAO,CAAC,CAAC,CAAA;AAAA,EAC9D,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AASO,SAAS,cAAc,GAAA,EAA4B;AACxD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AACjC,IAAA,OAAO,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF","file":"index.utils.cjs","sourcesContent":["// Utility functions for hazo_ui component library\n// Provides className merging utility (cn function)\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class string\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Sanitizes a user/admin-supplied href against a scheme allowlist to prevent\n * `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an\n * `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but\n * not protocol-relative `//…`). Returns `undefined` for anything else, so the\n * anchor renders with no href rather than an active malicious one.\n * @param url - Candidate href (may be undefined)\n * @returns The original url if allowed, otherwise undefined\n */\nexport function safeHref(url?: string): string | undefined {\n if (!url) return undefined;\n const trimmed = url.trim();\n const lower = trimmed.toLowerCase();\n if (\n lower.startsWith(\"http://\") ||\n lower.startsWith(\"https://\") ||\n lower.startsWith(\"mailto:\")\n ) {\n return trimmed;\n }\n // Same-origin absolute path, but not protocol-relative \"//host\".\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return trimmed;\n }\n return undefined;\n}\n\n","// remembered_state.ts — SSR-safe localStorage read/write/URL-param helpers\n// backing `useRememberedState` (src/hooks/use_remembered_state.ts). Pure,\n// framework-free functions (no React) so they're independently testable and\n// usable outside the hook. No \"use client\" directive here — this module is\n// re-exported from both the main client bundle (`hazo_ui`) and the pure\n// server-safe subpath (`hazo_ui/utils`), same dual-export pattern as `cn`.\n\n/**\n * Reads and JSON-parses the remembered value stored under `key`. SSR-safe —\n * returns `null` on the server, when nothing is stored, or when the stored\n * value is corrupt/unparsable.\n *\n * @param key - localStorage key to read.\n *\n * @example\n * const seed = readRememberedState<MySeed>(\"myapp:tool:cgt\");\n */\nexport function readRememberedState<T>(key: string): T | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(key);\n return raw ? (JSON.parse(raw) as T) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * JSON-stringifies and writes `snapshot` under `key`. SSR-safe — no-ops on\n * the server. Quota / private-mode errors are swallowed; losing a memory\n * write is non-fatal.\n *\n * @param key - localStorage key to write.\n * @param snapshot - Serialisable value to remember.\n *\n * @example\n * writeRememberedState(\"myapp:tool:cgt\", { proceeds: 1000 });\n */\nexport function writeRememberedState<T>(key: string, snapshot: T): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, JSON.stringify(snapshot));\n } catch {\n /* quota / private-mode — non-fatal */\n }\n}\n\n/**\n * True when `paramName` is present in the given search string (e.g.\n * `window.location.search`). Generic form of the \"an explicit shared-link /\n * saved-record query param always wins over remembered state\" precedence\n * check — callers supply the param name (e.g. `\"restore\"`); this module\n * never hardcodes one.\n *\n * @param search - A URL search string, e.g. `window.location.search`.\n * @param paramName - The query param to look for.\n *\n * @example\n * hasUrlParam(\"?restore=abc123\", \"restore\"); // true\n */\nexport function hasUrlParam(search: string, paramName: string): boolean {\n try {\n return new URLSearchParams(search).has(paramName);\n } catch {\n return false;\n }\n}\n","// announce.ts — SSR-safe screen-reader announcement bus. Dispatches a\n// `window` CustomEvent that `LiveRegion` (../components/live_region/)\n// listens for. Pure, framework-free (no React) so it's independently\n// testable and usable outside a component. No \"use client\" directive here\n// — this module is re-exported from both the main client bundle\n// (`hazo_ui`) and the pure server-safe subpath (`hazo_ui/utils`), same\n// dual-export pattern as `cn` and the `remembered_state` helpers.\n//\n// Ported from Kinstripe's `src/lib/a11y/announce.ts`, which hardcoded the\n// event name to `\"kinstripe:announce\"`. Here the event name is caller-\n// configurable (`options.eventName`) so multiple apps — or multiple\n// `LiveRegion` instances on the same page — don't collide on a single\n// hardcoded channel; it defaults to `DEFAULT_ANNOUNCE_EVENT` for\n// single-app usage.\n\nexport type AnnouncePriority = \"polite\" | \"assertive\";\n\nexport interface LiveAnnouncement {\n message: string;\n priority: AnnouncePriority;\n}\n\nexport interface AnnounceOptions {\n /** Custom-event name the paired `LiveRegion` listens on. Must match the `eventName` it was mounted with. Default `DEFAULT_ANNOUNCE_EVENT`. */\n eventName?: string;\n}\n\n/** Default `window` CustomEvent name used when `announce()` / `LiveRegion` are not given an explicit `eventName`. */\nexport const DEFAULT_ANNOUNCE_EVENT = \"hazo_ui:announce\";\n\n/**\n * Dispatch a screen-reader announcement via a `window` CustomEvent. Safe to\n * call from anywhere in the client tree — no-ops during SSR or before a\n * `LiveRegion` has mounted (fire-and-forget; there's no queue/replay).\n *\n * Use `\"polite\"` (default) for ambient updates (\"Sarah joined the canvas\",\n * \"2 new notifications\"). Use `\"assertive\"` only for things the user must\n * hear immediately (errors that interrupt a task).\n *\n * @param message - Text to announce. Empty / whitespace-only messages are dropped (no dispatch).\n * @param priority - `\"polite\"` (default) or `\"assertive\"`.\n * @param options.eventName - Custom-event name to dispatch on. Default `DEFAULT_ANNOUNCE_EVENT`.\n *\n * @example\n * announce(\"2 new notifications\");\n * announce(\"Save failed — check your connection\", \"assertive\");\n *\n * @example\n * // namespaced channel — must match the eventName the LiveRegion was mounted with\n * announce(\"Saved\", \"polite\", { eventName: \"myapp:announce\" });\n */\nexport function announce(\n message: string,\n priority: AnnouncePriority = \"polite\",\n options?: AnnounceOptions,\n): void {\n if (typeof window === \"undefined\") return;\n const trimmed = message.trim();\n if (!trimmed) return;\n const eventName = options?.eventName ?? DEFAULT_ANNOUNCE_EVENT;\n window.dispatchEvent(\n new CustomEvent<LiveAnnouncement>(eventName, {\n detail: { message: trimmed, priority },\n }),\n );\n}\n","// scroll_restore.ts — SSR-safe localStorage read/write helpers backing\n// `useScrollRestore` (../hooks/use_scroll_restore.ts). Pure, framework-free\n// functions (no React) so they're independently testable and usable\n// outside the hook — same split as `remembered_state.ts` /\n// `useRememberedState`. No \"use client\" directive here.\n//\n// Ported from Kinstripe's `src/lib/hooks/useScrollRestore.ts`, which\n// hardcoded the key prefix to `\"kinstripe.scrollPos\"`. Here the prefix is a\n// parameter (`buildScrollRestoreKey`) / hook option, so the package stays\n// app-agnostic.\n\n/** Default localStorage key prefix used when no `prefix` option is given. */\nexport const DEFAULT_SCROLL_RESTORE_PREFIX = \"hazo_ui:scrollPos\";\n\n/**\n * Composes the localStorage key from a prefix, tree id, and view name.\n *\n * @example\n * buildScrollRestoreKey(\"hazo_ui:scrollPos\", \"tree-42\", \"canvas\");\n * // -> \"hazo_ui:scrollPos.tree-42.canvas\"\n */\nexport function buildScrollRestoreKey(prefix: string, treeId: string, viewName: string): string {\n return `${prefix}.${treeId}.${viewName}`;\n}\n\n/**\n * Persists a scroll position (rounded to the nearest pixel) under `key`.\n * SSR-safe — no-ops on the server. Quota / private-mode errors are\n * swallowed; a failed write is non-fatal.\n *\n * @param key - localStorage key (see `buildScrollRestoreKey`).\n * @param scrollY - Scroll offset in pixels.\n */\nexport function saveScrollPos(key: string, scrollY: number): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, String(Math.round(scrollY)));\n } catch {\n /* quota exceeded — silently drop */\n }\n}\n\n/**\n * Reads back the scroll position stored under `key`. SSR-safe — returns\n * `null` on the server, when nothing is stored, or when the stored value\n * isn't a finite number.\n *\n * @param key - localStorage key (see `buildScrollRestoreKey`).\n */\nexport function loadScrollPos(key: string): number | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return null;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n"]}
|
package/dist/index.utils.d.cts
CHANGED
|
@@ -1,2 +1,32 @@
|
|
|
1
|
-
export { c as cn } from './
|
|
1
|
+
export { A as AnnounceOptions, a as AnnouncePriority, D as DEFAULT_ANNOUNCE_EVENT, L as LiveAnnouncement, b as announce, c as cn, h as hasUrlParam, r as readRememberedState, w as writeRememberedState } from './announce-DAnQDTld.cjs';
|
|
2
2
|
import 'clsx';
|
|
3
|
+
|
|
4
|
+
/** Default localStorage key prefix used when no `prefix` option is given. */
|
|
5
|
+
declare const DEFAULT_SCROLL_RESTORE_PREFIX = "hazo_ui:scrollPos";
|
|
6
|
+
/**
|
|
7
|
+
* Composes the localStorage key from a prefix, tree id, and view name.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* buildScrollRestoreKey("hazo_ui:scrollPos", "tree-42", "canvas");
|
|
11
|
+
* // -> "hazo_ui:scrollPos.tree-42.canvas"
|
|
12
|
+
*/
|
|
13
|
+
declare function buildScrollRestoreKey(prefix: string, treeId: string, viewName: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Persists a scroll position (rounded to the nearest pixel) under `key`.
|
|
16
|
+
* SSR-safe — no-ops on the server. Quota / private-mode errors are
|
|
17
|
+
* swallowed; a failed write is non-fatal.
|
|
18
|
+
*
|
|
19
|
+
* @param key - localStorage key (see `buildScrollRestoreKey`).
|
|
20
|
+
* @param scrollY - Scroll offset in pixels.
|
|
21
|
+
*/
|
|
22
|
+
declare function saveScrollPos(key: string, scrollY: number): void;
|
|
23
|
+
/**
|
|
24
|
+
* Reads back the scroll position stored under `key`. SSR-safe — returns
|
|
25
|
+
* `null` on the server, when nothing is stored, or when the stored value
|
|
26
|
+
* isn't a finite number.
|
|
27
|
+
*
|
|
28
|
+
* @param key - localStorage key (see `buildScrollRestoreKey`).
|
|
29
|
+
*/
|
|
30
|
+
declare function loadScrollPos(key: string): number | null;
|
|
31
|
+
|
|
32
|
+
export { DEFAULT_SCROLL_RESTORE_PREFIX, buildScrollRestoreKey, loadScrollPos, saveScrollPos };
|
package/dist/index.utils.d.ts
CHANGED
|
@@ -1,2 +1,32 @@
|
|
|
1
|
-
export { c as cn } from './
|
|
1
|
+
export { A as AnnounceOptions, a as AnnouncePriority, D as DEFAULT_ANNOUNCE_EVENT, L as LiveAnnouncement, b as announce, c as cn, h as hasUrlParam, r as readRememberedState, w as writeRememberedState } from './announce-DAnQDTld.js';
|
|
2
2
|
import 'clsx';
|
|
3
|
+
|
|
4
|
+
/** Default localStorage key prefix used when no `prefix` option is given. */
|
|
5
|
+
declare const DEFAULT_SCROLL_RESTORE_PREFIX = "hazo_ui:scrollPos";
|
|
6
|
+
/**
|
|
7
|
+
* Composes the localStorage key from a prefix, tree id, and view name.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* buildScrollRestoreKey("hazo_ui:scrollPos", "tree-42", "canvas");
|
|
11
|
+
* // -> "hazo_ui:scrollPos.tree-42.canvas"
|
|
12
|
+
*/
|
|
13
|
+
declare function buildScrollRestoreKey(prefix: string, treeId: string, viewName: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Persists a scroll position (rounded to the nearest pixel) under `key`.
|
|
16
|
+
* SSR-safe — no-ops on the server. Quota / private-mode errors are
|
|
17
|
+
* swallowed; a failed write is non-fatal.
|
|
18
|
+
*
|
|
19
|
+
* @param key - localStorage key (see `buildScrollRestoreKey`).
|
|
20
|
+
* @param scrollY - Scroll offset in pixels.
|
|
21
|
+
*/
|
|
22
|
+
declare function saveScrollPos(key: string, scrollY: number): void;
|
|
23
|
+
/**
|
|
24
|
+
* Reads back the scroll position stored under `key`. SSR-safe — returns
|
|
25
|
+
* `null` on the server, when nothing is stored, or when the stored value
|
|
26
|
+
* isn't a finite number.
|
|
27
|
+
*
|
|
28
|
+
* @param key - localStorage key (see `buildScrollRestoreKey`).
|
|
29
|
+
*/
|
|
30
|
+
declare function loadScrollPos(key: string): number | null;
|
|
31
|
+
|
|
32
|
+
export { DEFAULT_SCROLL_RESTORE_PREFIX, buildScrollRestoreKey, loadScrollPos, saveScrollPos };
|
package/dist/index.utils.js
CHANGED
|
@@ -5,6 +5,69 @@ function cn(...inputs) {
|
|
|
5
5
|
return twMerge(clsx(inputs));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// src/lib/remembered_state.ts
|
|
9
|
+
function readRememberedState(key) {
|
|
10
|
+
if (typeof window === "undefined") return null;
|
|
11
|
+
try {
|
|
12
|
+
const raw = window.localStorage.getItem(key);
|
|
13
|
+
return raw ? JSON.parse(raw) : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function writeRememberedState(key, snapshot) {
|
|
19
|
+
if (typeof window === "undefined") return;
|
|
20
|
+
try {
|
|
21
|
+
window.localStorage.setItem(key, JSON.stringify(snapshot));
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function hasUrlParam(search, paramName) {
|
|
26
|
+
try {
|
|
27
|
+
return new URLSearchParams(search).has(paramName);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/lib/announce.ts
|
|
34
|
+
var DEFAULT_ANNOUNCE_EVENT = "hazo_ui:announce";
|
|
35
|
+
function announce(message, priority = "polite", options) {
|
|
36
|
+
if (typeof window === "undefined") return;
|
|
37
|
+
const trimmed = message.trim();
|
|
38
|
+
if (!trimmed) return;
|
|
39
|
+
const eventName = options?.eventName ?? DEFAULT_ANNOUNCE_EVENT;
|
|
40
|
+
window.dispatchEvent(
|
|
41
|
+
new CustomEvent(eventName, {
|
|
42
|
+
detail: { message: trimmed, priority }
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/lib/scroll_restore.ts
|
|
48
|
+
var DEFAULT_SCROLL_RESTORE_PREFIX = "hazo_ui:scrollPos";
|
|
49
|
+
function buildScrollRestoreKey(prefix, treeId, viewName) {
|
|
50
|
+
return `${prefix}.${treeId}.${viewName}`;
|
|
51
|
+
}
|
|
52
|
+
function saveScrollPos(key, scrollY) {
|
|
53
|
+
if (typeof window === "undefined") return;
|
|
54
|
+
try {
|
|
55
|
+
window.localStorage.setItem(key, String(Math.round(scrollY)));
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function loadScrollPos(key) {
|
|
60
|
+
if (typeof window === "undefined") return null;
|
|
61
|
+
try {
|
|
62
|
+
const raw = window.localStorage.getItem(key);
|
|
63
|
+
if (!raw) return null;
|
|
64
|
+
const n = Number.parseInt(raw, 10);
|
|
65
|
+
return Number.isFinite(n) ? n : null;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { DEFAULT_ANNOUNCE_EVENT, DEFAULT_SCROLL_RESTORE_PREFIX, announce, buildScrollRestoreKey, cn, hasUrlParam, loadScrollPos, readRememberedState, saveScrollPos, writeRememberedState };
|
|
9
72
|
//# sourceMappingURL=index.utils.js.map
|
|
10
73
|
//# sourceMappingURL=index.utils.js.map
|
package/dist/index.utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/utils.ts"],"names":[],"mappings":";;;AAWO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"index.utils.js","sourcesContent":["// Utility functions for hazo_ui component library\n// Provides className merging utility (cn function)\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class string\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Sanitizes a user/admin-supplied href against a scheme allowlist to prevent\n * `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an\n * `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but\n * not protocol-relative `//…`). Returns `undefined` for anything else, so the\n * anchor renders with no href rather than an active malicious one.\n * @param url - Candidate href (may be undefined)\n * @returns The original url if allowed, otherwise undefined\n */\nexport function safeHref(url?: string): string | undefined {\n if (!url) return undefined;\n const trimmed = url.trim();\n const lower = trimmed.toLowerCase();\n if (\n lower.startsWith(\"http://\") ||\n lower.startsWith(\"https://\") ||\n lower.startsWith(\"mailto:\")\n ) {\n return trimmed;\n }\n // Same-origin absolute path, but not protocol-relative \"//host\".\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return trimmed;\n }\n return undefined;\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/lib/remembered_state.ts","../src/lib/announce.ts","../src/lib/scroll_restore.ts"],"names":[],"mappings":";;;AAWO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;;;ACIO,SAAS,oBAAuB,GAAA,EAAuB;AAC5D,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC3C,IAAA,OAAO,GAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAU,IAAA;AAAA,EACxC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAaO,SAAS,oBAAA,CAAwB,KAAa,QAAA,EAAmB;AACtE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,aAAa,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAC,CAAA;AAAA,EAC3D,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAeO,SAAS,WAAA,CAAY,QAAgB,SAAA,EAA4B;AACtE,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,eAAA,CAAgB,MAAM,CAAA,CAAE,IAAI,SAAS,CAAA;AAAA,EAClD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;;;ACtCO,IAAM,sBAAA,GAAyB;AAuB/B,SAAS,QAAA,CACd,OAAA,EACA,QAAA,GAA6B,QAAA,EAC7B,OAAA,EACM;AACN,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,MAAM,OAAA,GAAU,QAAQ,IAAA,EAAK;AAC7B,EAAA,IAAI,CAAC,OAAA,EAAS;AACd,EAAA,MAAM,SAAA,GAAY,SAAS,SAAA,IAAa,sBAAA;AACxC,EAAA,MAAA,CAAO,aAAA;AAAA,IACL,IAAI,YAA8B,SAAA,EAAW;AAAA,MAC3C,MAAA,EAAQ,EAAE,OAAA,EAAS,OAAA,EAAS,QAAA;AAAS,KACtC;AAAA,GACH;AACF;;;ACrDO,IAAM,6BAAA,GAAgC;AAStC,SAAS,qBAAA,CAAsB,MAAA,EAAgB,MAAA,EAAgB,QAAA,EAA0B;AAC9F,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAM,IAAI,QAAQ,CAAA,CAAA;AACxC;AAUO,SAAS,aAAA,CAAc,KAAa,OAAA,EAAuB;AAChE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,YAAA,CAAa,QAAQ,GAAA,EAAK,MAAA,CAAO,KAAK,KAAA,CAAM,OAAO,CAAC,CAAC,CAAA;AAAA,EAC9D,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AASO,SAAS,cAAc,GAAA,EAA4B;AACxD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AACjC,IAAA,OAAO,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF","file":"index.utils.js","sourcesContent":["// Utility functions for hazo_ui component library\n// Provides className merging utility (cn function)\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class string\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Sanitizes a user/admin-supplied href against a scheme allowlist to prevent\n * `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an\n * `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but\n * not protocol-relative `//…`). Returns `undefined` for anything else, so the\n * anchor renders with no href rather than an active malicious one.\n * @param url - Candidate href (may be undefined)\n * @returns The original url if allowed, otherwise undefined\n */\nexport function safeHref(url?: string): string | undefined {\n if (!url) return undefined;\n const trimmed = url.trim();\n const lower = trimmed.toLowerCase();\n if (\n lower.startsWith(\"http://\") ||\n lower.startsWith(\"https://\") ||\n lower.startsWith(\"mailto:\")\n ) {\n return trimmed;\n }\n // Same-origin absolute path, but not protocol-relative \"//host\".\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) {\n return trimmed;\n }\n return undefined;\n}\n\n","// remembered_state.ts — SSR-safe localStorage read/write/URL-param helpers\n// backing `useRememberedState` (src/hooks/use_remembered_state.ts). Pure,\n// framework-free functions (no React) so they're independently testable and\n// usable outside the hook. No \"use client\" directive here — this module is\n// re-exported from both the main client bundle (`hazo_ui`) and the pure\n// server-safe subpath (`hazo_ui/utils`), same dual-export pattern as `cn`.\n\n/**\n * Reads and JSON-parses the remembered value stored under `key`. SSR-safe —\n * returns `null` on the server, when nothing is stored, or when the stored\n * value is corrupt/unparsable.\n *\n * @param key - localStorage key to read.\n *\n * @example\n * const seed = readRememberedState<MySeed>(\"myapp:tool:cgt\");\n */\nexport function readRememberedState<T>(key: string): T | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(key);\n return raw ? (JSON.parse(raw) as T) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * JSON-stringifies and writes `snapshot` under `key`. SSR-safe — no-ops on\n * the server. Quota / private-mode errors are swallowed; losing a memory\n * write is non-fatal.\n *\n * @param key - localStorage key to write.\n * @param snapshot - Serialisable value to remember.\n *\n * @example\n * writeRememberedState(\"myapp:tool:cgt\", { proceeds: 1000 });\n */\nexport function writeRememberedState<T>(key: string, snapshot: T): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, JSON.stringify(snapshot));\n } catch {\n /* quota / private-mode — non-fatal */\n }\n}\n\n/**\n * True when `paramName` is present in the given search string (e.g.\n * `window.location.search`). Generic form of the \"an explicit shared-link /\n * saved-record query param always wins over remembered state\" precedence\n * check — callers supply the param name (e.g. `\"restore\"`); this module\n * never hardcodes one.\n *\n * @param search - A URL search string, e.g. `window.location.search`.\n * @param paramName - The query param to look for.\n *\n * @example\n * hasUrlParam(\"?restore=abc123\", \"restore\"); // true\n */\nexport function hasUrlParam(search: string, paramName: string): boolean {\n try {\n return new URLSearchParams(search).has(paramName);\n } catch {\n return false;\n }\n}\n","// announce.ts — SSR-safe screen-reader announcement bus. Dispatches a\n// `window` CustomEvent that `LiveRegion` (../components/live_region/)\n// listens for. Pure, framework-free (no React) so it's independently\n// testable and usable outside a component. No \"use client\" directive here\n// — this module is re-exported from both the main client bundle\n// (`hazo_ui`) and the pure server-safe subpath (`hazo_ui/utils`), same\n// dual-export pattern as `cn` and the `remembered_state` helpers.\n//\n// Ported from Kinstripe's `src/lib/a11y/announce.ts`, which hardcoded the\n// event name to `\"kinstripe:announce\"`. Here the event name is caller-\n// configurable (`options.eventName`) so multiple apps — or multiple\n// `LiveRegion` instances on the same page — don't collide on a single\n// hardcoded channel; it defaults to `DEFAULT_ANNOUNCE_EVENT` for\n// single-app usage.\n\nexport type AnnouncePriority = \"polite\" | \"assertive\";\n\nexport interface LiveAnnouncement {\n message: string;\n priority: AnnouncePriority;\n}\n\nexport interface AnnounceOptions {\n /** Custom-event name the paired `LiveRegion` listens on. Must match the `eventName` it was mounted with. Default `DEFAULT_ANNOUNCE_EVENT`. */\n eventName?: string;\n}\n\n/** Default `window` CustomEvent name used when `announce()` / `LiveRegion` are not given an explicit `eventName`. */\nexport const DEFAULT_ANNOUNCE_EVENT = \"hazo_ui:announce\";\n\n/**\n * Dispatch a screen-reader announcement via a `window` CustomEvent. Safe to\n * call from anywhere in the client tree — no-ops during SSR or before a\n * `LiveRegion` has mounted (fire-and-forget; there's no queue/replay).\n *\n * Use `\"polite\"` (default) for ambient updates (\"Sarah joined the canvas\",\n * \"2 new notifications\"). Use `\"assertive\"` only for things the user must\n * hear immediately (errors that interrupt a task).\n *\n * @param message - Text to announce. Empty / whitespace-only messages are dropped (no dispatch).\n * @param priority - `\"polite\"` (default) or `\"assertive\"`.\n * @param options.eventName - Custom-event name to dispatch on. Default `DEFAULT_ANNOUNCE_EVENT`.\n *\n * @example\n * announce(\"2 new notifications\");\n * announce(\"Save failed — check your connection\", \"assertive\");\n *\n * @example\n * // namespaced channel — must match the eventName the LiveRegion was mounted with\n * announce(\"Saved\", \"polite\", { eventName: \"myapp:announce\" });\n */\nexport function announce(\n message: string,\n priority: AnnouncePriority = \"polite\",\n options?: AnnounceOptions,\n): void {\n if (typeof window === \"undefined\") return;\n const trimmed = message.trim();\n if (!trimmed) return;\n const eventName = options?.eventName ?? DEFAULT_ANNOUNCE_EVENT;\n window.dispatchEvent(\n new CustomEvent<LiveAnnouncement>(eventName, {\n detail: { message: trimmed, priority },\n }),\n );\n}\n","// scroll_restore.ts — SSR-safe localStorage read/write helpers backing\n// `useScrollRestore` (../hooks/use_scroll_restore.ts). Pure, framework-free\n// functions (no React) so they're independently testable and usable\n// outside the hook — same split as `remembered_state.ts` /\n// `useRememberedState`. No \"use client\" directive here.\n//\n// Ported from Kinstripe's `src/lib/hooks/useScrollRestore.ts`, which\n// hardcoded the key prefix to `\"kinstripe.scrollPos\"`. Here the prefix is a\n// parameter (`buildScrollRestoreKey`) / hook option, so the package stays\n// app-agnostic.\n\n/** Default localStorage key prefix used when no `prefix` option is given. */\nexport const DEFAULT_SCROLL_RESTORE_PREFIX = \"hazo_ui:scrollPos\";\n\n/**\n * Composes the localStorage key from a prefix, tree id, and view name.\n *\n * @example\n * buildScrollRestoreKey(\"hazo_ui:scrollPos\", \"tree-42\", \"canvas\");\n * // -> \"hazo_ui:scrollPos.tree-42.canvas\"\n */\nexport function buildScrollRestoreKey(prefix: string, treeId: string, viewName: string): string {\n return `${prefix}.${treeId}.${viewName}`;\n}\n\n/**\n * Persists a scroll position (rounded to the nearest pixel) under `key`.\n * SSR-safe — no-ops on the server. Quota / private-mode errors are\n * swallowed; a failed write is non-fatal.\n *\n * @param key - localStorage key (see `buildScrollRestoreKey`).\n * @param scrollY - Scroll offset in pixels.\n */\nexport function saveScrollPos(key: string, scrollY: number): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, String(Math.round(scrollY)));\n } catch {\n /* quota exceeded — silently drop */\n }\n}\n\n/**\n * Reads back the scroll position stored under `key`. SSR-safe — returns\n * `null` on the server, when nothing is stored, or when the stored value\n * isn't a finite number.\n *\n * @param key - localStorage key (see `buildScrollRestoreKey`).\n */\nexport function loadScrollPos(key: string): number | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return null;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Set of UI components for common interaction elements in a SaaS app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
46
46
|
"hazo_core": "^1.2.1",
|
|
47
47
|
"hazo_state": "^0.1.2",
|
|
48
|
-
"hazo_theme": "^1.0.
|
|
48
|
+
"hazo_theme": "^1.0.1"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@dnd-kit/core": "^6.1.0",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"@types/react": "^18.3.3",
|
|
116
116
|
"@types/react-dom": "^18.3.0",
|
|
117
117
|
"hazo_core": "^1.2.1",
|
|
118
|
-
"hazo_theme": "^1.0.
|
|
118
|
+
"hazo_theme": "^1.0.1",
|
|
119
119
|
"postcss": "^8.4.49",
|
|
120
120
|
"react": "^18.3.1",
|
|
121
121
|
"react-dom": "^18.3.1",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ClassValue } from 'clsx';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Merges class names using clsx and tailwind-merge
|
|
5
|
-
* @param inputs - Class values to merge
|
|
6
|
-
* @returns Merged class string
|
|
7
|
-
*/
|
|
8
|
-
declare function cn(...inputs: ClassValue[]): string;
|
|
9
|
-
/**
|
|
10
|
-
* Sanitizes a user/admin-supplied href against a scheme allowlist to prevent
|
|
11
|
-
* `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an
|
|
12
|
-
* `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but
|
|
13
|
-
* not protocol-relative `//…`). Returns `undefined` for anything else, so the
|
|
14
|
-
* anchor renders with no href rather than an active malicious one.
|
|
15
|
-
* @param url - Candidate href (may be undefined)
|
|
16
|
-
* @returns The original url if allowed, otherwise undefined
|
|
17
|
-
*/
|
|
18
|
-
declare function safeHref(url?: string): string | undefined;
|
|
19
|
-
|
|
20
|
-
export { cn as c, safeHref as s };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ClassValue } from 'clsx';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Merges class names using clsx and tailwind-merge
|
|
5
|
-
* @param inputs - Class values to merge
|
|
6
|
-
* @returns Merged class string
|
|
7
|
-
*/
|
|
8
|
-
declare function cn(...inputs: ClassValue[]): string;
|
|
9
|
-
/**
|
|
10
|
-
* Sanitizes a user/admin-supplied href against a scheme allowlist to prevent
|
|
11
|
-
* `javascript:`/`data:`/`vbscript:` URI XSS when the value is rendered into an
|
|
12
|
-
* `<a href>`. Allows http(s), mailto, and same-origin absolute paths (`/…` but
|
|
13
|
-
* not protocol-relative `//…`). Returns `undefined` for anything else, so the
|
|
14
|
-
* anchor renders with no href rather than an active malicious one.
|
|
15
|
-
* @param url - Candidate href (may be undefined)
|
|
16
|
-
* @returns The original url if allowed, otherwise undefined
|
|
17
|
-
*/
|
|
18
|
-
declare function safeHref(url?: string): string | undefined;
|
|
19
|
-
|
|
20
|
-
export { cn as c, safeHref as s };
|