@salt-ds/styles 0.0.0-snapshot-20260109210611 → 0.0.0-snapshot-20260112210403
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/useClassNameInjection.js +53 -22
- package/dist-cjs/useClassNameInjection.js.map +1 -1
- package/dist-es/index.js +1 -1
- package/dist-es/useClassNameInjection.js +54 -23
- package/dist-es/useClassNameInjection.js.map +1 -1
- package/dist-types/useClassNameInjection.d.ts +25 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
# @salt-ds/styles
|
|
2
2
|
|
|
3
|
-
## 0.0.0-snapshot-
|
|
3
|
+
## 0.0.0-snapshot-20260112210403
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- b639bf8: This feature is in-development, for exploration and feedback only.
|
|
8
|
+
|
|
9
|
+
Introduce a context‑driven API for injecting CSS class names into Salt components based on their props, and optionally stripping implementation‑only props before forwarding.
|
|
10
|
+
|
|
11
|
+
_Status_
|
|
12
|
+
Non‑breaking for existing Salt consumers
|
|
13
|
+
Experimental and incomplete — interfaces and behavior may change without notice
|
|
14
|
+
|
|
15
|
+
**Note to JPM employees**
|
|
16
|
+
Use only in non‑production codebases, or with prior permission from the Salt engineering team
|
|
17
|
+
What’s included
|
|
18
|
+
|
|
19
|
+
- `ClassNameInjectionProvider` — supplies a registry of class injectors via React context
|
|
20
|
+
- `useClassNameInjection(component, props)`
|
|
21
|
+
- computes additional classes via registered injectors
|
|
22
|
+
- merges them with any className provided at the call site
|
|
23
|
+
- removes internal/derived props (declared by each injector) before forwarding
|
|
24
|
+
- `registerClassInjector(registry, component, keys, injector)` — registers per‑component injection rules
|
|
25
|
+
|
|
26
|
+
_Documentation_
|
|
27
|
+
Full documentation will follow once the API is stabilized; for now, consider this API private and subject to change.
|
|
8
28
|
|
|
9
29
|
## 0.2.1
|
|
10
30
|
|
package/dist-cjs/index.js
CHANGED
|
@@ -14,5 +14,5 @@ exports.useInsertionPoint = InsertionPointProvider.useInsertionPoint;
|
|
|
14
14
|
exports.useComponentCssInjection = useStyleInjection.useComponentCssInjection;
|
|
15
15
|
exports.ClassNameInjectionProvider = useClassNameInjection.ClassNameInjectionProvider;
|
|
16
16
|
exports.registerClassInjector = useClassNameInjection.registerClassInjector;
|
|
17
|
-
exports.
|
|
17
|
+
exports.useClassNameInjection = useClassNameInjection.useClassNameInjection;
|
|
18
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,41 +4,72 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var clsx = require('clsx');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const EMPTY_REGISTRY = /* @__PURE__ */ new Map();
|
|
8
|
+
const InjectionContext = React.createContext(EMPTY_REGISTRY);
|
|
9
|
+
let hasWarnedExperimentalOnce = false;
|
|
8
10
|
function ClassNameInjectionProvider({
|
|
9
11
|
children,
|
|
10
12
|
value
|
|
11
13
|
}) {
|
|
12
|
-
const registry = React.useMemo(() => value ??
|
|
14
|
+
const registry = React.useMemo(() => value ?? EMPTY_REGISTRY, [value]);
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
if (!hasWarnedExperimentalOnce && process.env.NODE_ENV !== "production") {
|
|
17
|
+
console.warn(
|
|
18
|
+
"Salt ClassNameInjectionProvider is experimental and subject to change. JPM users: only recommended in non-production environments or with prior permission from the Salt team."
|
|
19
|
+
);
|
|
20
|
+
hasWarnedExperimentalOnce = true;
|
|
21
|
+
}
|
|
22
|
+
}, []);
|
|
13
23
|
return /* @__PURE__ */ jsxRuntime.jsx(InjectionContext.Provider, { value: registry, children });
|
|
14
24
|
}
|
|
15
|
-
function
|
|
16
|
-
const { className: classNameProp, ...restProps } = props;
|
|
25
|
+
function useClassNameInjection(component, props) {
|
|
17
26
|
const registry = React.useContext(InjectionContext);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const entries = registry.get(component) ?? [];
|
|
28
|
+
const deps = React.useMemo(
|
|
29
|
+
() => entries.length ? entries.flatMap((e) => e.keys.map((k) => props[k])) : [],
|
|
30
|
+
[entries, props]
|
|
31
|
+
);
|
|
32
|
+
const injected = React.useMemo(() => {
|
|
33
|
+
const { className: _ignore, ...restProps } = props;
|
|
34
|
+
if (!entries.length) return [];
|
|
35
|
+
return entries.map((e) => e.fn(restProps)).filter((v) => v != null);
|
|
36
|
+
}, [entries, deps, props]);
|
|
37
|
+
const className = React.useMemo(
|
|
38
|
+
() => clsx.clsx(props.className, injected) || void 0,
|
|
39
|
+
[props, injected]
|
|
40
|
+
);
|
|
41
|
+
const cleanProps = React.useMemo(() => {
|
|
42
|
+
const { className: _ignore, ...restProps } = props;
|
|
43
|
+
if (!entries.length) {
|
|
44
|
+
return restProps;
|
|
31
45
|
}
|
|
32
|
-
|
|
46
|
+
const copy = { ...restProps };
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
for (const key of entry.keys) {
|
|
49
|
+
if (Object.hasOwn(copy, key)) {
|
|
50
|
+
delete copy[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return copy;
|
|
55
|
+
}, [entries, deps, props]);
|
|
33
56
|
return { className, props: cleanProps };
|
|
34
57
|
}
|
|
35
58
|
function registerClassInjector(registry, component, keys, injector) {
|
|
36
|
-
registry
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
if (!registry.has(component)) {
|
|
60
|
+
registry.set(component, []);
|
|
61
|
+
}
|
|
62
|
+
const wrapped = (props) => {
|
|
63
|
+
const picked = Object.fromEntries(keys.map((k) => [k, props[k]]));
|
|
64
|
+
return injector(picked);
|
|
65
|
+
};
|
|
66
|
+
registry.get(component).push({
|
|
67
|
+
fn: wrapped,
|
|
68
|
+
keys
|
|
69
|
+
});
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
exports.ClassNameInjectionProvider = ClassNameInjectionProvider;
|
|
42
73
|
exports.registerClassInjector = registerClassInjector;
|
|
43
|
-
exports.
|
|
74
|
+
exports.useClassNameInjection = useClassNameInjection;
|
|
44
75
|
//# sourceMappingURL=useClassNameInjection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClassNameInjection.js","sources":["../src/useClassNameInjection.tsx"],"sourcesContent":["import { clsx } from \"clsx\";\nimport { createContext, useContext, useMemo } from \"react\";\n\nexport type ClassNameInjector<Props, Keys extends keyof Props> = (\n props: Pick<Props, Keys>,\n) => string | undefined;\n\ninterface ClassNameInjectorEntry
|
|
1
|
+
{"version":3,"file":"useClassNameInjection.js","sources":["../src/useClassNameInjection.tsx"],"sourcesContent":["import { clsx } from \"clsx\";\nimport { createContext, useContext, useEffect, useMemo } from \"react\";\n\n/**\n * Extensible map of supported components → their props.\n * @salt-ds/core must augment this with the components that support the API,\n * using the same key string that the component passes to useClassNameInjection,\n * e.g., \"saltButton\": ButtonProps.\n */\nexport type ComponentPropMap = {};\n\n/** Component key type: only keys declared in ComponentPropMap are allowed. */\ntype SupportedComponent = keyof ComponentPropMap extends never\n ? string\n : keyof ComponentPropMap;\n\nexport type ClassNameInjector<Props, Keys extends keyof Props> = (\n props: Pick<Props, Keys>,\n) => string | undefined;\n\ninterface ClassNameInjectorEntry {\n fn: (props: unknown) => string | undefined;\n keys: string[];\n}\n\nexport type ClassNameInjectionRegistry = Map<\n SupportedComponent,\n ClassNameInjectorEntry[]\n>;\n\nconst EMPTY_REGISTRY: ClassNameInjectionRegistry = new Map();\nconst InjectionContext =\n createContext<ClassNameInjectionRegistry>(EMPTY_REGISTRY);\n\nexport type ClassNameInjectionProviderProps = {\n children: React.ReactNode;\n value?: ClassNameInjectionRegistry;\n};\n\nlet hasWarnedExperimentalOnce = false;\n\nexport function ClassNameInjectionProvider({\n children,\n value,\n}: ClassNameInjectionProviderProps) {\n const registry = useMemo(() => value ?? EMPTY_REGISTRY, [value]);\n\n useEffect(() => {\n if (!hasWarnedExperimentalOnce && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Salt ClassNameInjectionProvider is experimental and subject to change. JPM users: only recommended in non-production environments or with prior permission from the Salt team.\",\n );\n hasWarnedExperimentalOnce = true;\n }\n }, []);\n\n return (\n <InjectionContext.Provider value={registry}>\n {children}\n </InjectionContext.Provider>\n );\n}\n\ntype PropsWithClassName = { className?: string } & Record<string, any>;\n\n/**\n * Return the className created by the registry and a props object with injector keys stripped.\n * Only components declared in ComponentPropMap can call this at compile time.\n */\nexport function useClassNameInjection<Props extends PropsWithClassName>(\n component: SupportedComponent,\n props: Props,\n): { className: string | undefined; props: Omit<Props, \"className\"> } {\n const registry = useContext(InjectionContext);\n const entries = registry.get(component) ?? [];\n\n const deps = useMemo(\n () =>\n entries.length\n ? entries.flatMap((e) => e.keys.map((k) => (props as any)[k]))\n : [],\n [entries, props],\n );\n\n // Compute injected classes provided through ClassNameInjectionRegistry\n const injected = useMemo(() => {\n const { className: _ignore, ...restProps } = props as any;\n if (!entries.length) return [];\n return entries\n .map((e) => e.fn(restProps))\n .filter((v): v is string => v != null);\n }, [entries, deps, props]);\n\n // Merge original className with injected classes\n const className = useMemo(\n () => clsx((props as any).className, injected) || undefined,\n [props, injected],\n );\n\n // Create a cleaned props object by stripping keys used by injectors, to avoid DOM errors with unknown attributes\n const cleanProps = useMemo(() => {\n const { className: _ignore, ...restProps } = props as any;\n if (!entries.length) {\n return restProps as Omit<Props, \"className\">;\n }\n const copy = { ...restProps } as Omit<Props, \"className\">;\n for (const entry of entries) {\n for (const key of entry.keys) {\n if (Object.hasOwn(copy, key)) {\n delete (copy as any)[key];\n }\n }\n }\n return copy;\n }, [entries, deps, props]);\n\n return { className, props: cleanProps };\n}\n\n/**\n * Register a class injector for a supported component name.\n * - Keys must be valid string keys for that component’s props (as declared in ComponentPropMap).\n * - Injector receives only the declared keys (Pick<PropsOf<C>, Keys>).\n */\nexport function registerClassInjector<\n Props extends Record<string, any>,\n Keys extends Extract<keyof Props, string>,\n>(\n registry: ClassNameInjectionRegistry,\n component: SupportedComponent,\n keys: Keys[],\n injector: ClassNameInjector<Props, Keys>,\n) {\n if (!registry.has(component)) {\n registry.set(component, []);\n }\n\n const wrapped = (props: Record<string, any>) => {\n const picked = Object.fromEntries(keys.map((k) => [k, props[k]])) as Pick<\n Props,\n Keys\n >;\n return injector(picked);\n };\n\n registry.get(component)!.push({\n fn: wrapped as (props: unknown) => string | undefined,\n keys: keys as string[],\n });\n}\n"],"names":["createContext","useMemo","useEffect","useContext","clsx"],"mappings":"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"}
|
package/dist-es/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { StyleInjectionProvider, useStyleInjection } from './style-injection-provider/index.js';
|
|
2
2
|
export { InsertionPointProvider, useInsertionPoint } from './use-style-injection/InsertionPointProvider.js';
|
|
3
3
|
export { useComponentCssInjection } from './use-style-injection/useStyleInjection.js';
|
|
4
|
-
export { ClassNameInjectionProvider, registerClassInjector,
|
|
4
|
+
export { ClassNameInjectionProvider, registerClassInjector, useClassNameInjection } from './useClassNameInjection.js';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,40 +1,71 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { createContext, useMemo, useContext } from 'react';
|
|
3
|
+
import { createContext, useMemo, useEffect, useContext } from 'react';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const EMPTY_REGISTRY = /* @__PURE__ */ new Map();
|
|
6
|
+
const InjectionContext = createContext(EMPTY_REGISTRY);
|
|
7
|
+
let hasWarnedExperimentalOnce = false;
|
|
6
8
|
function ClassNameInjectionProvider({
|
|
7
9
|
children,
|
|
8
10
|
value
|
|
9
11
|
}) {
|
|
10
|
-
const registry = useMemo(() => value ??
|
|
12
|
+
const registry = useMemo(() => value ?? EMPTY_REGISTRY, [value]);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!hasWarnedExperimentalOnce && process.env.NODE_ENV !== "production") {
|
|
15
|
+
console.warn(
|
|
16
|
+
"Salt ClassNameInjectionProvider is experimental and subject to change. JPM users: only recommended in non-production environments or with prior permission from the Salt team."
|
|
17
|
+
);
|
|
18
|
+
hasWarnedExperimentalOnce = true;
|
|
19
|
+
}
|
|
20
|
+
}, []);
|
|
11
21
|
return /* @__PURE__ */ jsx(InjectionContext.Provider, { value: registry, children });
|
|
12
22
|
}
|
|
13
|
-
function
|
|
14
|
-
const { className: classNameProp, ...restProps } = props;
|
|
23
|
+
function useClassNameInjection(component, props) {
|
|
15
24
|
const registry = useContext(InjectionContext);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const entries = registry.get(component) ?? [];
|
|
26
|
+
const deps = useMemo(
|
|
27
|
+
() => entries.length ? entries.flatMap((e) => e.keys.map((k) => props[k])) : [],
|
|
28
|
+
[entries, props]
|
|
29
|
+
);
|
|
30
|
+
const injected = useMemo(() => {
|
|
31
|
+
const { className: _ignore, ...restProps } = props;
|
|
32
|
+
if (!entries.length) return [];
|
|
33
|
+
return entries.map((e) => e.fn(restProps)).filter((v) => v != null);
|
|
34
|
+
}, [entries, deps, props]);
|
|
35
|
+
const className = useMemo(
|
|
36
|
+
() => clsx(props.className, injected) || void 0,
|
|
37
|
+
[props, injected]
|
|
38
|
+
);
|
|
39
|
+
const cleanProps = useMemo(() => {
|
|
40
|
+
const { className: _ignore, ...restProps } = props;
|
|
41
|
+
if (!entries.length) {
|
|
42
|
+
return restProps;
|
|
29
43
|
}
|
|
30
|
-
|
|
44
|
+
const copy = { ...restProps };
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
for (const key of entry.keys) {
|
|
47
|
+
if (Object.hasOwn(copy, key)) {
|
|
48
|
+
delete copy[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return copy;
|
|
53
|
+
}, [entries, deps, props]);
|
|
31
54
|
return { className, props: cleanProps };
|
|
32
55
|
}
|
|
33
56
|
function registerClassInjector(registry, component, keys, injector) {
|
|
34
|
-
registry
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
if (!registry.has(component)) {
|
|
58
|
+
registry.set(component, []);
|
|
59
|
+
}
|
|
60
|
+
const wrapped = (props) => {
|
|
61
|
+
const picked = Object.fromEntries(keys.map((k) => [k, props[k]]));
|
|
62
|
+
return injector(picked);
|
|
63
|
+
};
|
|
64
|
+
registry.get(component).push({
|
|
65
|
+
fn: wrapped,
|
|
66
|
+
keys
|
|
67
|
+
});
|
|
37
68
|
}
|
|
38
69
|
|
|
39
|
-
export { ClassNameInjectionProvider, registerClassInjector,
|
|
70
|
+
export { ClassNameInjectionProvider, registerClassInjector, useClassNameInjection };
|
|
40
71
|
//# sourceMappingURL=useClassNameInjection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClassNameInjection.js","sources":["../src/useClassNameInjection.tsx"],"sourcesContent":["import { clsx } from \"clsx\";\nimport { createContext, useContext, useMemo } from \"react\";\n\nexport type ClassNameInjector<Props, Keys extends keyof Props> = (\n props: Pick<Props, Keys>,\n) => string | undefined;\n\ninterface ClassNameInjectorEntry
|
|
1
|
+
{"version":3,"file":"useClassNameInjection.js","sources":["../src/useClassNameInjection.tsx"],"sourcesContent":["import { clsx } from \"clsx\";\nimport { createContext, useContext, useEffect, useMemo } from \"react\";\n\n/**\n * Extensible map of supported components → their props.\n * @salt-ds/core must augment this with the components that support the API,\n * using the same key string that the component passes to useClassNameInjection,\n * e.g., \"saltButton\": ButtonProps.\n */\nexport type ComponentPropMap = {};\n\n/** Component key type: only keys declared in ComponentPropMap are allowed. */\ntype SupportedComponent = keyof ComponentPropMap extends never\n ? string\n : keyof ComponentPropMap;\n\nexport type ClassNameInjector<Props, Keys extends keyof Props> = (\n props: Pick<Props, Keys>,\n) => string | undefined;\n\ninterface ClassNameInjectorEntry {\n fn: (props: unknown) => string | undefined;\n keys: string[];\n}\n\nexport type ClassNameInjectionRegistry = Map<\n SupportedComponent,\n ClassNameInjectorEntry[]\n>;\n\nconst EMPTY_REGISTRY: ClassNameInjectionRegistry = new Map();\nconst InjectionContext =\n createContext<ClassNameInjectionRegistry>(EMPTY_REGISTRY);\n\nexport type ClassNameInjectionProviderProps = {\n children: React.ReactNode;\n value?: ClassNameInjectionRegistry;\n};\n\nlet hasWarnedExperimentalOnce = false;\n\nexport function ClassNameInjectionProvider({\n children,\n value,\n}: ClassNameInjectionProviderProps) {\n const registry = useMemo(() => value ?? EMPTY_REGISTRY, [value]);\n\n useEffect(() => {\n if (!hasWarnedExperimentalOnce && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"Salt ClassNameInjectionProvider is experimental and subject to change. JPM users: only recommended in non-production environments or with prior permission from the Salt team.\",\n );\n hasWarnedExperimentalOnce = true;\n }\n }, []);\n\n return (\n <InjectionContext.Provider value={registry}>\n {children}\n </InjectionContext.Provider>\n );\n}\n\ntype PropsWithClassName = { className?: string } & Record<string, any>;\n\n/**\n * Return the className created by the registry and a props object with injector keys stripped.\n * Only components declared in ComponentPropMap can call this at compile time.\n */\nexport function useClassNameInjection<Props extends PropsWithClassName>(\n component: SupportedComponent,\n props: Props,\n): { className: string | undefined; props: Omit<Props, \"className\"> } {\n const registry = useContext(InjectionContext);\n const entries = registry.get(component) ?? [];\n\n const deps = useMemo(\n () =>\n entries.length\n ? entries.flatMap((e) => e.keys.map((k) => (props as any)[k]))\n : [],\n [entries, props],\n );\n\n // Compute injected classes provided through ClassNameInjectionRegistry\n const injected = useMemo(() => {\n const { className: _ignore, ...restProps } = props as any;\n if (!entries.length) return [];\n return entries\n .map((e) => e.fn(restProps))\n .filter((v): v is string => v != null);\n }, [entries, deps, props]);\n\n // Merge original className with injected classes\n const className = useMemo(\n () => clsx((props as any).className, injected) || undefined,\n [props, injected],\n );\n\n // Create a cleaned props object by stripping keys used by injectors, to avoid DOM errors with unknown attributes\n const cleanProps = useMemo(() => {\n const { className: _ignore, ...restProps } = props as any;\n if (!entries.length) {\n return restProps as Omit<Props, \"className\">;\n }\n const copy = { ...restProps } as Omit<Props, \"className\">;\n for (const entry of entries) {\n for (const key of entry.keys) {\n if (Object.hasOwn(copy, key)) {\n delete (copy as any)[key];\n }\n }\n }\n return copy;\n }, [entries, deps, props]);\n\n return { className, props: cleanProps };\n}\n\n/**\n * Register a class injector for a supported component name.\n * - Keys must be valid string keys for that component’s props (as declared in ComponentPropMap).\n * - Injector receives only the declared keys (Pick<PropsOf<C>, Keys>).\n */\nexport function registerClassInjector<\n Props extends Record<string, any>,\n Keys extends Extract<keyof Props, string>,\n>(\n registry: ClassNameInjectionRegistry,\n component: SupportedComponent,\n keys: Keys[],\n injector: ClassNameInjector<Props, Keys>,\n) {\n if (!registry.has(component)) {\n registry.set(component, []);\n }\n\n const wrapped = (props: Record<string, any>) => {\n const picked = Object.fromEntries(keys.map((k) => [k, props[k]])) as Pick<\n Props,\n Keys\n >;\n return injector(picked);\n };\n\n registry.get(component)!.push({\n fn: wrapped as (props: unknown) => string | undefined,\n keys: keys as string[],\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extensible map of supported components → their props.
|
|
3
|
+
* @salt-ds/core must augment this with the components that support the API,
|
|
4
|
+
* using the same key string that the component passes to useClassNameInjection,
|
|
5
|
+
* e.g., "saltButton": ButtonProps.
|
|
6
|
+
*/
|
|
7
|
+
export type ComponentPropMap = {};
|
|
8
|
+
/** Component key type: only keys declared in ComponentPropMap are allowed. */
|
|
9
|
+
type SupportedComponent = keyof ComponentPropMap extends never ? string : keyof ComponentPropMap;
|
|
1
10
|
export type ClassNameInjector<Props, Keys extends keyof Props> = (props: Pick<Props, Keys>) => string | undefined;
|
|
2
|
-
interface ClassNameInjectorEntry
|
|
3
|
-
fn: (props:
|
|
4
|
-
keys:
|
|
11
|
+
interface ClassNameInjectorEntry {
|
|
12
|
+
fn: (props: unknown) => string | undefined;
|
|
13
|
+
keys: string[];
|
|
5
14
|
}
|
|
6
|
-
export type ClassNameInjectionRegistry =
|
|
15
|
+
export type ClassNameInjectionRegistry = Map<SupportedComponent, ClassNameInjectorEntry[]>;
|
|
7
16
|
export type ClassNameInjectionProviderProps = {
|
|
8
17
|
children: React.ReactNode;
|
|
9
18
|
value?: ClassNameInjectionRegistry;
|
|
@@ -12,9 +21,18 @@ export declare function ClassNameInjectionProvider({ children, value, }: ClassNa
|
|
|
12
21
|
type PropsWithClassName = {
|
|
13
22
|
className?: string;
|
|
14
23
|
} & Record<string, any>;
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Return the className created by the registry and a props object with injector keys stripped.
|
|
26
|
+
* Only components declared in ComponentPropMap can call this at compile time.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useClassNameInjection<Props extends PropsWithClassName>(component: SupportedComponent, props: Props): {
|
|
29
|
+
className: string | undefined;
|
|
17
30
|
props: Omit<Props, "className">;
|
|
18
31
|
};
|
|
19
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Register a class injector for a supported component name.
|
|
34
|
+
* - Keys must be valid string keys for that component’s props (as declared in ComponentPropMap).
|
|
35
|
+
* - Injector receives only the declared keys (Pick<PropsOf<C>, Keys>).
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerClassInjector<Props extends Record<string, any>, Keys extends Extract<keyof Props, string>>(registry: ClassNameInjectionRegistry, component: SupportedComponent, keys: Keys[], injector: ClassNameInjector<Props, Keys>): void;
|
|
20
38
|
export {};
|