praxis-kit 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/_shared/diagnostics.d.ts +312 -0
  4. package/dist/_shared/diagnostics.js +360 -0
  5. package/dist/build-runtime-CJ_nQEaZ.js +5065 -0
  6. package/dist/codemod/index.d.ts +2 -0
  7. package/dist/codemod/index.js +176520 -0
  8. package/dist/contract/index.d.ts +677 -0
  9. package/dist/contract/index.js +341 -0
  10. package/dist/eslint/index.d.ts +90 -0
  11. package/dist/eslint/index.js +1047 -0
  12. package/dist/guards/index.d.ts +78 -0
  13. package/dist/guards/index.js +118 -0
  14. package/dist/html/index.d.ts +151 -0
  15. package/dist/html/index.js +1244 -0
  16. package/dist/index-BIBd_iPD.d.ts +951 -0
  17. package/dist/lit/index.d.ts +862 -0
  18. package/dist/lit/index.js +4893 -0
  19. package/dist/preact/index.d.ts +796 -0
  20. package/dist/preact/index.js +5043 -0
  21. package/dist/react/index.d.ts +28 -0
  22. package/dist/react/index.js +205 -0
  23. package/dist/react/legacy.d.ts +29 -0
  24. package/dist/react/legacy.js +80 -0
  25. package/dist/solid/index.d.ts +728 -0
  26. package/dist/solid/index.js +4821 -0
  27. package/dist/svelte/Polymorphic.svelte +190 -0
  28. package/dist/svelte/_polymorphic-runtime.d.ts +102 -0
  29. package/dist/svelte/_polymorphic-runtime.js +371 -0
  30. package/dist/svelte/index.d.ts +994 -0
  31. package/dist/svelte/index.js +4482 -0
  32. package/dist/tailwind/index.d.ts +197 -0
  33. package/dist/tailwind/index.js +767 -0
  34. package/dist/tailwind/safelist.css +20 -0
  35. package/dist/ts-plugin/index.cjs +166 -0
  36. package/dist/ts-plugin/index.d.cts +9 -0
  37. package/dist/utils/index.d.ts +19 -0
  38. package/dist/utils/index.js +21 -0
  39. package/dist/vite-plugin/index.d.ts +200 -0
  40. package/dist/vite-plugin/index.js +2106 -0
  41. package/dist/vue/index.d.ts +729 -0
  42. package/dist/vue/index.js +4945 -0
  43. package/dist/web/index.d.ts +832 -0
  44. package/dist/web/index.js +4868 -0
  45. package/package.json +258 -0
