@wsxjs/wsx-vite-plugin 0.0.30 → 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.
package/dist/index.js CHANGED
@@ -632,125 +632,6 @@ function babelPluginWSXStyle() {
632
632
  };
633
633
  }
634
634
 
635
- // src/babel-plugin-wsx-focus.ts
636
- var tModule3 = __toESM(require("@babel/types"));
637
- var FOCUSABLE_ELEMENTS = /* @__PURE__ */ new Set([
638
- "input",
639
- "textarea",
640
- "select",
641
- "button"
642
- // Also focusable
643
- ]);
644
- function isFocusableElement(tagName, hasContentEditable) {
645
- const lowerTag = tagName.toLowerCase();
646
- return FOCUSABLE_ELEMENTS.has(lowerTag) || hasContentEditable;
647
- }
648
- function extractPropsFromJSXAttributes(attributes) {
649
- const props = {};
650
- for (const attr of attributes) {
651
- if (tModule3.isJSXAttribute(attr) && tModule3.isJSXIdentifier(attr.name)) {
652
- const keyName = attr.name.name;
653
- if (keyName === "id" || keyName === "name" || keyName === "type") {
654
- if (tModule3.isStringLiteral(attr.value)) {
655
- props[keyName] = attr.value.value;
656
- } else if (tModule3.isJSXExpressionContainer(attr.value) && tModule3.isStringLiteral(attr.value.expression)) {
657
- props[keyName] = attr.value.expression.value;
658
- }
659
- }
660
- }
661
- }
662
- return props;
663
- }
664
- function generateStableKey(tagName, componentName, path, props) {
665
- const pathStr = path.join("-");
666
- const lowerTag = tagName.toLowerCase();
667
- if (props.id) {
668
- return `${componentName}-${props.id}`;
669
- }
670
- if (props.name) {
671
- return `${componentName}-${props.name}`;
672
- }
673
- const typeStr = props.type || "text";
674
- return `${componentName}-${lowerTag}-${typeStr}-${pathStr}`;
675
- }
676
- function calculateJSXPath(path) {
677
- const pathArray = [];
678
- let currentPath = path.parentPath;
679
- while (currentPath) {
680
- if (currentPath.isJSXElement()) {
681
- const parent = currentPath.parentPath;
682
- if (parent && parent.isJSXElement()) {
683
- const parentNode = parent.node;
684
- let index = 0;
685
- for (let i = 0; i < parentNode.children.length; i++) {
686
- const child = parentNode.children[i];
687
- if (child === currentPath.node) {
688
- index = i;
689
- break;
690
- }
691
- }
692
- pathArray.unshift(index);
693
- } else if (parent && parent.isReturnStatement()) {
694
- break;
695
- }
696
- } else if (currentPath.isReturnStatement()) {
697
- break;
698
- }
699
- currentPath = currentPath.parentPath;
700
- }
701
- return pathArray.length > 0 ? pathArray : [0];
702
- }
703
- function findComponentName(path) {
704
- let classPath = path;
705
- while (classPath) {
706
- if (classPath.isClassDeclaration()) {
707
- const classNode = classPath.node;
708
- if (classNode.id && tModule3.isIdentifier(classNode.id)) {
709
- return classNode.id.name;
710
- }
711
- break;
712
- }
713
- classPath = classPath.parentPath;
714
- }
715
- return "Component";
716
- }
717
- function babelPluginWSXFocus() {
718
- const t = tModule3;
719
- return {
720
- name: "babel-plugin-wsx-focus",
721
- visitor: {
722
- JSXOpeningElement(path) {
723
- const element = path.node;
724
- if (!t.isJSXIdentifier(element.name)) {
725
- return;
726
- }
727
- const elementName = element.name.name;
728
- const hasKey = element.attributes.some(
729
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "data-wsx-key"
730
- );
731
- if (hasKey) {
732
- return;
733
- }
734
- const props = extractPropsFromJSXAttributes(element.attributes);
735
- const hasContentEditable = element.attributes.some(
736
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && (attr.name.name === "contenteditable" || attr.name.name === "contentEditable")
737
- );
738
- if (!isFocusableElement(elementName, hasContentEditable)) {
739
- return;
740
- }
741
- const componentName = findComponentName(path);
742
- const pathArray = calculateJSXPath(path);
743
- const key = generateStableKey(elementName, componentName, pathArray, props);
744
- const keyAttr = t.jsxAttribute(
745
- t.jsxIdentifier("data-wsx-key"),
746
- t.stringLiteral(key)
747
- );
748
- element.attributes.push(keyAttr);
749
- }
750
- }
751
- };
752
- }
753
-
754
635
  // src/vite-plugin-wsx-babel.ts
