@lynx-js/tailwind-preset-canary 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @lynx-js/tailwind-preset
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add support for Lynx UI plugin system with configurable options. ([#1363](https://github.com/lynx-family/lynx-stack/pull/1363))
8
+
9
+ - Introduced `lynxUIPlugins` option in `createLynxPreset`, allowing userland opt-in to Lynx UI specific plugins.
10
+
11
+ - Implemented `uiVariants` plugin as the first UI plugin, supporting `ui-*` variant prefixes (e.g. `ui-checked`, `ui-open`) with customizable mappings.
12
+
3
13
  ## 0.1.2
4
14
 
5
15
  ### Patch Changes
package/README.md CHANGED
@@ -1,13 +1,21 @@
1
1
  # Lynx Tailwind Preset (V3)
2
2
 
3
- A [Tailwind V3](https://v3.tailwindcss.com/) CSS preset specifically designed for Lynx, ensuring that only CSS properties supported by Lynx are available as Tailwind utilities.
3
+ A [Tailwind CSS v3](https://v3.tailwindcss.com/) preset for the Lynx ecosystem.
4
+
5
+ This preset is not a 1:1 port of Tailwind's core. Instead, it provides a **Lynx-native Tailwind experience** tailored for the platform's rendering model and ecosystem needs by:
6
+
7
+ - Including only CSS utilities that Lynx supports
8
+
9
+ - Reimagining certain utilities to align with Lynx's styling constraints and runtime behavior
10
+
11
+ - Enabling ecosystem extensions such as UI state variants, animation presets, and design token integration
4
12
 
5
13
  > **⚠️ Experimental**\
6
14
  > This preset is currently in experimental stage as we are still exploring the best possible DX to write Tailwind upon Lynx. We welcome and encourage contributions from the community to help shape its future development. Your feedback, bug reports, and pull requests are invaluable in making this preset more robust and feature-complete.
7
15
 
8
16
  ## Basic Usage
9
17
 
10
- ```typescript
18
+ ```ts
11
19
  // tailwind.config.ts
12
20
  import preset from '@lynx-js/tailwind-preset';
13
21
 
@@ -17,7 +25,7 @@ export default {
17
25
  };
18
26
  ```
19
27
 
20
- ```typescript
28
+ ```ts
21
29
  // tailwind.config.ts
22
30
  import { createLynxPreset } from '@lynx-js/tailwind-preset';
23
31
 
@@ -31,80 +39,7 @@ export default {
31
39
  };
32
40
  ```
33
41
 
34
- ## Structure
35
-
36
- - `src/lynx.ts`: Main preset configuration that reverse-engineered [Tailwind's core plugins](https://github.com/tailwindlabs/tailwindcss/blob/v3/src/corePlugins.js).
37
- - `src/plugins/lynx/`: Custom plugins as replacement when per-class customization are needed.
38
- - `src/__tests__/`: Test files to ensure correct utility generation
39
-
40
- ## Contributing
41
-
42
- ### Getting Started
43
-
44
- ```bash
45
- # Install dependencies
46
- pnpm install
47
-
48
- # Run tests
49
- pnpm test
50
- ```
51
-
52
- ### Adding New Utilities
53
-
54
- #### 1. Check if the CSS property is supported by Lynx
55
-
56
- This can be verified in three ways:
57
-
58
- 1. [`@lynx-js/css-defines`](https://www.npmjs.com/package/@lynx-js/css-defines), this is the most accurate list of CSS properties supported by Lynx, directly generated from the source of Lynx internal definitions and released along with each Lynx releases.
59
- 2. `csstype.d.ts` in `@lynx-js/types`, this is used as the types of inline styles (e.g. `<view style>`) but this is currently maintained manually.
60
- 3. Lynx's runtime behaviors.
61
-
62
- #### 2. Add/Remove it from the preset
63
-
64
- ##### 2.1 If enabling a Tailwind core plugin
65
-
66
- - Add it to `DEFAULT_CORE_PLUGINS` array in `src/core.ts`
67
-
68
- ##### 2.2 If it requires custom handling
69
-
70
- ###### Lynx core plugins
71
-
72
- For plugins that are considered **part of the Lynx core preset** — either implementing Tailwind core utilities or providing Lynx-specific functionality
73
-
74
- - Create a new plugin in `src/plugins/lynx/`
75
- - Use shared helpers from `src/helpers.ts` and `src/plugin-utils/` where applicable
76
- - Export it from `src/plugins/lynx/index.ts`
77
- - Register it in `src/plugins/lynx/plugin-registry.ts`\
78
- _(This ensures a stable sorting order for the core set)_
79
- - It will be automatically included in `src/core.ts` via the Lynx plugin registry
80
-
81
- ###### Non-core / Custom plugin categories
82
-
83
- For plugins that are **not part of the Lynx core preset** — such as experimental features, app-specific utilities, or standalone plugin groups:
84
-
85
- - These plugins require explicit registration and ordering
86
- - Create a new category folder under `src/plugins/` (e.g. `src/plugins/experimental/`)
87
- - Add the plugin in that folder, and optionally extract shared logic into `plugin-utils/`
88
- - Export the plugin from `src/plugins/{category}/index.ts`
89
- - Define a `plugin-registry.ts` in the same folder to control plugin order
90
- - Import the registry in `src/core.ts` and include it in the appropriate position
91
-
92
- #### 3. Adding Tests
93
-
94
- We test by using Tailwind CLI to build `src/__tests__/` demo project with our preset, then extracting all properties used in the generated utilities and verify if all used properties are allowed according to `@lynx-js/types`.
95
-
96
- To test new Tailwind utilities:
97
-
98
- 1. Modify `testClasses` in `src/__tests__/test-content.tsx`
99
- 2. Modify `supportedProperties` or `allowedUnsupportedProperties` in `config.test.ts`
100
- 3. Run tests with `pnpm test` to verify with Vitest.
101
-
102
- To test new plugins:
103
-
104
- 1. Add new test file in `src/__tests__/plugins`. Import `runPlugin` test util function from `src/__tests__/utils/run-plugin.ts`. Mock theme values.
105
- 2. Run tests with `pnpm test` to verify with Vitest.
106
-
107
- ## Integration notes
42
+ ## Integration Notes
108
43
 
109
44
  ### tailwind-merge & rsbuild-plugin-tailwindcss
110
45
 
@@ -128,3 +63,43 @@ export default {
128
63
  ],
129
64
  };
130
65
  ```
66
+
67
+ ## Ecosystem Extensions
68
+
69
+ Beyond core utility coverage, this preset supports ecosystem-level extensions to improve component styling DX and support common Tailwind ecosystem patterns adapted for Lynx.
70
+
71
+ ### Enabling Lynx UI Plugins
72
+
73
+ UI plugins are not enabled by default. You can enable all plugins with:
74
+
75
+ ```ts
76
+ createLynxPreset({
77
+ lynxUIPlugins: true,
78
+ });
79
+ ```
80
+
81
+ Or enable individual plugins with their default options:
82
+
83
+ ```ts
84
+ createLynxPreset({
85
+ lynxUIPlugins: { uiVariants: true },
86
+ });
87
+ ```
88
+
89
+ Or configure each plugin individually — see each plugin's documentation for available options:
90
+
91
+ ```ts
92
+ createLynxPreset({
93
+ lynxUIPlugins: {
94
+ uiVariants: {
95
+ prefixes: {
96
+ ui: ['open', 'checked'],
97
+ },
98
+ },
99
+ },
100
+ });
101
+ ```
102
+
103
+ #### Available Plugins
104
+
105
+ - [uiVariants](https://github.com/lynx-family/lynx-stack/tree/main/packages/third-party/tailwind-preset/docs/plugins/lynx-ui/uiVariants.md) — Class-based variants for expressing component state or structure using `ui-*` prefixes (e.g. `.ui-open:`, `.ui-side-left:`).
package/dist/core.d.ts CHANGED
@@ -1,13 +1,22 @@
1
1
  import { LYNX_PLUGIN_MAP, ORDERED_LYNX_PLUGIN_NAMES } from './plugins/lynx/plugin-registry.js';
2
2
  import type { LynxPluginName } from './plugins/lynx/plugin-types.js';
3
+ import { LYNX_UI_PLUGIN_MAP, ORDERED_LYNX_UI_PLUGIN_NAMES } from './plugins/lynx-ui/plugin-registry.js';
4
+ import type { LynxUIPluginName, LynxUIPluginOptionsMap } from './plugins/lynx-ui/plugin-registry.js';
3
5
  import type { CorePluginsConfig } from './types/tailwind-types.js';
4
6
  export declare const DEFAULT_CORE_PLUGINS: CorePluginsConfig;
5
7
  export type LynxPluginsOption = boolean | LynxPluginName[] | Partial<Record<LynxPluginName, boolean>>;
8
+ export type LynxUIPluginsOption = boolean | LynxUIPluginName[] | Partial<{
9
+ [K in LynxUIPluginName]: boolean | LynxUIPluginOptionsMap[K];
10
+ }>;
6
11
  export declare function toEnabledSet(opt?: LynxPluginsOption): Set<LynxPluginName>;
12
+ export declare function toEnabledLynxUIPluginSet(opt?: LynxUIPluginsOption): Set<LynxUIPluginName>;
13
+ export declare function resolveUIPluginEntries(raw: LynxUIPluginsOption): {
14
+ [K in LynxUIPluginName]: [K, LynxUIPluginOptionsMap[K] | undefined];
15
+ }[LynxUIPluginName][];
7
16
  export declare const getReplaceablePlugins: () => readonly LynxPluginName[];
8
17
  export declare const isPluginReplaceable: (p: string) => p is LynxPluginName;
9
- export type { LynxPluginName };
10
- export { LYNX_PLUGIN_MAP, ORDERED_LYNX_PLUGIN_NAMES };
18
+ export type { LynxPluginName, LynxUIPluginName, LynxUIPluginOptionsMap };
19
+ export { LYNX_PLUGIN_MAP, ORDERED_LYNX_PLUGIN_NAMES, LYNX_UI_PLUGIN_MAP, ORDERED_LYNX_UI_PLUGIN_NAMES, };
11
20
  /** svg-related plugins */
12
21
  /** filter-related plugins, only gradyscale and blur are supported*/
13
22
  /** backdrop-related plugins */
package/dist/helpers.d.ts CHANGED
@@ -1,20 +1,13 @@
1
1
  import { formatBoxShadowValue, parseBoxShadowValue } from 'tailwindcss/lib/util/parseBoxShadowValue.js';
2
2
  import type { ShadowPart } from 'tailwindcss/lib/util/parseBoxShadowValue.js';
3
3
  import type { ThemeKey, ValueTransformer } from 'tailwindcss/lib/util/transformThemeValue.js';
4
- import type { Bound, BoundedPluginCreator, PluginFn, UtilityPluginOptions, UtilityVariations } from './types/plugin-types.js';
5
- import type { Config, Plugin, PluginCreator } from './types/tailwind-types.js';
4
+ import type { Bound, CreatePluginFunction, PluginWithOptions, UtilityPluginOptions, UtilityVariations } from './types/plugin-types.js';
5
+ import type { Plugin, PluginCreator } from './types/tailwind-types.js';
6
+ declare const createPlugin: CreatePluginFunction;
6
7
  /**
7
- * Wraps a Tailwind PluginCreator and auto-binds all function properties of the API.
8
+ * Type guard
8
9
  */
9
- declare function createPlugin(fn: BoundedPluginCreator, cfg?: Partial<Config>): {
10
- handler: PluginCreator;
11
- config?: Partial<Config> | undefined;
12
- };
13
- declare function createPluginWithName(name: string, fn: BoundedPluginCreator, cfg?: Partial<Config>): {
14
- handler: PluginCreator;
15
- config?: Partial<Config> | undefined;
16
- };
17
- export declare const plugin: PluginFn;
10
+ declare function isPluginWithOptions<T = unknown>(plugin: unknown): plugin is PluginWithOptions<T>;
18
11
  /**
19
12
  * Returns a shallow clone of the object where all function values
20
13
  * are bound with `this` set to `undefined`,
@@ -31,8 +24,8 @@ declare function autoBind<T extends object>(obj: T): Bound<T>;
31
24
  * For internal use in Lynx plugin system.
32
25
  */
33
26
  declare function createUtilityPlugin(themeKey: string, utilityVariations?: UtilityVariations, options?: UtilityPluginOptions): PluginCreator;
34
- export { createUtilityPlugin, createPlugin, createPluginWithName, autoBind };
35
- export type { Plugin };
27
+ export { createUtilityPlugin, createPlugin, autoBind, isPluginWithOptions };
28
+ export type { Plugin, PluginWithOptions };
36
29
  export declare const transformThemeValue: (key: ThemeKey) => ValueTransformer;
37
30
  export { parseBoxShadowValue, formatBoxShadowValue };
38
31
  export type { ShadowPart };
package/dist/lynx.cjs CHANGED
@@ -41,28 +41,27 @@ var createUtilityPlugin_js_default = /*#__PURE__*/ __webpack_require__.n(createU
41
41
  require("tailwindcss/lib/util/parseBoxShadowValue.js");
42
42
  const transformThemeValue_js_namespaceObject = require("tailwindcss/lib/util/transformThemeValue.js");
43
43
  var transformThemeValue_js_default = /*#__PURE__*/ __webpack_require__.n(transformThemeValue_js_namespaceObject);
44
- function createPlugin(fn, cfg) {
44
+ function createPluginImpl(fn, cfg) {
45
45
  return {
46
46
  handler: (api)=>fn(autoBind(api)),
47
47
  config: cfg
48
48
  };
49
49
  }
50
- function basePlugin(pluginFn, cfg) {
51
- return {
52
- handler: pluginFn,
53
- config: cfg
50
+ createPluginImpl.withOptions = withOptions;
51
+ const createPlugin = createPluginImpl;
52
+ function withOptions(pluginFn, configFn = ()=>({})) {
53
+ const optionsFunction = function(options) {
54
+ return {
55
+ __options: options,
56
+ handler: (api)=>pluginFn(options)(autoBind(api)),
57
+ config: configFn(options)
58
+ };
54
59
  };
60
+ optionsFunction.__isOptionsFunction = true;
61
+ optionsFunction.__pluginFunction = pluginFn;
62
+ optionsFunction.__configFunction = configFn;
63
+ return optionsFunction;
55
64
  }
56
- function pluginImpl(pluginFn, cfg) {
57
- const wrapped = (api)=>pluginFn(autoBind(api));
58
- return basePlugin(wrapped, cfg);
59
- }
60
- function withOptions(factory, cfgFactory) {
61
- const optionsFn = (options)=>basePlugin((api)=>factory(options)(autoBind(api)), cfgFactory?.(options));
62
- optionsFn.__isOptionsFunction = true;
63
- return optionsFn;
64
- }
65
- pluginImpl.withOptions = withOptions;
66
65
  function autoBind(obj) {
67
66
  return Object.fromEntries(Object.entries(obj).map(([k, v])=>isFunction(v) ? [
68
67
  k,
@@ -929,6 +928,69 @@ const LYNX_PLUGIN_ENTRIES = [
929
928
  const plugin_registry_LYNX_PLUGIN_MAP = Object.fromEntries(LYNX_PLUGIN_ENTRIES);
930
929
  const ORDERED_LYNX_PLUGIN_NAMES = LYNX_PLUGIN_ENTRIES.map(([n])=>n);
931
930
  const plugin_registry_REPLACEABLE_LYNX_PLUGINS = ORDERED_LYNX_PLUGIN_NAMES.filter((n)=>'defaults' !== n);
931
+ const DEFAULT_PREFIXES = {
932
+ ui: [
933
+ 'active',
934
+ 'disabled',
935
+ 'readonly',
936
+ 'checked',
937
+ 'selected',
938
+ 'open',
939
+ 'leaving',
940
+ 'entering',
941
+ 'animating',
942
+ 'busy'
943
+ ],
944
+ 'ui-side': [
945
+ 'left',
946
+ 'right',
947
+ 'top',
948
+ 'bottom'
949
+ ],
950
+ 'ui-align': [
951
+ 'start',
952
+ 'end',
953
+ 'center'
954
+ ]
955
+ };
956
+ const uiVariants = createPlugin.withOptions((options)=>({ matchVariant })=>{
957
+ options = options ?? {};
958
+ const resolvedPrefixes = normalizePrefixes(options?.prefixes);
959
+ const entries = Object.entries(resolvedPrefixes);
960
+ for (const [prefix, states] of entries){
961
+ const stateEntries = Array.isArray(states) ? states.map((k)=>[
962
+ k,
963
+ k
964
+ ]) : Object.entries(states);
965
+ const valueMap = Object.fromEntries(stateEntries);
966
+ matchVariant(prefix, (value, { modifier } = {})=>{
967
+ const mapped = valueMap[value];
968
+ if (!mapped || 'string' != typeof mapped) return '';
969
+ const selector = `&.${prefix}-${mapped}`;
970
+ return modifier && 'string' == typeof modifier ? `${selector}\\/${modifier}` : selector;
971
+ }, {
972
+ values: valueMap
973
+ });
974
+ }
975
+ });
976
+ function normalizePrefixes(input) {
977
+ if ('function' == typeof input) return input(DEFAULT_PREFIXES);
978
+ if (Array.isArray(input)) return Object.fromEntries(input.map((prefix)=>[
979
+ prefix,
980
+ DEFAULT_PREFIXES[prefix] ?? []
981
+ ]));
982
+ return input ?? {
983
+ ui: DEFAULT_PREFIXES.ui
984
+ };
985
+ }
986
+ const LYNX_UI_PLUGIN_ENTRIES = [
987
+ [
988
+ 'uiVariants',
989
+ uiVariants
990
+ ]
991
+ ];
992
+ const LYNX_UI_PLUGIN_MAP = Object.fromEntries(LYNX_UI_PLUGIN_ENTRIES);
993
+ const ORDERED_LYNX_UI_PLUGIN_NAMES = LYNX_UI_PLUGIN_ENTRIES.map(([n])=>n);
932
994
  const DEFAULT_CORE_PLUGINS = [
933
995
  'animation',
934
996
  'aspectRatio',
@@ -997,6 +1059,39 @@ function toEnabledSet(opt = true) {
997
1059
  else if (true === on) set.add(k);
998
1060
  return set;
999
1061
  }
1062
+ function toEnabledLynxUIPluginSet(opt = true) {
1063
+ if (true === opt) return new Set(ORDERED_LYNX_UI_PLUGIN_NAMES);
1064
+ if (false === opt) return new Set();
1065
+ if (Array.isArray(opt)) return new Set(opt);
1066
+ const set = new Set(ORDERED_LYNX_UI_PLUGIN_NAMES);
1067
+ for (const [k, on] of Object.entries(opt))if (false === on) set.delete(k);
1068
+ else if (true === on) set.add(k);
1069
+ return set;
1070
+ }
1071
+ function resolveUIPluginEntries(raw) {
1072
+ if (false === raw) return [];
1073
+ if (true === raw) return ORDERED_LYNX_UI_PLUGIN_NAMES.map((n)=>[
1074
+ n,
1075
+ {}
1076
+ ]);
1077
+ if (Array.isArray(raw)) return ORDERED_LYNX_UI_PLUGIN_NAMES.filter((n)=>raw.includes(n)).map((n)=>[
1078
+ n,
1079
+ {}
1080
+ ]);
1081
+ const out = [];
1082
+ for (const name of ORDERED_LYNX_UI_PLUGIN_NAMES){
1083
+ const val = raw[name];
1084
+ if (false !== val) if (true === val || void 0 === val) out.push([
1085
+ name,
1086
+ {}
1087
+ ]);
1088
+ else out.push([
1089
+ name,
1090
+ val
1091
+ ]);
1092
+ }
1093
+ return out;
1094
+ }
1000
1095
  const lynxTheme = {
1001
1096
  boxShadow: {
1002
1097
  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
@@ -1093,18 +1188,24 @@ const lynxTheme = {
1093
1188
  }
1094
1189
  }
1095
1190
  };
1096
- function createLynxPreset({ lynxPlugins = true, debug = false, theme } = {}) {
1097
- const enabled = toEnabledSet(lynxPlugins);
1191
+ function createLynxPreset({ lynxPlugins = true, lynxUIPlugins = false, debug = false, theme } = {}) {
1192
+ const coreSetEnabled = toEnabledSet(lynxPlugins);
1193
+ const uiSetEnabled = toEnabledLynxUIPluginSet(lynxUIPlugins);
1098
1194
  const defaultPluginName = 'defaults';
1099
1195
  const plugins = [
1100
1196
  plugin_registry_LYNX_PLUGIN_MAP[defaultPluginName]
1101
1197
  ];
1102
1198
  for (const name of ORDERED_LYNX_PLUGIN_NAMES)if ('defaults' !== name) {
1103
- if (enabled.has(name)) {
1199
+ if (coreSetEnabled.has(name)) {
1104
1200
  plugins.push(plugin_registry_LYNX_PLUGIN_MAP[name]);
1105
- if (debug) console.debug(`[Lynx] enabled plugin: ${name}`);
1201
+ if (debug) console.debug(`[Lynx] enabled core plugin: ${name}`);
1106
1202
  }
1107
1203
  }
1204
+ for (const [name, options] of resolveUIPluginEntries(lynxUIPlugins))if (uiSetEnabled.has(name)) {
1205
+ const fn = LYNX_UI_PLUGIN_MAP[name];
1206
+ plugins.push(fn(options));
1207
+ if (debug) console.debug(`[Lynx] enabled UI plugin: ${name}`);
1208
+ }
1108
1209
  return {
1109
1210
  plugins,
1110
1211
  corePlugins: DEFAULT_CORE_PLUGINS,
package/dist/lynx.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { Config } from 'tailwindcss';
2
- import type { LynxPluginName, LynxPluginsOption } from './core.js';
2
+ import type { LynxPluginName, LynxPluginsOption, LynxUIPluginsOption } from './core.js';
3
3
  /**
4
4
  * Should be used with Tailwind v3+ (JIT is enabled by default) and configured with `content`,
5
5
  * otherwise the generated CSS bundle may include unused utilities.
6
6
  */
7
- declare function createLynxPreset({ lynxPlugins, debug, theme, }?: {
7
+ declare function createLynxPreset({ lynxPlugins, lynxUIPlugins, debug, theme, }?: {
8
8
  lynxPlugins?: LynxPluginsOption;
9
+ lynxUIPlugins?: LynxUIPluginsOption;
9
10
  debug?: boolean;
10
11
  theme?: Config['theme'];
11
12
  }): Partial<Config>;
package/dist/lynx.js CHANGED
@@ -1,28 +1,27 @@
1
1
  import createUtilityPlugin from "tailwindcss/lib/util/createUtilityPlugin.js";
2
2
  import "tailwindcss/lib/util/parseBoxShadowValue.js";
3
3
  import transformThemeValue from "tailwindcss/lib/util/transformThemeValue.js";
4
- function createPlugin(fn, cfg) {
4
+ function createPluginImpl(fn, cfg) {
5
5
  return {
6
6
  handler: (api)=>fn(autoBind(api)),
7
7
  config: cfg
8
8
  };
9
9
  }
10
- function basePlugin(pluginFn, cfg) {
11
- return {
12
- handler: pluginFn,
13
- config: cfg
10
+ createPluginImpl.withOptions = withOptions;
11
+ const createPlugin = createPluginImpl;
12
+ function withOptions(pluginFn, configFn = ()=>({})) {
13
+ const optionsFunction = function(options) {
14
+ return {
15
+ __options: options,
16
+ handler: (api)=>pluginFn(options)(autoBind(api)),
17
+ config: configFn(options)
18
+ };
14
19
  };
20
+ optionsFunction.__isOptionsFunction = true;
21
+ optionsFunction.__pluginFunction = pluginFn;
22
+ optionsFunction.__configFunction = configFn;
23
+ return optionsFunction;
15
24
  }
16
- function pluginImpl(pluginFn, cfg) {
17
- const wrapped = (api)=>pluginFn(autoBind(api));
18
- return basePlugin(wrapped, cfg);
19
- }
20
- function withOptions(factory, cfgFactory) {
21
- const optionsFn = (options)=>basePlugin((api)=>factory(options)(autoBind(api)), cfgFactory?.(options));
22
- optionsFn.__isOptionsFunction = true;
23
- return optionsFn;
24
- }
25
- pluginImpl.withOptions = withOptions;
26
25
  function autoBind(obj) {
27
26
  return Object.fromEntries(Object.entries(obj).map(([k, v])=>isFunction(v) ? [
28
27
  k,
@@ -889,6 +888,69 @@ const LYNX_PLUGIN_ENTRIES = [
889
888
  const plugin_registry_LYNX_PLUGIN_MAP = Object.fromEntries(LYNX_PLUGIN_ENTRIES);
890
889
  const ORDERED_LYNX_PLUGIN_NAMES = LYNX_PLUGIN_ENTRIES.map(([n])=>n);
891
890
  const plugin_registry_REPLACEABLE_LYNX_PLUGINS = ORDERED_LYNX_PLUGIN_NAMES.filter((n)=>'defaults' !== n);
891
+ const DEFAULT_PREFIXES = {
892
+ ui: [
893
+ 'active',
894
+ 'disabled',
895
+ 'readonly',
896
+ 'checked',
897
+ 'selected',
898
+ 'open',
899
+ 'leaving',
900
+ 'entering',
901
+ 'animating',
902
+ 'busy'
903
+ ],
904
+ 'ui-side': [
905
+ 'left',
906
+ 'right',
907
+ 'top',
908
+ 'bottom'
909
+ ],
910
+ 'ui-align': [
911
+ 'start',
912
+ 'end',
913
+ 'center'
914
+ ]
915
+ };
916
+ const uiVariants = createPlugin.withOptions((options)=>({ matchVariant })=>{
917
+ options = options ?? {};
918
+ const resolvedPrefixes = normalizePrefixes(options?.prefixes);
919
+ const entries = Object.entries(resolvedPrefixes);
920
+ for (const [prefix, states] of entries){
921
+ const stateEntries = Array.isArray(states) ? states.map((k)=>[
922
+ k,
923
+ k
924
+ ]) : Object.entries(states);
925
+ const valueMap = Object.fromEntries(stateEntries);
926
+ matchVariant(prefix, (value, { modifier } = {})=>{
927
+ const mapped = valueMap[value];
928
+ if (!mapped || 'string' != typeof mapped) return '';
929
+ const selector = `&.${prefix}-${mapped}`;
930
+ return modifier && 'string' == typeof modifier ? `${selector}\\/${modifier}` : selector;
931
+ }, {
932
+ values: valueMap
933
+ });
934
+ }
935
+ });
936
+ function normalizePrefixes(input) {
937
+ if ('function' == typeof input) return input(DEFAULT_PREFIXES);
938
+ if (Array.isArray(input)) return Object.fromEntries(input.map((prefix)=>[
939
+ prefix,
940
+ DEFAULT_PREFIXES[prefix] ?? []
941
+ ]));
942
+ return input ?? {
943
+ ui: DEFAULT_PREFIXES.ui
944
+ };
945
+ }
946
+ const LYNX_UI_PLUGIN_ENTRIES = [
947
+ [
948
+ 'uiVariants',
949
+ uiVariants
950
+ ]
951
+ ];
952
+ const LYNX_UI_PLUGIN_MAP = Object.fromEntries(LYNX_UI_PLUGIN_ENTRIES);
953
+ const ORDERED_LYNX_UI_PLUGIN_NAMES = LYNX_UI_PLUGIN_ENTRIES.map(([n])=>n);
892
954
  const DEFAULT_CORE_PLUGINS = [
893
955
  'animation',
894
956
  'aspectRatio',
@@ -957,6 +1019,39 @@ function toEnabledSet(opt = true) {
957
1019
  else if (true === on) set.add(k);
958
1020
  return set;
959
1021
  }
1022
+ function toEnabledLynxUIPluginSet(opt = true) {
1023
+ if (true === opt) return new Set(ORDERED_LYNX_UI_PLUGIN_NAMES);
1024
+ if (false === opt) return new Set();
1025
+ if (Array.isArray(opt)) return new Set(opt);
1026
+ const set = new Set(ORDERED_LYNX_UI_PLUGIN_NAMES);
1027
+ for (const [k, on] of Object.entries(opt))if (false === on) set.delete(k);
1028
+ else if (true === on) set.add(k);
1029
+ return set;
1030
+ }
1031
+ function resolveUIPluginEntries(raw) {
1032
+ if (false === raw) return [];
1033
+ if (true === raw) return ORDERED_LYNX_UI_PLUGIN_NAMES.map((n)=>[
1034
+ n,
1035
+ {}
1036
+ ]);
1037
+ if (Array.isArray(raw)) return ORDERED_LYNX_UI_PLUGIN_NAMES.filter((n)=>raw.includes(n)).map((n)=>[
1038
+ n,
1039
+ {}
1040
+ ]);
1041
+ const out = [];
1042
+ for (const name of ORDERED_LYNX_UI_PLUGIN_NAMES){
1043
+ const val = raw[name];
1044
+ if (false !== val) if (true === val || void 0 === val) out.push([
1045
+ name,
1046
+ {}
1047
+ ]);
1048
+ else out.push([
1049
+ name,
1050
+ val
1051
+ ]);
1052
+ }
1053
+ return out;
1054
+ }
960
1055
  const lynxTheme = {
961
1056
  boxShadow: {
962
1057
  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
@@ -1053,18 +1148,24 @@ const lynxTheme = {
1053
1148
  }
1054
1149
  }
1055
1150
  };
1056
- function createLynxPreset({ lynxPlugins = true, debug = false, theme } = {}) {
1057
- const enabled = toEnabledSet(lynxPlugins);
1151
+ function createLynxPreset({ lynxPlugins = true, lynxUIPlugins = false, debug = false, theme } = {}) {
1152
+ const coreSetEnabled = toEnabledSet(lynxPlugins);
1153
+ const uiSetEnabled = toEnabledLynxUIPluginSet(lynxUIPlugins);
1058
1154
  const defaultPluginName = 'defaults';
1059
1155
  const plugins = [
1060
1156
  plugin_registry_LYNX_PLUGIN_MAP[defaultPluginName]
1061
1157
  ];
1062
1158
  for (const name of ORDERED_LYNX_PLUGIN_NAMES)if ('defaults' !== name) {
1063
- if (enabled.has(name)) {
1159
+ if (coreSetEnabled.has(name)) {
1064
1160
  plugins.push(plugin_registry_LYNX_PLUGIN_MAP[name]);
1065
- if (debug) console.debug(`[Lynx] enabled plugin: ${name}`);
1161
+ if (debug) console.debug(`[Lynx] enabled core plugin: ${name}`);
1066
1162
  }
1067
1163
  }
1164
+ for (const [name, options] of resolveUIPluginEntries(lynxUIPlugins))if (uiSetEnabled.has(name)) {
1165
+ const fn = LYNX_UI_PLUGIN_MAP[name];
1166
+ plugins.push(fn(options));
1167
+ if (debug) console.debug(`[Lynx] enabled UI plugin: ${name}`);
1168
+ }
1068
1169
  return {
1069
1170
  plugins,
1070
1171
  corePlugins: DEFAULT_CORE_PLUGINS,
@@ -0,0 +1 @@
1
+ export { uiVariants } from './uiVariants.js';
@@ -0,0 +1,20 @@
1
+ import type { UIVariantsOptions } from './uiVariants.js';
2
+ import type { PluginWithOptions } from '../../helpers.js';
3
+ type Entry<K extends LynxUIPluginName> = readonly [
4
+ K,
5
+ PluginWithOptions<LynxUIPluginOptionsMap[K]>
6
+ ];
7
+ interface LynxUIPluginOptionsMap {
8
+ uiVariants: UIVariantsOptions;
9
+ }
10
+ type LynxUIPluginName = keyof LynxUIPluginOptionsMap;
11
+ export declare const LYNX_UI_PLUGIN_ENTRIES: readonly [
12
+ Entry<'uiVariants'>
13
+ ];
14
+ type EntryUnion = typeof LYNX_UI_PLUGIN_ENTRIES[number];
15
+ type PluginMap = {
16
+ [K in EntryUnion[0]]: Extract<EntryUnion, readonly [K, any]>[1];
17
+ };
18
+ export declare const LYNX_UI_PLUGIN_MAP: PluginMap;
19
+ export declare const ORDERED_LYNX_UI_PLUGIN_NAMES: readonly LynxUIPluginName[];
20
+ export type { LynxUIPluginName, LynxUIPluginOptionsMap };
@@ -0,0 +1,32 @@
1
+ import type { PluginWithOptions } from '../../helpers.js';
2
+ import type { KeyValuePairOrList } from '../../types/plugin-types.js';
3
+ declare const DEFAULT_PREFIXES: {
4
+ readonly ui: readonly ["active", "disabled", "readonly", "checked", "selected", "open", "leaving", "entering", "animating", "busy"];
5
+ readonly 'ui-side': readonly ["left", "right", "top", "bottom"];
6
+ readonly 'ui-align': readonly ["start", "end", "center"];
7
+ };
8
+ type DefaultPrefixMap = typeof DEFAULT_PREFIXES;
9
+ type PrefixConfig = string[] | Record<string, KeyValuePairOrList> | ((defaults: DefaultPrefixMap) => Record<string, KeyValuePairOrList>);
10
+ interface UIVariantsOptions {
11
+ /**
12
+ * Configures state-based variant prefixes.
13
+ *
14
+ * You can provide:
15
+ * - An array of prefixes to use their default states
16
+ * - Or an object mapping each prefix to an array or map of custom states.
17
+ * - An explicit object of prefix → values (array or map)
18
+ *
19
+ * @example
20
+ * prefixes: ['ui'] // → `ui-checked:*`, `ui-open:*` using default states
21
+ *
22
+ * @example
23
+ * prefixes: {
24
+ * ui: ['checked', 'open'],
25
+ * aria: { expanded: 'expanded', pressed: 'pressed' },
26
+ * }
27
+ */
28
+ prefixes?: PrefixConfig;
29
+ }
30
+ declare const uiVariants: PluginWithOptions<UIVariantsOptions>;
31
+ export type { UIVariantsOptions };
32
+ export { uiVariants };
@@ -1,4 +1,6 @@
1
1
  import type { Config, PluginAPI, PluginCreator, ValueType } from './tailwind-types.js';
2
+ /** A flat structure of possible variant values, either list or key-value form. */
3
+ type KeyValuePairOrList = Record<string, string> | string[] | ReadonlyArray<string>;
2
4
  /** Anything that is legal on the right-hand side of a CSS-in-JS object. */
3
5
  type CSSStatic = string | number | string[] | Record<string, unknown> | null | undefined;
4
6
  /**
@@ -27,24 +29,16 @@ type Bound<T> = {
27
29
  [K in keyof T]: T[K] extends (...args: infer _A) => infer _R ? OmitThisParameter<T[K]> : T[K];
28
30
  };
29
31
  type BoundedPluginCreator = (api: Bound<PluginAPI>) => void;
30
- interface OptionsFn<T> {
31
- (options: T): {
32
- handler: PluginCreator;
33
- config?: Partial<Config>;
34
- };
32
+ interface PluginWithConfig {
33
+ handler: PluginCreator;
34
+ config?: Partial<Config> | undefined;
35
+ }
36
+ interface PluginWithOptions<T> {
37
+ (options?: T): PluginWithConfig;
35
38
  __isOptionsFunction: true;
36
39
  }
37
- interface PluginFn {
38
- (pluginFn: BoundedPluginCreator, cfg?: Partial<Config>): {
39
- handler: PluginCreator;
40
- config?: Partial<Config> | undefined;
41
- };
42
- withOptions<T>(factory: (opts: T) => BoundedPluginCreator, cfgFactory?: (opts: T) => Partial<Config>): {
43
- (opts: T): {
44
- handler: PluginCreator;
45
- config?: Partial<Config> | undefined;
46
- };
47
- __isOptionsFunction: true;
48
- };
40
+ interface CreatePluginFunction {
41
+ (pluginFn: BoundedPluginCreator, cfg?: Partial<Config>): PluginWithConfig;
42
+ withOptions<T>(pluginFn: (options?: T) => BoundedPluginCreator, configFn?: (options?: T) => Partial<Config>): PluginWithOptions<T>;
49
43
  }
50
- export type { CSSStatic, PropertyEntry, UtilityEntry, UtilityGroup, UtilityVariations, UtilityPluginOptions, BoundedPluginCreator, OptionsFn, PluginFn, Bound, };
44
+ export type { CSSStatic, KeyValuePairOrList, PropertyEntry, UtilityEntry, UtilityGroup, UtilityVariations, UtilityPluginOptions, BoundedPluginCreator, Bound, CreatePluginFunction, PluginWithConfig, PluginWithOptions, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynx-js/tailwind-preset-canary",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A tailwindcss preset for ReactLynx",
5
5
  "keywords": [
6
6
  "Lynx",