@@ -0,0 +1,20 @@
1
+ /*
2
+ * `createTailwindPipeline` assembles display-mode classes (flex, inline-flex,
3
+ * grid, hidden, etc.) at runtime from boolean props — they never appear as a
4
+ * literal string anywhere in scanned source, so Tailwind v4's content
5
+ * detection never generates their CSS on its own.
6
+ *
7
+ * `@source inline(...)` forces Tailwind to generate these utilities
8
+ * regardless of what its scanner finds. This file is copied verbatim into
9
+ * `praxis-kit`'s published dist (see packages/kit/scripts/postbuild.mjs) and
10
+ * exposed as the `praxis-kit/tailwind.css` subpath — `@praxis-kit/tailwind`
11
+ * itself is a private, unpublished workspace package. Import it alongside
12
+ * `tailwindcss` in your app's main CSS entry point:
13
+ *
14
+ * @import "tailwindcss";
15
+ * @import "praxis-kit/tailwind.css";
16
+ *
17
+ * Keep this list in sync with `layout-keys.ts` — enforced by
18
+ * `tailwind-safelist.test.ts`.
19
+ */
20
+ @source inline("flex inline-flex grid inline-grid block inline-block inline hidden contents flow-root list-item table inline-table table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row-group table-row");
@@ -0,0 +1,166 @@
1
+ //#region ../../plugins/typescript/src/ast.ts
2
+ function getObjectProperty(ts, obj, key) {
3
+ for (const prop of obj.properties) {
4
+ if (!ts.isPropertyAssignment(prop)) continue;
5
+ const name = prop.name;
6
+ if (ts.isIdentifier(name) && name.text === key) return prop;
7
+ if (ts.isStringLiteral(name) && name.text === key) return prop;
8
+ }
9
+ }
10
+ function asObjectLiteralExpression(ts, node) {
11
+ if (!node) return void 0;
12
+ return ts.isObjectLiteralExpression(node) ? node : void 0;
13
+ }
14
+ function asArrayLiteralExpression(ts, node) {
15
+ if (!node) return void 0;
16
+ return ts.isArrayLiteralExpression(node) ? node : void 0;
17
+ }
18
+ function asNumericValue(ts, node) {
19
+ if (!node) return void 0;
20
+ if (ts.isNumericLiteral(node)) return Number(node.text);
21
+ if (ts.isPrefixUnaryExpression(node) && (node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken) && ts.isNumericLiteral(node.operand)) {
22
+ const val = Number(node.operand.text);
23
+ return node.operator === ts.SyntaxKind.MinusToken ? -val : val;
24
+ }
25
+ }
26
+ function isFactoryCall(ts, node, names) {
27
+ const { expression } = node;
28
+ if (ts.isIdentifier(expression)) return names.has(expression.text);
29
+ if (ts.isPropertyAccessExpression(expression)) return names.has(expression.name.text);
30
+ return false;
31
+ }
32
+ function getFirstObjectArg(ts, node) {
33
+ const [first] = node.arguments;
34
+ if (!first) return void 0;
35
+ return ts.isObjectLiteralExpression(first) ? first : void 0;
36
+ }
37
+ //#endregion
38
+ //#region ../../plugins/typescript/src/diagnostics/walk-enforcement.ts
39
+ /**
40
+ * Walks a source file and calls `cb` for every factory call that has a
41
+ * statically-resolvable `enforcement` object literal argument.
42
+ */
43
+ function walkEnforcement(ts, sourceFile, calleeNames, cb) {
44
+ function visit(node) {
45
+ if (ts.isCallExpression(node) && isFactoryCall(ts, node, calleeNames)) {
46
+ const arg = getFirstObjectArg(ts, node);
47
+ if (arg) {
48
+ const enfProp = getObjectProperty(ts, arg, "enforcement");
49
+ if (enfProp) {
50
+ const enf = asObjectLiteralExpression(ts, enfProp.initializer);
51
+ if (enf) cb(node, enf);
52
+ }
53
+ }
54
+ }
55
+ ts.forEachChild(node, visit);
56
+ }
57
+ visit(sourceFile);
58
+ }
59
+ //#endregion
60
+ //#region ../../plugins/typescript/src/diagnostics/no-enforcement-without-strict.ts
61
+ const MISSING_STRICT_CODE = 90001;
62
+ function checkNoEnforcementWithoutStrict(ts, sourceFile, calleeNames) {
63
+ const diagnostics = [];
64
+ walkEnforcement(ts, sourceFile, calleeNames, (_node, enf) => {
65
+ if (!(getObjectProperty(ts, enf, "strict") !== void 0)) for (const field of ["children", "aria"]) {
66
+ const fieldProp = getObjectProperty(ts, enf, field);
67
+ if (!fieldProp) continue;
68
+ if (field === "children") {
69
+ const arr = asArrayLiteralExpression(ts, fieldProp.initializer);
70
+ if (!arr || arr.elements.length === 0) continue;
71
+ }
72
+ const anchor = fieldProp.name;
73
+ diagnostics.push({
74
+ file: sourceFile,
75
+ start: anchor.getStart(sourceFile),
76
+ length: anchor.getWidth(sourceFile),
77
+ category: ts.DiagnosticCategory.Warning,
78
+ code: MISSING_STRICT_CODE,
79
+ messageText: `enforcement.${field} is defined but enforcement.strict is not explicitly set. Adapter defaults vary — declare strict explicitly so the behavior is clear at the call site.`,
80
+ source: "praxis-kit/ts-plugin"
81
+ });
82
+ break;
83
+ }
84
+ });
85
+ return diagnostics;
86
+ }
87
+ //#endregion
88
+ //#region ../../plugins/typescript/src/diagnostics/valid-cardinality.ts
89
+ const NEGATIVE_MIN_CODE = 90002;
90
+ const NEGATIVE_MAX_CODE = 90003;
91
+ const MAX_LESS_THAN_MIN_CODE = 90004;
92
+ function checkValidCardinality(ts, sourceFile, calleeNames) {
93
+ const diagnostics = [];
94
+ walkEnforcement(ts, sourceFile, calleeNames, (_, enf) => {
95
+ const childrenProp = getObjectProperty(ts, enf, "children");
96
+ if (!childrenProp) return;
97
+ const arr = asArrayLiteralExpression(ts, childrenProp.initializer);
98
+ if (!arr) return;
99
+ for (const element of arr.elements) {
100
+ if (!ts.isObjectLiteralExpression(element)) continue;
101
+ const cardProp = getObjectProperty(ts, element, "cardinality");
102
+ if (!cardProp) continue;
103
+ const card = asObjectLiteralExpression(ts, cardProp.initializer);
104
+ if (!card) continue;
105
+ const minProp = getObjectProperty(ts, card, "min");
106
+ const maxProp = getObjectProperty(ts, card, "max");
107
+ const min = minProp ? asNumericValue(ts, minProp.initializer) : void 0;
108
+ const max = maxProp ? asNumericValue(ts, maxProp.initializer) : void 0;
109
+ if (min !== void 0 && min < 0) diagnostics.push({
110
+ file: sourceFile,
111
+ start: minProp.getStart(sourceFile),
112
+ length: minProp.getWidth(sourceFile),
113
+ category: ts.DiagnosticCategory.Error,
114
+ code: NEGATIVE_MIN_CODE,
115
+ messageText: `cardinality.min must be >= 0 (got ${min}).`,
116
+ source: "praxis-kit/ts-plugin"
117
+ });
118
+ if (max !== void 0 && max < 0) diagnostics.push({
119
+ file: sourceFile,
120
+ start: maxProp.getStart(sourceFile),
121
+ length: maxProp.getWidth(sourceFile),
122
+ category: ts.DiagnosticCategory.Error,
123
+ code: NEGATIVE_MAX_CODE,
124
+ messageText: `cardinality.max must be >= 0 (got ${max}).`,
125
+ source: "praxis-kit/ts-plugin"
126
+ });
127
+ if (min !== void 0 && max !== void 0 && min >= 0 && max > 0 && max < min) diagnostics.push({
128
+ file: sourceFile,
129
+ start: cardProp.getStart(sourceFile),
130
+ length: cardProp.getWidth(sourceFile),
131
+ category: ts.DiagnosticCategory.Error,
132
+ code: MAX_LESS_THAN_MIN_CODE,
133
+ messageText: `cardinality.max (${max}) must be >= cardinality.min (${min}). This rule can never be satisfied.`,
134
+ source: "praxis-kit/ts-plugin"
135
+ });
136
+ }
137
+ });
138
+ return diagnostics;
139
+ }
140
+ //#endregion
141
+ //#region ../../plugins/typescript/src/index.ts
142
+ const DEFAULT_CALLEE_NAMES = ["createContractComponent"];
143
+ function init(modules) {
144
+ const ts = modules.typescript;
145
+ function create(info) {
146
+ const calleeNames = new Set(info.config.calleeNames ?? DEFAULT_CALLEE_NAMES);
147
+ const proxy = Object.create(null);
148
+ for (const k of Object.keys(info.languageService)) {
149
+ const x = info.languageService[k];
150
+ Reflect.set(proxy, k, typeof x === "function" ? x.bind(info.languageService) : x);
151
+ }
152
+ proxy.getSemanticDiagnostics = (fileName) => {
153
+ const existing = info.languageService.getSemanticDiagnostics(fileName);
154
+ const program = info.languageService.getProgram();
155
+ if (!program) return existing;
156
+ const sourceFile = program.getSourceFile(fileName);
157
+ if (!sourceFile) return existing;
158
+ const extra = [...checkNoEnforcementWithoutStrict(ts, sourceFile, calleeNames), ...checkValidCardinality(ts, sourceFile, calleeNames)];
159
+ return [...existing, ...extra];
160
+ };
161
+ return proxy;
162
+ }
163
+ return { create };
164
+ }
165
+ module.exports = init;
166
+ //#endregion
@@ -0,0 +1,9 @@
1
+ import tsserverlibrary from "typescript/lib/tsserverlibrary";
2
+ //#region ../../plugins/typescript/src/index.d.ts
3
+ type TS = typeof tsserverlibrary;
4
+ declare function init(modules: {
5
+ typescript: TS;
6
+ }): {
7
+ create: (info: tsserverlibrary.server.PluginCreateInfo) => tsserverlibrary.LanguageService;
8
+ };
9
+ export = init;
@@ -0,0 +1,19 @@
1
+ import "clsx";
2
+ import "../_shared/diagnostics.js";
3
+ import "type-fest";
4
+ //#region ../../lib/primitive/src/types/function-types.d.ts
5
+ /** A single-argument pure function — the shape `memoize()` wraps. */
6
+ type UnaryFn<T, R> = (arg: T) => R;
7
+ //#endregion
8
+ //#region ../../lib/primitive/src/utils/memoize.d.ts
9
+ /**
10
+ * Caches the result of a pure, single-argument function in an unbounded `Map`.
11
+ *
12
+ * Best suited to functions that are expensive relative to a cache lookup and
13
+ * repeatedly called with a relatively small, bounded set of inputs (for
14
+ * example, a finite vocabulary of tokens or strings). Avoid memoizing
15
+ * unbounded or attacker-controlled input spaces, as the cache will grow
16
+ * without limit. Use `LRUCache` instead when cache size should remain bounded.
17
+ */
18
+ export declare function memoize<T, R>(fn: UnaryFn<T, R>): UnaryFn<T, R>;
19
+ //#endregion
@@ -0,0 +1,21 @@
1
+ //#region ../../lib/primitive/src/utils/memoize.ts
2
+ /**
3
+ * Caches the result of a pure, single-argument function in an unbounded `Map`.
4
+ *
5
+ * Best suited to functions that are expensive relative to a cache lookup and
6
+ * repeatedly called with a relatively small, bounded set of inputs (for
7
+ * example, a finite vocabulary of tokens or strings). Avoid memoizing
8
+ * unbounded or attacker-controlled input spaces, as the cache will grow
9
+ * without limit. Use `LRUCache` instead when cache size should remain bounded.
10
+ */
11
+ function memoize(fn) {
12
+ const cache = /* @__PURE__ */ new Map();
13
+ return (arg) => {
14
+ if (cache.has(arg)) return cache.get(arg);
15
+ const result = fn(arg);
16
+ cache.set(arg, result);
17
+ return result;
18
+ };
19
+ }
20
+ //#endregion
21
+ export { memoize };
@@ -0,0 +1,200 @@
1
+ import ts from "typescript";
2
+ import "clsx";
3
+ import { DiagnosticCode } from "../_shared/diagnostics.js";
4
+ import { Plugin } from "vite";
5
+ import "type-fest";
6
+ //#region ../../lib/foundation/src/string-map.d.ts
7
+ /**
8
+ * A string-keyed object whose values are of type `T`.
9
+ */
10
+ type StringMap<T = unknown> = Record<string, T>;
11
+ //#endregion
12
+ //#region ../../lib/primitive/src/types/aria-rule/severity.d.ts
13
+ type Severity = 'error' | 'warning' | (string & {});
14
+ //#endregion
15
+ //#region ../../plugins/vite/src/types/plugin-options.d.ts
16
+ /** Options accepted by contractPlugin() and analyze(). */
17
+ type PluginOptions = {
18
+ /**
19
+ * Factory function names to look for.
20
+ * @default ['createPolymorphicComponent', 'createContractComponent']
21
+ */
22
+ calleeNames?: string[];
23
+ /**
24
+ * Severity of cardinality violations in Vite build output.
25
+ * Matches the Severity vocabulary used by ValidationViolation.
26
+ * @default 'warning'
27
+ */
28
+ severity?: Severity;
29
+ };
30
+ //#endregion
31
+ //#region ../../plugins/vite/src/design-tokens.d.ts
32
+ type ComponentTokens = {
33
+ base: string[];
34
+ variantClasses: string[];
35
+ compoundClasses: string[];
36
+ tagClasses: string[];
37
+ };
38
+ type DesignTokenManifest = {
39
+ components: StringMap<ComponentTokens>;
40
+ allClasses: string[];
41
+ };
42
+ type DesignTokensOptions = {
43
+ /**
44
+ * Path where the manifest JSON is written, relative to the Vite project root.
45
+ * @default 'praxis-tokens.json'
46
+ */
47
+ outFile?: string;
48
+ } & Pick<PluginOptions, 'calleeNames'>;
49
+ /**
50
+ * Vite plugin that collects every statically-declared class string from
51
+ * `createContractComponent` factory calls and writes a design token manifest
52
+ * to a JSON file on each build.
53
+ *
54
+ * The manifest contains per-component class lists and a flat `allClasses`
55
+ * union that can be used as a Tailwind content source to prevent purging of
56
+ * variant classes.
57
+ *
58
+ * @example
59
+ * // vite.config.ts
60
+ * import { designTokensPlugin } from 'praxis-kit/vite-plugin'
61
+ * export default { plugins: [designTokensPlugin({ outFile: 'praxis-tokens.json' })] }
62
+ *
63
+ * @example
64
+ * // tailwind.config.js
65
+ * export default { content: ['./src/**', './praxis-tokens.json'] }
66
+ */
67
+ export declare function designTokensPlugin(options?: DesignTokensOptions): Plugin;
68
+ //#endregion
69
+ //#region ../../plugins/vite/src/index.d.ts
70
+ /**
71
+ * Vite plugin that performs static enforcement.children cardinality checks at
72
+ * build time for components created with createContractComponent.
73
+ *
74
+ * **Single-file scope:** Components defined and used in the same `.tsx` / `.jsx`
75
+ * file are validated during `transform`. JSX children containing expressions
76
+ * (`{...}`) are skipped — their count is unknowable at compile time.
77
+ *
78
+ * **Cross-file scope:** Components imported from other files are validated in
79
+ * `buildEnd` once the full constraint registry is populated. Only named imports
80
+ * whose source file was also transformed by this plugin are checked.
81
+ *
82
+ * @example
83
+ * // vite.config.ts
84
+ * import { contractPlugin } from 'praxis-kit/vite-plugin'
85
+ * export default { plugins: [contractPlugin()] }
86
+ */
87
+ export declare function contractPlugin(options?: PluginOptions): Plugin;
88
+ /**
89
+ * Vite plugin that removes dead `styling.compounds` entries from factory calls
90
+ * at build time, reducing bundle size and eliminating unreachable CVA compound
91
+ * checks at runtime.
92
+ *
93
+ * A compound entry is dead when any of its conditions reference a variant key
94
+ * that does not exist in `styling.variants`, or a value that is not valid for
95
+ * that key. Only entries whose conditions are fully static (string/array
96
+ * literals) are pruned — dynamic conditions are left unchanged.
97
+ *
98
+ * Place before `contractPlugin` so the pruned source is what gets analyzed.
99
+ *
100
+ * @example
101
+ * // vite.config.ts
102
+ * import { compoundPrunePlugin, contractPlugin } from 'praxis-kit/vite-plugin'
103
+ * export default { plugins: [compoundPrunePlugin(), contractPlugin()] }
104
+ */
105
+ export declare function compoundPrunePlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
106
+ /**
107
+ * Vite plugin that pre-computes variant class strings at build time and injects
108
+ * them as a static `precomputedClasses` map into each factory call's `styling`
109
+ * object.
110
+ *
111
+ * At runtime, `VariantClassResolver` checks this map before calling CVA — a
112
+ * plain object lookup replaces a CVA invocation + LRU cache write for every
113
+ * statically-known combination. Only combinations that appear in the map are
114
+ * accelerated; invalid or dynamic variant values fall through to the existing
115
+ * compute path unchanged.
116
+ *
117
+ * Injection is skipped when:
118
+ * - `styling.variants` is absent or contains non-literal values
119
+ * - `styling.compounds` contains non-literal conditions or classes
120
+ * - The number of valid combinations exceeds 512
121
+ *
122
+ * Place after `compoundPrunePlugin` so the injected map reflects the live
123
+ * compound set.
124
+ *
125
+ * @example
126
+ * // vite.config.ts
127
+ * import { compoundPrunePlugin, classExtractPlugin, contractPlugin } from 'praxis-kit/vite-plugin'
128
+ * export default { plugins: [compoundPrunePlugin(), classExtractPlugin(), contractPlugin()] }
129
+ */
130
+ export declare function classExtractPlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
131
+ /**
132
+ * Vite plugin that transforms `asChild` JSX usage sites to the render-prop form
133
+ * at build time, eliminating the Slot/cloneElement/mergeProps runtime path.
134
+ *
135
+ * Only transforms sites where the transform is semantically safe:
136
+ * - Exactly one static child element
137
+ * - Child has no `className`, `style`, or event handler props
138
+ *
139
+ * Complex asChild patterns (conflicting props, dynamic children, Slottable
140
+ * siblings) are left unchanged and handled by the runtime Slot path.
141
+ *
142
+ * Place before `contractPlugin` so cardinality analysis sees the transformed
143
+ * source.
144
+ *
145
+ * @example
146
+ * // vite.config.ts
147
+ * import { slotTransformPlugin, contractPlugin } from 'praxis-kit/vite-plugin'
148
+ * export default { plugins: [slotTransformPlugin(), contractPlugin()] }
149
+ */
150
+ export declare function slotTransformPlugin(): Plugin;
151
+ /**
152
+ * Vite plugin that replaces polymorphic component usage sites with direct
153
+ * element creation at build time, eliminating the runtime render pipeline for
154
+ * statically-analyzable usages.
155
+ *
156
+ * **Requires classExtractPlugin to run first** so that `precomputedClasses` is
157
+ * present in the factory call before this plugin reads it. Place after
158
+ * `classExtractPlugin` in the plugins array.
159
+ *
160
+ * A usage site is inlined when:
161
+ * - The component is defined in the same file or imported from a file already
162
+ * transformed by this plugin, with `precomputedClasses` injected
163
+ * - No `as`, `asChild`, `render`, or spread attributes at the site
164
+ * - All variant props are static string literals
165
+ * - `className` is absent or a static string literal
166
+ * - The factory config has no `defaults` or `enforcement`
167
+ *
168
+ * Cross-file inlining degrades gracefully when the definition file has not yet
169
+ * been transformed (dev mode ordering, barrel re-exports, aliased imports).
170
+ *
171
+ * @example
172
+ * // vite.config.ts
173
+ * import { classExtractPlugin, staticCompositionPlugin } from 'praxis-kit/vite-plugin'
174
+ * export default { plugins: [classExtractPlugin(), staticCompositionPlugin()] }
175
+ */
176
+ export declare function staticCompositionPlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
177
+ /**
178
+ * Convenience plugin bundle that applies all three build-time rendering
179
+ * optimisations in the correct dependency order:
180
+ *
181
+ * 1. `slotTransformPlugin` — rewrites `asChild` → render-prop form,
182
+ * eliminating `cloneElement` for static sites
183
+ * 2. `classExtractPlugin` — injects `precomputedClasses` into factory
184
+ * calls for O(1) variant class resolution
185
+ * 3. `staticCompositionPlugin` — inlines same-file static usages into direct
186
+ * element creation, bypassing the runtime
187
+ * pipeline entirely
188
+ *
189
+ * Place before `contractPlugin` so cardinality analysis sees the transformed
190
+ * source. Especially effective for SSR builds where each component renders
191
+ * exactly once per request and eliminates per-render pipeline overhead.
192
+ *
193
+ * @example
194
+ * // vite.config.ts
195
+ * import { ssrOptimizePlugin, contractPlugin } from 'praxis-kit/vite-plugin'
196
+ * export default { plugins: [ssrOptimizePlugin(), contractPlugin()] }
197
+ */
198
+ export declare function ssrOptimizePlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin[];
199
+ //#endregion
200
+ export type { ComponentTokens, DesignTokenManifest, DesignTokensOptions, PluginOptions };