755
636
  function getJSXFactoryImportPath(_options) {
756
637
  return "@wsxjs/wsx-core";
@@ -842,9 +723,6 @@ function vitePluginWSXWithBabel(options = {}) {
842
723
  }
843
724
  ]
844
725
  ] : [],
845
- // Focus key generation plugin runs early to add data-wsx-key attributes
846
- // This must run before JSX is transformed to h() calls
847
- babelPluginWSXFocus,
848
726
  // CRITICAL: State decorator transformation must run BEFORE @babel/plugin-proposal-decorators
849
727
  // This allows the plugin to detect @state decorators in their original form and throw errors if needed
850
728
  // The plugin removes @state decorators after processing, so the decorator plugin won't see them
package/dist/index.mjs CHANGED
@@ -596,125 +596,6 @@ function babelPluginWSXStyle() {
596
596
  };
597
597
  }
598
598
 
599
- // src/babel-plugin-wsx-focus.ts
600
- import * as tModule3 from "@babel/types";
601
- var FOCUSABLE_ELEMENTS = /* @__PURE__ */ new Set([
602
- "input",
603
- "textarea",
604
- "select",
605
- "button"
606
- // Also focusable
607
- ]);
608
- function isFocusableElement(tagName, hasContentEditable) {
609
- const lowerTag = tagName.toLowerCase();
610
- return FOCUSABLE_ELEMENTS.has(lowerTag) || hasContentEditable;
611
- }
612
- function extractPropsFromJSXAttributes(attributes) {
613
- const props = {};
614
- for (const attr of attributes) {
615
- if (tModule3.isJSXAttribute(attr) && tModule3.isJSXIdentifier(attr.name)) {
616
- const keyName = attr.name.name;
617
- if (keyName === "id" || keyName === "name" || keyName === "type") {
618
- if (tModule3.isStringLiteral(attr.value)) {
619
- props[keyName] = attr.value.value;
620
- } else if (tModule3.isJSXExpressionContainer(attr.value) && tModule3.isStringLiteral(attr.value.expression)) {
621
- props[keyName] = attr.value.expression.value;
622
- }
623
- }
624
- }
625
- }
626
- return props;
627
- }
628
- function generateStableKey(tagName, componentName, path, props) {
629
- const pathStr = path.join("-");
630
- const lowerTag = tagName.toLowerCase();
631
- if (props.id) {
632
- return `${componentName}-${props.id}`;
633
- }
634
- if (props.name) {
635
- return `${componentName}-${props.name}`;
636
- }
637
- const typeStr = props.type || "text";
638
- return `${componentName}-${lowerTag}-${typeStr}-${pathStr}`;
639
- }
640
- function calculateJSXPath(path) {
641
- const pathArray = [];
642
- let currentPath = path.parentPath;
643
- while (currentPath) {
644
- if (currentPath.isJSXElement()) {
645
- const parent = currentPath.parentPath;
646
- if (parent && parent.isJSXElement()) {
647
- const parentNode = parent.node;
648
- let index = 0;
649
- for (let i = 0; i < parentNode.children.length; i++) {
650
- const child = parentNode.children[i];
651
- if (child === currentPath.node) {
652
- index = i;
653
- break;
654
- }
655
- }
656
- pathArray.unshift(index);
657
- } else if (parent && parent.isReturnStatement()) {
658
- break;
659
- }
660
- } else if (currentPath.isReturnStatement()) {
661
- break;
662
- }
663
- currentPath = currentPath.parentPath;
664
- }
665
- return pathArray.length > 0 ? pathArray : [0];
666
- }
667
- function findComponentName(path) {
668
- let classPath = path;
669
- while (classPath) {
670
- if (classPath.isClassDeclaration()) {
671
- const classNode = classPath.node;
672
- if (classNode.id && tModule3.isIdentifier(classNode.id)) {
673
- return classNode.id.name;
674
- }
675
- break;
676
- }
677
- classPath = classPath.parentPath;
678
- }
679
- return "Component";
680
- }
681
- function babelPluginWSXFocus() {
682
- const t = tModule3;
683
- return {
684
- name: "babel-plugin-wsx-focus",
685
- visitor: {
686
- JSXOpeningElement(path) {
687
- const element = path.node;
688
- if (!t.isJSXIdentifier(element.name)) {
689
- return;
690
- }
691
- const elementName = element.name.name;
692
- const hasKey = element.attributes.some(
693
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "data-wsx-key"
694
- );
695
- if (hasKey) {
696
- return;
697
- }
698
- const props = extractPropsFromJSXAttributes(element.attributes);
699
- const hasContentEditable = element.attributes.some(
700
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && (attr.name.name === "contenteditable" || attr.name.name === "contentEditable")
701
- );
702
- if (!isFocusableElement(elementName, hasContentEditable)) {
703
- return;
704
- }
705
- const componentName = findComponentName(path);
706
- const pathArray = calculateJSXPath(path);
707
- const key = generateStableKey(elementName, componentName, pathArray, props);
708
- const keyAttr = t.jsxAttribute(
709
- t.jsxIdentifier("data-wsx-key"),
710
- t.stringLiteral(key)
711
- );
712
- element.attributes.push(keyAttr);
713
- }
714
- }
715
- };
716
- }
717
-
718
599
  // src/vite-plugin-wsx-babel.ts
