@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 +0 -122
- package/dist/index.mjs +0 -122
- package/package.json +2 -2
- package/src/vite-plugin-wsx-babel.ts +0 -4
- package/src/babel-plugin-wsx-focus.ts +0 -225
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
|
|
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
|
|
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
|
-
}
|