719
600
  function getJSXFactoryImportPath(_options) {
720
601
  return "@wsxjs/wsx-core";
@@ -806,9 +687,6 @@ function vitePluginWSXWithBabel(options = {}) {
806
687
  }
807
688
  ]
808
689
  ] : [],
809
- // Focus key generation plugin runs early to add data-wsx-key attributes
810
- // This must run before JSX is transformed to h() calls
811
- babelPluginWSXFocus,
812
690
  // CRITICAL: State decorator transformation must run BEFORE @babel/plugin-proposal-decorators
813
691
  // This allows the plugin to detect @state decorators in their original form and throw errors if needed
814
692
  // The plugin removes @state decorators after processing, so the decorator plugin won't see them
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-vite-plugin",
3
- "version": "0.0.30",
3
+ "version": "0.1.0",
4
4
  "description": "Vite plugin for WSXJS",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "@babel/plugin-transform-class-static-block": "^7.28.0",
32
32
  "@babel/preset-typescript": "^7.28.5",
33
33
  "@babel/types": "^7.28.1",
34
- "@wsxjs/wsx-core": "0.0.30"
34
+ "@wsxjs/wsx-core": "0.1.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/traverse": "^7.28.5",
@@ -12,7 +12,6 @@ import { existsSync } from "fs";
12
12
  import { dirname, join, basename } from "path";
13
13
  import babelPluginWSXState from "./babel-plugin-wsx-state";
14
14
  import babelPluginWSXStyle from "./babel-plugin-wsx-style";
15
- import babelPluginWSXFocus from "./babel-plugin-wsx-focus";
16
15
 
17
16
  export interface WSXPluginOptions {
18
17
  jsxFactory?: string;
@@ -131,9 +130,6 @@ export function vitePluginWSXWithBabel(options: WSXPluginOptions = {}): Plugin {
131
130
  ],
132
131
  ]
133
132
  : []),
134
- // Focus key generation plugin runs early to add data-wsx-key attributes
135
- // This must run before JSX is transformed to h() calls
136
- babelPluginWSXFocus,
137
133
  // CRITICAL: State decorator transformation must run BEFORE @babel/plugin-proposal-decorators
138
134
  // This allows the plugin to detect @state decorators in their original form and throw errors if needed
139
135
  // The plugin removes @state decorators after processing, so the decorator plugin won't see them
@@ -1,225 +0,0 @@
1
- /**
2
- * Babel plugin to automatically add data-wsx-key attributes to focusable elements
3
- * for focus preservation only (RFC-0046).
4
- *
5
- * Transforms:
6
- * <input value={this.name} onInput={this.handleInput} />
7
- *
8
- * To:
9
- * <input
10
- * data-wsx-key="MyComponent-input-text-0-0"
11
- * value={this.name}
12
- * onInput={this.handleInput}
13
- * />
14
- *
15
- * This enables automatic focus preservation during component rerenders.
16
- *
17
- * IMPORTANT: This plugin ONLY handles focus preservation. It does NOT generate
18
- * cache keys or position IDs. The framework handles cache key generation using
19
- * user-provided `key` prop or runtime counters (following React/Vue design).
20
- */
21
-
22
- import type { PluginObj, NodePath } from "@babel/core";
23
- import type * as t from "@babel/types";
24
- import * as tModule from "@babel/types";
25
-
26
- /**
27
- * Focusable HTML elements that need keys
28
- */
29
- const FOCUSABLE_ELEMENTS = new Set([
30
- "input",
31
- "textarea",
32
- "select",
33
- "button", // Also focusable
34
- ]);
35
-
36
- /**
37
- * Check if an element is focusable
38
- */
39
- function isFocusableElement(tagName: string, hasContentEditable: boolean): boolean {
40
- const lowerTag = tagName.toLowerCase();
41
- return FOCUSABLE_ELEMENTS.has(lowerTag) || hasContentEditable;
42
- }
43
-
44
- /**
45
- * Extract props from JSX attributes for key generation
46
- */
47
- function extractPropsFromJSXAttributes(attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[]): {
48
- id?: string;
49
- name?: string;
50
- type?: string;
51
- } {
52
- const props: { id?: string; name?: string; type?: string } = {};
53
-
54
- for (const attr of attributes) {
55
- if (tModule.isJSXAttribute(attr) && tModule.isJSXIdentifier(attr.name)) {
56
- const keyName = attr.name.name;
57
-
58
- if (keyName === "id" || keyName === "name" || keyName === "type") {
59
- if (tModule.isStringLiteral(attr.value)) {
60
- props[keyName as "id" | "name" | "type"] = attr.value.value;
61
- } else if (
62
- tModule.isJSXExpressionContainer(attr.value) &&
63
- tModule.isStringLiteral(attr.value.expression)
64
- ) {
65
- props[keyName as "id" | "name" | "type"] = attr.value.expression.value;
66
- }
67
- }
68
- }
69
- }
70
-
71
- return props;
72
- }
73
-
74
- /**
75
- * Generate stable key for an element
76
- * @param tagName - HTML tag name
77
- * @param componentName - Component class name
78
- * @param path - Path from root (array of sibling indices)
79
- * @param props - Element properties (for id, name, type)
80
- * @returns Stable key string
81
- */
82
- function generateStableKey(
83
- tagName: string,
84
- componentName: string,
85
- path: number[],
86
- props: { id?: string; name?: string; type?: string }
87
- ): string {
88
- const pathStr = path.join("-");
89
- const lowerTag = tagName.toLowerCase();
90
-
91
- // Priority: id > name > type + path
92
- if (props.id) {
93
- return `${componentName}-${props.id}`;
94
- }
95
-
96
- if (props.name) {
97
- return `${componentName}-${props.name}`;
98
- }
99
-
100
- // Default: component-tag-type-path
101
- const typeStr = props.type || "text";
102
- return `${componentName}-${lowerTag}-${typeStr}-${pathStr}`;
103
- }
104
-
105
- /**
106
- * Calculate path from root JSX element
107
- */
108
- function calculateJSXPath(path: NodePath<t.JSXOpeningElement>): number[] {
109
- const pathArray: number[] = [];
110
- let currentPath: NodePath | null = path.parentPath; // JSXElement
111
-
112
- // Walk up to find siblings
113
- while (currentPath) {
114
- if (currentPath.isJSXElement()) {
115
- const parent = currentPath.parentPath;
116
- if (parent && parent.isJSXElement()) {
117
- // Find index in parent's children
118
- const parentNode = parent.node;
119
- let index = 0;
120
- for (let i = 0; i < parentNode.children.length; i++) {
121
- const child = parentNode.children[i];
122
- if (child === currentPath.node) {
123
- index = i;
124
- break;
125
- }
126
- }
127
- pathArray.unshift(index);
128
- } else if (parent && parent.isReturnStatement()) {
129
- // At root level
130
- break;
131
- }
132
- } else if (currentPath.isReturnStatement()) {
133
- // At root level
134
- break;
135
- }
136
- currentPath = currentPath.parentPath;
137
- }
138
-
139
- return pathArray.length > 0 ? pathArray : [0];
140
- }
141
-
142
- /**
143
- * Find component name from class declaration
144
- */
145
- function findComponentName(path: NodePath<t.JSXOpeningElement>): string {
146
- let classPath: NodePath<t.Node> | null = path;
147
-
148
- // Find parent class declaration
149
- while (classPath) {
150
- if (classPath.isClassDeclaration()) {
151
- const classNode = classPath.node;
152
- if (classNode.id && tModule.isIdentifier(classNode.id)) {
153
- return classNode.id.name;
154
- }
155
- break;
156
- }
157
- classPath = classPath.parentPath;
158
- }
159
-
160
- return "Component";
161
- }
162
-
163
- export default function babelPluginWSXFocus(): PluginObj {
164
- const t = tModule;
165
- return {
166
- name: "babel-plugin-wsx-focus",
167
- visitor: {
168
- JSXOpeningElement(path) {
169
- const element = path.node;
170
-
171
- if (!t.isJSXIdentifier(element.name)) {
172
- return;
173
- }
174
-
175
- const elementName = element.name.name;
176
-
177
- // Check if already has data-wsx-key
178
- const hasKey = element.attributes.some(
179
- (attr) =>
180
- t.isJSXAttribute(attr) &&
181
- t.isJSXIdentifier(attr.name) &&
182
- attr.name.name === "data-wsx-key"
183
- );
184
-
185
- if (hasKey) {
186
- return; // Skip if already has key
187
- }
188
-
189
- // Extract props
190
- const props = extractPropsFromJSXAttributes(element.attributes);
191
-
192
- // Check for contenteditable attribute
193
- const hasContentEditable = element.attributes.some(
194
- (attr) =>
195
- t.isJSXAttribute(attr) &&
196
- t.isJSXIdentifier(attr.name) &&
197
- (attr.name.name === "contenteditable" ||
198
- attr.name.name === "contentEditable")
199
- );
200
-
201
- // Check if element is focusable
202
- if (!isFocusableElement(elementName, hasContentEditable)) {
203
- return; // Skip non-focusable elements
204
- }
205
-
206
- // Get component name
207
- const componentName = findComponentName(path);
208
-
209
- // Calculate path from root
210
- const pathArray = calculateJSXPath(path);
211
-
212
- // Generate key
213
- const key = generateStableKey(elementName, componentName, pathArray, props);
214
-
215
- // Add data-wsx-key attribute
216
- const keyAttr = t.jsxAttribute(
217
- t.jsxIdentifier("data-wsx-key"),
218
- t.stringLiteral(key)
219
- );
220
-
221
- element.attributes.push(keyAttr);
222
- },
223
- },
224
- };
225
- }