modern-cmdk 1.4.0 → 1.5.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/README.md +31 -27
- package/THIRD-PARTY-NOTICES.md +303 -0
- package/dist/codemod/bin.mjs +14 -0
- package/dist/codemod/chunk-jscodeshift.mjs +36210 -0
- package/dist/codemod/chunk-print-options.mjs +43 -0
- package/dist/codemod/chunk-worker.mjs +476 -0
- package/dist/codemod/cli.mjs +297 -43660
- package/dist/codemod/transforms/data-attrs.mjs +551 -38
- package/dist/codemod/transforms/forward-ref.mjs +197 -32
- package/dist/codemod/transforms/import-rewrite.mjs +389 -30
- package/dist/codemod/transforms/should-filter.mjs +199 -10
- package/dist/codemod/worker.mjs +1 -0
- package/dist/core/index.d.mts +6 -1
- package/dist/core/index.mjs +12 -4
- package/dist/react/dialog.mjs +0 -1
- package/dist/react/index.d.mts +21 -3
- package/dist/react/index.mjs +45 -16
- package/package.json +5 -7
|
@@ -1,65 +1,578 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { t as printOptions } from "../chunk-print-options.mjs";
|
|
2
|
+
// ../modern-cmdk-codemod/src/transforms/data-attrs.ts
|
|
3
|
+
/** Mapping from cmdk attribute names to the attribute modern-cmdk renders. */
|
|
3
4
|
const ATTR_MAP = /* @__PURE__ */ new Map([
|
|
4
|
-
["cmdk-root", "data-command"],
|
|
5
|
+
["cmdk-root", "data-command-root"],
|
|
6
|
+
["cmdk-dialog", "data-command-dialog"],
|
|
7
|
+
["cmdk-overlay", "data-command-overlay"],
|
|
5
8
|
["cmdk-input", "data-command-input"],
|
|
6
9
|
["cmdk-list", "data-command-list"],
|
|
10
|
+
["cmdk-list-sizer", "data-command-list-inner"],
|
|
7
11
|
["cmdk-item", "data-command-item"],
|
|
8
12
|
["cmdk-group", "data-command-group"],
|
|
13
|
+
["cmdk-group-heading", "data-command-group-heading"],
|
|
14
|
+
["cmdk-group-items", "data-command-group-items"],
|
|
9
15
|
["cmdk-separator", "data-command-separator"],
|
|
10
16
|
["cmdk-empty", "data-command-empty"],
|
|
11
17
|
["cmdk-loading", "data-command-loading"]
|
|
12
18
|
]);
|
|
13
|
-
/** CSS custom property renames */
|
|
19
|
+
/** CSS custom property renames. */
|
|
14
20
|
const CSS_VAR_MAP = /* @__PURE__ */ new Map([["--cmdk-list-height", "--command-list-height"]]);
|
|
21
|
+
/** cmdk names with no modern-cmdk equivalent, each with the reason a site is reported. */
|
|
22
|
+
const UNMAPPED_ATTRS = /* @__PURE__ */ new Map([["cmdk-label", "cmdk renders a visually hidden <label>; modern-cmdk puts the label on the root as aria-label and renders no label element"], ["cmdk-item-select", "a DOM event cmdk dispatches on an item to run its onSelect, not an attribute; modern-cmdk has no such event, use the item onSelect prop"]]);
|
|
23
|
+
/** cmdk state attributes (always rendered as "true" or "false") and the presence attribute that replaces each. */
|
|
24
|
+
const STATE_ATTRS = /* @__PURE__ */ new Map([["data-selected", "data-active"], ["data-disabled", "data-disabled"]]);
|
|
25
|
+
const CMDK_NAMES = /* @__PURE__ */ new Set([...ATTR_MAP.keys(), ...UNMAPPED_ATTRS.keys()]);
|
|
26
|
+
/** Imports whose bindings make a JSX element a cmdk element for the Tailwind rewrite. */
|
|
27
|
+
const CMDK_SOURCES = /* @__PURE__ */ new Set(["cmdk", "modern-cmdk/react"]);
|
|
28
|
+
/** The JSX attributes whose string content is a class list. */
|
|
29
|
+
const CLASS_ATTRS = /* @__PURE__ */ new Set(["className", "class"]);
|
|
30
|
+
const CSS_VARS_LONGEST_FIRST = [...CSS_VAR_MAP.keys()].sort((a, b) => b.length - a.length);
|
|
31
|
+
/** One attribute selector inside a compound: [name], [name=value], [name~="value" i]. */
|
|
32
|
+
const ATTRIBUTE_SELECTOR = /\[\s*([A-Za-z_][\w-]*)\s*(?:([~|^$*]?=)\s*("[^"]*"|'[^']*'|[^\]\s"']*)\s*)?(?:[iIsS]\s*)?\]/dy;
|
|
15
33
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
34
|
+
* The start of an attribute selector cut short by a template literal's `${`: [cmdk-item=" or
|
|
35
|
+
* [cmdk-item. Only the name is rewritten.
|
|
18
36
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
const PARTIAL_ATTRIBUTE_SELECTOR = /\[\s*([A-Za-z_][\w-]*)(?=\s*[~|^$*]?=|\s*$)/dy;
|
|
38
|
+
/** Tailwind presence form of the two state attributes: data-[selected]:, data-[disabled]/name:. */
|
|
39
|
+
const TAILWIND_PRESENCE = /(?<!\w)data-\[(selected|disabled)\](?=[:/])/g;
|
|
40
|
+
/** Tailwind value variants of the two state attributes: data-[selected=true]:, data-[disabled='false']/name:. */
|
|
41
|
+
const TAILWIND_STATE = /(?<!\w)data-\[(selected|disabled)=(?:(["'])(true|false)\2|(true|false))\](?=[:/])/g;
|
|
42
|
+
/** Whitespace that only pads a closing parenthesis, a comma or a combinator inside parentheses. */
|
|
43
|
+
const ARGUMENT_PADDING = /\s*[),>+~]/y;
|
|
44
|
+
/** Tailwind's `*:` and `**:` variants, which move a class to the element's children or descendants. */
|
|
45
|
+
const CHILD_VARIANT = /(?:^|:)\*\*?:/;
|
|
46
|
+
function parentMatchOf(flags) {
|
|
47
|
+
if (flags.every(Boolean)) return "all";
|
|
48
|
+
return flags.some(Boolean) ? "some" : "none";
|
|
49
|
+
}
|
|
50
|
+
function applyEdits(text, edits) {
|
|
51
|
+
let result = text;
|
|
52
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
27
53
|
return result;
|
|
28
54
|
}
|
|
55
|
+
function unquote(value) {
|
|
56
|
+
const quote = value[0];
|
|
57
|
+
return (quote === "\"" || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
|
|
58
|
+
}
|
|
29
59
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
60
|
+
* Rewrite the cmdk attribute selectors and custom properties in a piece of text: a selector
|
|
61
|
+
* string or template quasi (`css` false), or a whole stylesheet (`css` true).
|
|
62
|
+
*
|
|
63
|
+
* The text is cut into compounds (runs of simple selectors with no combinator, comma, brace or
|
|
64
|
+
* whitespace at the top level). Inside any compound, a bracketed attribute whose name is in
|
|
65
|
+
* ATTR_MAP gets its name replaced, operator and value kept; a name in UNMAPPED_ATTRS is reported.
|
|
66
|
+
* A compound that holds a cmdk attribute also has its data-selected / data-disabled value
|
|
67
|
+
* attributes rewritten: ="true" to the presence attribute, ="false" to :not(presence); any other
|
|
68
|
+
* form is reported. In a stylesheet, a compound starting with `&` counts as holding a cmdk
|
|
69
|
+
* attribute when the enclosing rule's selectors all end in one; under a rule where only some do,
|
|
70
|
+
* each state attribute of such a compound is reported. Inside the parentheses of :not() / :is() /
|
|
71
|
+
* :where() / :matches(), the compound after an argument's last combinator (its subject) joins the
|
|
72
|
+
* outer compound, and the compounds before it stand alone, rewritten only if they hold a cmdk
|
|
73
|
+
* attribute themselves. Inside any other parentheses (:has(), :nth-child(), url(), var(), ...)
|
|
74
|
+
* every compound stands alone. `tailwind` marks a class value of a cmdk-bound element, where an
|
|
75
|
+
* arbitrary variant `[&[data-selected=true]_svg]` holds the bound element's own attributes up to
|
|
76
|
+
* its first `_`, `>`, `+` or `~`. Inside the parentheses of such a variant, `_` is a combinator too. A `[&...]` after a `*:` or `**:` variant, or after an earlier
|
|
77
|
+
* `[&...]` variant, is not taken as the bound element.
|
|
78
|
+
*/
|
|
79
|
+
function rewriteSelectors(text, css, report, tailwind) {
|
|
80
|
+
return applyEdits(text, selectorEdits(text, css, report, tailwind));
|
|
81
|
+
}
|
|
82
|
+
/** The edits rewriteSelectors applies, as ranges of `text`; reports are made while collecting. */
|
|
83
|
+
function selectorEdits(text, css, report, tailwind) {
|
|
84
|
+
const edits = [];
|
|
85
|
+
const ruleStack = [];
|
|
86
|
+
let selectorFlags = [];
|
|
87
|
+
let lastFlag = false;
|
|
88
|
+
let preludeFresh = true;
|
|
89
|
+
let preludeIsAtRule = false;
|
|
90
|
+
let attrs = [];
|
|
91
|
+
let compoundStart = -1;
|
|
92
|
+
/** Index of the first `[&` arbitrary variant of the current class token, at its start or after a `:` of a variant prefix that holds no `*:` or `**:` variant. */
|
|
93
|
+
let ampStart = -1;
|
|
94
|
+
/** Index of the first `_`, `>`, `+` or `~` at bracket depth 1, outside parentheses, of a `[&...` arbitrary variant: where the part on another element starts. */
|
|
95
|
+
let ampDescendant = -1;
|
|
96
|
+
const frames = [];
|
|
97
|
+
let brackets = 0;
|
|
98
|
+
if (!css) {
|
|
99
|
+
const whole = text.trim();
|
|
100
|
+
const reason = UNMAPPED_ATTRS.get(whole);
|
|
101
|
+
if (reason !== void 0) report(text.indexOf(whole), `${whole} has no modern-cmdk equivalent: ${reason}; left unchanged`);
|
|
102
|
+
const mapped = ATTR_MAP.get(whole);
|
|
103
|
+
if (mapped !== void 0) report(text.indexOf(whole), `${whole} is a cmdk attribute name outside a selector; modern-cmdk renders ${mapped}; left unchanged`);
|
|
104
|
+
}
|
|
105
|
+
const endCompound = (isArgument = false) => {
|
|
106
|
+
if (compoundStart === -1) return;
|
|
107
|
+
const holds = attrs.some((a) => CMDK_NAMES.has(a.name));
|
|
108
|
+
const parentMatch = text[compoundStart] === "&" ? ruleStack.at(-1) ?? "none" : "none";
|
|
109
|
+
const viaParent = parentMatch === "all";
|
|
110
|
+
const partialParent = parentMatch === "some" && !holds;
|
|
111
|
+
const ampBound = tailwind && ampStart !== -1;
|
|
112
|
+
for (const a of attrs) {
|
|
113
|
+
const target = ATTR_MAP.get(a.name);
|
|
114
|
+
if (target !== void 0) {
|
|
115
|
+
edits.push({
|
|
116
|
+
start: a.nameStart,
|
|
117
|
+
end: a.nameEnd,
|
|
118
|
+
text: target
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const reason = UNMAPPED_ATTRS.get(a.name);
|
|
123
|
+
if (reason !== void 0) {
|
|
124
|
+
report(a.start, `${a.name} has no modern-cmdk equivalent: ${reason}; left unchanged`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const presence = STATE_ATTRS.get(a.name);
|
|
128
|
+
if (presence === void 0 || a.partial) continue;
|
|
129
|
+
if (partialParent) {
|
|
130
|
+
report(a.start, `${text.slice(a.start, a.end)} under a rule whose selectors only partly hold a cmdk attribute cannot be migrated automatically; left unchanged`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const onBound = ampBound && (ampDescendant === -1 || a.start < ampDescendant);
|
|
134
|
+
if (!(holds || viaParent || onBound)) continue;
|
|
135
|
+
const value = a.operator === "=" && a.value !== void 0 ? unquote(a.value) : void 0;
|
|
136
|
+
if (value === "true") edits.push({
|
|
137
|
+
start: a.start,
|
|
138
|
+
end: a.end,
|
|
139
|
+
text: `[${presence}]`
|
|
140
|
+
});
|
|
141
|
+
else if (value === "false") edits.push({
|
|
142
|
+
start: a.start,
|
|
143
|
+
end: a.end,
|
|
144
|
+
text: `:not([${presence}])`
|
|
145
|
+
});
|
|
146
|
+
else report(a.start, `${text.slice(a.start, a.end)} beside a cmdk attribute cannot be migrated automatically: cmdk rendered ${a.name} with a "true" or "false" value, modern-cmdk sets ${presence} only while the state holds; left unchanged`);
|
|
147
|
+
}
|
|
148
|
+
if (!isArgument) lastFlag = holds || viaParent;
|
|
149
|
+
attrs = [];
|
|
150
|
+
compoundStart = -1;
|
|
151
|
+
ampStart = -1;
|
|
152
|
+
ampDescendant = -1;
|
|
153
|
+
};
|
|
154
|
+
/** End the compound collected since the last combinator or comma: an argument's subject joins the outer compound. */
|
|
155
|
+
const endArgumentSubject = (frame) => {
|
|
156
|
+
if (!frame.subject) {
|
|
157
|
+
endCompound(true);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
frame.outer.attrs.push(...attrs);
|
|
161
|
+
if (frame.outer.start === -1) frame.outer.start = compoundStart;
|
|
162
|
+
attrs = [];
|
|
163
|
+
compoundStart = -1;
|
|
164
|
+
};
|
|
165
|
+
const padsArgument = (at) => {
|
|
166
|
+
ARGUMENT_PADDING.lastIndex = at;
|
|
167
|
+
return ARGUMENT_PADDING.test(text);
|
|
168
|
+
};
|
|
169
|
+
const startPrelude = () => {
|
|
170
|
+
selectorFlags = [];
|
|
171
|
+
lastFlag = false;
|
|
172
|
+
preludeFresh = true;
|
|
173
|
+
preludeIsAtRule = false;
|
|
174
|
+
};
|
|
175
|
+
for (let i = 0; i < text.length;) {
|
|
176
|
+
const c = text[i];
|
|
177
|
+
if (css && c === "/" && text[i + 1] === "*") {
|
|
178
|
+
endCompound();
|
|
179
|
+
const close = text.indexOf("*/", i + 2);
|
|
180
|
+
i = close === -1 ? text.length : close + 2;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (css && frames.length === 0 && c === "/" && text[i + 1] === "/" && (i === 0 || /[\s;{}]/.test(text[i - 1]))) {
|
|
184
|
+
endCompound();
|
|
185
|
+
const eol = text.indexOf("\n", i);
|
|
186
|
+
i = eol === -1 ? text.length : eol;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (brackets > 0 && /\s/.test(c)) brackets = 0;
|
|
190
|
+
const frame = frames.at(-1);
|
|
191
|
+
if (frame !== void 0 && (brackets === 0 && /[\s>+~,]/.test(c) || tailwind && frame.brackets > 0 && brackets === frame.brackets && /[_>+~,]/.test(c))) {
|
|
192
|
+
if (c === ",") {
|
|
193
|
+
endArgumentSubject(frame);
|
|
194
|
+
frame.hasContent = false;
|
|
195
|
+
} else if (!/\s/.test(c) || frame.hasContent && !padsArgument(i)) {
|
|
196
|
+
endCompound(true);
|
|
197
|
+
frame.hasContent = false;
|
|
198
|
+
}
|
|
199
|
+
i++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (frame === void 0 && brackets === 0 && /[\s>+~,{};]/.test(c)) {
|
|
203
|
+
endCompound();
|
|
204
|
+
if (c === ",") {
|
|
205
|
+
selectorFlags.push(lastFlag);
|
|
206
|
+
lastFlag = false;
|
|
207
|
+
} else if (c === "{") {
|
|
208
|
+
selectorFlags.push(lastFlag);
|
|
209
|
+
ruleStack.push(preludeIsAtRule ? ruleStack.at(-1) ?? "none" : parentMatchOf(selectorFlags));
|
|
210
|
+
startPrelude();
|
|
211
|
+
} else if (c === "}") {
|
|
212
|
+
ruleStack.pop();
|
|
213
|
+
startPrelude();
|
|
214
|
+
} else if (c === ";") startPrelude();
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (compoundStart === -1) {
|
|
219
|
+
compoundStart = i;
|
|
220
|
+
if (preludeFresh) {
|
|
221
|
+
preludeFresh = false;
|
|
222
|
+
preludeIsAtRule = c === "@";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (frame !== void 0) frame.hasContent = true;
|
|
226
|
+
if (css && (c === "\"" || c === "'") && brackets === 0) {
|
|
227
|
+
let j = i + 1;
|
|
228
|
+
while (j < text.length && text[j] !== c) j += text[j] === "\\" ? 2 : 1;
|
|
229
|
+
i = j + 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (c === "[") {
|
|
233
|
+
ATTRIBUTE_SELECTOR.lastIndex = i;
|
|
234
|
+
const m = ATTRIBUTE_SELECTOR.exec(text);
|
|
235
|
+
const nameSpan = m?.indices?.[1];
|
|
236
|
+
if (m !== null && nameSpan !== void 0) {
|
|
237
|
+
attrs.push({
|
|
238
|
+
start: i,
|
|
239
|
+
end: ATTRIBUTE_SELECTOR.lastIndex,
|
|
240
|
+
name: m[1],
|
|
241
|
+
nameStart: nameSpan[0],
|
|
242
|
+
nameEnd: nameSpan[1],
|
|
243
|
+
operator: m[2],
|
|
244
|
+
value: m[3],
|
|
245
|
+
partial: false
|
|
246
|
+
});
|
|
247
|
+
i = ATTRIBUTE_SELECTOR.lastIndex;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
PARTIAL_ATTRIBUTE_SELECTOR.lastIndex = i;
|
|
251
|
+
const partial = PARTIAL_ATTRIBUTE_SELECTOR.exec(text);
|
|
252
|
+
const partialName = partial?.indices?.[1];
|
|
253
|
+
if (partial !== null && partialName !== void 0) {
|
|
254
|
+
attrs.push({
|
|
255
|
+
start: i,
|
|
256
|
+
end: PARTIAL_ATTRIBUTE_SELECTOR.lastIndex,
|
|
257
|
+
name: partial[1],
|
|
258
|
+
nameStart: partialName[0],
|
|
259
|
+
nameEnd: partialName[1],
|
|
260
|
+
operator: void 0,
|
|
261
|
+
value: void 0,
|
|
262
|
+
partial: true
|
|
263
|
+
});
|
|
264
|
+
i = PARTIAL_ATTRIBUTE_SELECTOR.lastIndex;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (tailwind && brackets === 0 && ampStart === -1 && text.startsWith("[&", i) && (i === compoundStart || text[i - 1] === ":" && !CHILD_VARIANT.test(text.slice(compoundStart, i)))) {
|
|
268
|
+
ampStart = i;
|
|
269
|
+
ampDescendant = -1;
|
|
270
|
+
}
|
|
271
|
+
brackets++;
|
|
272
|
+
} else if (c === "]") {
|
|
273
|
+
if (brackets > 0) brackets--;
|
|
274
|
+
} else if (tailwind && brackets === 1 && frames.length === 0 && ampStart !== -1 && ampDescendant === -1 && /[_>+~]/.test(c)) ampDescendant = i;
|
|
275
|
+
else if (c === "(") {
|
|
276
|
+
frames.push({
|
|
277
|
+
brackets,
|
|
278
|
+
hasContent: false,
|
|
279
|
+
outer: {
|
|
280
|
+
attrs,
|
|
281
|
+
start: compoundStart,
|
|
282
|
+
ampStart,
|
|
283
|
+
ampDescendant
|
|
284
|
+
},
|
|
285
|
+
subject: /:(?:not|is|where|matches)$/i.test(text.slice(Math.max(0, i - 8), i))
|
|
286
|
+
});
|
|
287
|
+
attrs = [];
|
|
288
|
+
compoundStart = -1;
|
|
289
|
+
ampStart = -1;
|
|
290
|
+
ampDescendant = -1;
|
|
291
|
+
} else if (c === ")") {
|
|
292
|
+
const closing = frames.pop();
|
|
293
|
+
if (closing !== void 0) {
|
|
294
|
+
endArgumentSubject(closing);
|
|
295
|
+
attrs = closing.outer.attrs;
|
|
296
|
+
compoundStart = closing.outer.start;
|
|
297
|
+
ampStart = closing.outer.ampStart;
|
|
298
|
+
ampDescendant = closing.outer.ampDescendant;
|
|
299
|
+
}
|
|
300
|
+
} else if (c === "-" && text.startsWith("--cmdk-", i) && !/[\w-]/.test(text[i - 1] ?? "")) {
|
|
301
|
+
const name = CSS_VARS_LONGEST_FIRST.find((v) => text.startsWith(v, i) && !/[\w-]/.test(text[i + v.length] ?? ""));
|
|
302
|
+
if (name !== void 0) {
|
|
303
|
+
edits.push({
|
|
304
|
+
start: i,
|
|
305
|
+
end: i + name.length,
|
|
306
|
+
text: CSS_VAR_MAP.get(name)
|
|
307
|
+
});
|
|
308
|
+
i += name.length;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
i++;
|
|
313
|
+
}
|
|
314
|
+
for (let open = frames.pop(); open !== void 0; open = frames.pop()) {
|
|
315
|
+
endArgumentSubject(open);
|
|
316
|
+
attrs = open.outer.attrs;
|
|
317
|
+
compoundStart = open.outer.start;
|
|
318
|
+
ampStart = open.outer.ampStart;
|
|
319
|
+
ampDescendant = open.outer.ampDescendant;
|
|
320
|
+
}
|
|
321
|
+
endCompound();
|
|
322
|
+
return edits;
|
|
323
|
+
}
|
|
324
|
+
/** Rewrite the Tailwind value variants of the two state attributes in a class list, reporting the presence form. */
|
|
325
|
+
function rewriteTailwind(text, report) {
|
|
326
|
+
return applyEdits(text, tailwindEdits(text, report));
|
|
327
|
+
}
|
|
328
|
+
/** The edits rewriteTailwind applies, as ranges of `text`. */
|
|
329
|
+
function tailwindEdits(text, report) {
|
|
330
|
+
const edits = [];
|
|
331
|
+
for (const m of text.matchAll(TAILWIND_PRESENCE)) {
|
|
332
|
+
const name = `data-${m[1]}`;
|
|
333
|
+
const presence = STATE_ATTRS.get(name);
|
|
334
|
+
report(m.index, `${m[0]}: on a cmdk element cannot be migrated automatically: cmdk rendered ${name} with a "true" or "false" value, modern-cmdk sets ${presence} only while the state holds; left unchanged`);
|
|
335
|
+
}
|
|
336
|
+
for (const m of text.matchAll(TAILWIND_STATE)) {
|
|
337
|
+
const presence = STATE_ATTRS.get(`data-${m[1]}`);
|
|
338
|
+
if ((m[3] ?? m[4]) === "true") edits.push({
|
|
339
|
+
start: m.index,
|
|
340
|
+
end: m.index + m[0].length,
|
|
341
|
+
text: presence
|
|
342
|
+
});
|
|
343
|
+
else report(m.index, `${m[0]}: has no modern-cmdk equivalent, ${presence} is a presence attribute with no false value (use not-${presence}:); left unchanged`);
|
|
344
|
+
}
|
|
345
|
+
return edits;
|
|
346
|
+
}
|
|
347
|
+
/** 1-based line of an index in a text that itself starts at `firstLine`. */
|
|
348
|
+
function lineAt(text, index, firstLine) {
|
|
349
|
+
let line = firstLine;
|
|
350
|
+
for (let i = text.indexOf("\n"); i !== -1 && i < index; i = text.indexOf("\n", i + 1)) line++;
|
|
351
|
+
return line;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Stylesheet pass for .css and .scss files: the same selector and custom property rewrite as the
|
|
355
|
+
* script pass, with comments, strings outside attribute selectors and formatting untouched.
|
|
356
|
+
* Each site left for manual migration is reported once with its line.
|
|
357
|
+
*/
|
|
358
|
+
function transformStylesheet(source, report) {
|
|
359
|
+
return rewriteSelectors(source, true, (index, message) => {
|
|
360
|
+
report(`line ${lineAt(source, index, 1)}: ${message}`);
|
|
361
|
+
}, false);
|
|
362
|
+
}
|
|
363
|
+
/** Stands in for `${...}` while a template literal's quasis are scanned as one text. */
|
|
364
|
+
const PLACEHOLDER = "";
|
|
365
|
+
/** How a placeholder reads in a report. */
|
|
366
|
+
const PLACEHOLDER_TEXT = "${...}";
|
|
367
|
+
function rawOf(node) {
|
|
368
|
+
return node.extra?.raw ?? node.raw;
|
|
369
|
+
}
|
|
370
|
+
const SIMPLE_ESCAPES = /* @__PURE__ */ new Map([
|
|
371
|
+
["n", "\n"],
|
|
372
|
+
["t", " "],
|
|
373
|
+
["b", "\b"],
|
|
374
|
+
["f", "\f"],
|
|
375
|
+
["v", "\v"],
|
|
376
|
+
["r", "\r"]
|
|
377
|
+
]);
|
|
378
|
+
function decodeRaw(inner) {
|
|
379
|
+
const units = [];
|
|
380
|
+
const rawStart = [];
|
|
381
|
+
const rawEnd = [];
|
|
382
|
+
const push = (value, from, to) => {
|
|
383
|
+
for (const unit of value) for (let k = 0; k < unit.length; k++) {
|
|
384
|
+
units.push(unit.charAt(k));
|
|
385
|
+
rawStart.push(from);
|
|
386
|
+
rawEnd.push(to);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
let i = 0;
|
|
390
|
+
while (i < inner.length) {
|
|
391
|
+
const c = inner.charAt(i);
|
|
392
|
+
if (c !== "\\") {
|
|
393
|
+
push(c, i, i + 1);
|
|
394
|
+
i++;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const next = inner.charAt(i + 1);
|
|
398
|
+
let end = i + 2;
|
|
399
|
+
let value = next;
|
|
400
|
+
if (next === "\r" && inner.charAt(i + 2) === "\n") {
|
|
401
|
+
end = i + 3;
|
|
402
|
+
value = "";
|
|
403
|
+
} else if (next === "\n" || next === "\r" || next === "\u2028" || next === "\u2029") value = "";
|
|
404
|
+
else if (SIMPLE_ESCAPES.has(next)) value = SIMPLE_ESCAPES.get(next);
|
|
405
|
+
else if (next === "x") {
|
|
406
|
+
end = i + 4;
|
|
407
|
+
value = String.fromCharCode(Number.parseInt(inner.slice(i + 2, end), 16));
|
|
408
|
+
} else if (next === "u") {
|
|
409
|
+
if (inner.charAt(i + 2) === "{") {
|
|
410
|
+
end = inner.indexOf("}", i + 3) + 1;
|
|
411
|
+
value = String.fromCodePoint(Number.parseInt(inner.slice(i + 3, end - 1), 16));
|
|
412
|
+
} else {
|
|
413
|
+
end = i + 6;
|
|
414
|
+
value = String.fromCharCode(Number.parseInt(inner.slice(i + 2, end), 16));
|
|
415
|
+
}
|
|
416
|
+
} else if (/[0-7]/.test(next)) {
|
|
417
|
+
const octal = /^[0-7]{1,3}/.exec(inner.slice(i + 1));
|
|
418
|
+
end = i + 1 + octal[0].length;
|
|
419
|
+
value = String.fromCharCode(Number.parseInt(octal[0], 8));
|
|
420
|
+
}
|
|
421
|
+
push(value, i, end);
|
|
422
|
+
i = end;
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
cooked: units.join(""),
|
|
426
|
+
rawStart,
|
|
427
|
+
rawEnd
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/** Apply edits made against `decoded.cooked` to the raw interior they came from. */
|
|
431
|
+
function applyCookedEdits(inner, decoded, edits) {
|
|
432
|
+
const total = decoded.rawStart.length;
|
|
433
|
+
return applyEdits(inner, edits.map((edit) => {
|
|
434
|
+
const start = edit.start < total ? decoded.rawStart[edit.start] : inner.length;
|
|
435
|
+
return {
|
|
436
|
+
start,
|
|
437
|
+
end: edit.end > edit.start ? decoded.rawEnd[edit.end - 1] : start,
|
|
438
|
+
text: edit.text
|
|
439
|
+
};
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* jscodeshift transform — renames cmdk attributes in JSX, selectors in string and template
|
|
444
|
+
* literals, and Tailwind state variants on cmdk-bound elements.
|
|
32
445
|
*/
|
|
33
446
|
function transform(fileInfo, api) {
|
|
34
447
|
if (!fileInfo.source.includes("cmdk")) return fileInfo.source;
|
|
35
448
|
const j = api.jscodeshift;
|
|
36
449
|
const root = j(fileInfo.source);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
450
|
+
const sites = [];
|
|
451
|
+
let sampleRaw;
|
|
452
|
+
let changed = false;
|
|
453
|
+
const reportAt = (text, firstLine) => {
|
|
454
|
+
return (index, message) => {
|
|
455
|
+
const line = firstLine === void 0 ? "" : `line ${lineAt(text, index, firstLine)}: `;
|
|
456
|
+
api.report(line + message);
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
const startOf = (node) => node.start ?? 0;
|
|
460
|
+
const bound = /* @__PURE__ */ new Set();
|
|
461
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
462
|
+
if (!CMDK_SOURCES.has(String(path.node.source.value))) return;
|
|
463
|
+
for (const specifier of path.node.specifiers ?? []) {
|
|
464
|
+
const local = specifier.local?.name;
|
|
465
|
+
if (typeof local === "string") bound.add(local);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
const isBound = (name) => {
|
|
469
|
+
let current = name;
|
|
470
|
+
while (current?.type === "JSXMemberExpression") current = current.object;
|
|
471
|
+
return current?.type === "JSXIdentifier" && bound.has(current.name);
|
|
472
|
+
};
|
|
473
|
+
const tailwind = /* @__PURE__ */ new WeakSet();
|
|
474
|
+
root.find(j.JSXAttribute).forEach((path) => {
|
|
475
|
+
const attrName = path.node.name;
|
|
476
|
+
if (attrName.type !== "JSXIdentifier" || !CLASS_ATTRS.has(attrName.name)) return;
|
|
477
|
+
const element = path.parent?.node;
|
|
478
|
+
if (!isBound(element?.name)) return;
|
|
479
|
+
j(path).find(j.Literal).forEach((p) => void tailwind.add(p.node));
|
|
480
|
+
j(path).find(j.TemplateLiteral).forEach((p) => void tailwind.add(p.node));
|
|
481
|
+
});
|
|
482
|
+
root.find(j.JSXAttribute).forEach((path) => {
|
|
483
|
+
const attrName = path.node.name;
|
|
484
|
+
if (attrName.type !== "JSXIdentifier") return;
|
|
485
|
+
const target = ATTR_MAP.get(attrName.name);
|
|
486
|
+
const reason = UNMAPPED_ATTRS.get(attrName.name);
|
|
487
|
+
if (target === void 0 && reason === void 0) return;
|
|
488
|
+
const node = path.node;
|
|
489
|
+
sites.push({
|
|
490
|
+
pos: startOf(node),
|
|
491
|
+
run: () => {
|
|
492
|
+
if (target !== void 0) {
|
|
493
|
+
attrName.name = target;
|
|
494
|
+
changed = true;
|
|
495
|
+
} else reportAt("", node.loc?.start?.line === void 0 ? void 0 : node.loc.start.line)(0, `${attrName.name} has no modern-cmdk equivalent: ${reason}; left unchanged`);
|
|
496
|
+
}
|
|
42
497
|
});
|
|
43
498
|
});
|
|
44
|
-
root.find(j.
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
499
|
+
root.find(j.Literal).forEach((path) => {
|
|
500
|
+
const node = path.node;
|
|
501
|
+
if (typeof node.value !== "string" || node.type === "JSXText") return;
|
|
502
|
+
const original = node.value;
|
|
503
|
+
const raw = rawOf(node);
|
|
504
|
+
const inJsxAttribute = path.parent?.node?.type === "JSXAttribute";
|
|
505
|
+
const useTailwind = tailwind.has(path.node);
|
|
506
|
+
sites.push({
|
|
507
|
+
pos: startOf(node),
|
|
508
|
+
run: () => {
|
|
509
|
+
const quote = raw?.[0];
|
|
510
|
+
if (!(quote === "\"" || quote === "'")) {
|
|
511
|
+
const report = reportAt(original, node.loc?.start?.line);
|
|
512
|
+
let rewritten = rewriteSelectors(original, false, report, useTailwind);
|
|
513
|
+
if (useTailwind) rewritten = rewriteTailwind(rewritten, report);
|
|
514
|
+
if (rewritten === original) return;
|
|
515
|
+
changed = true;
|
|
516
|
+
path.node.value = rewritten;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const inner = raw.slice(1, -1);
|
|
520
|
+
const decoded = inJsxAttribute ? {
|
|
521
|
+
cooked: inner,
|
|
522
|
+
rawStart: Array.from({ length: inner.length }, (_, k) => k),
|
|
523
|
+
rawEnd: Array.from({ length: inner.length }, (_, k) => k + 1)
|
|
524
|
+
} : decodeRaw(inner);
|
|
525
|
+
const report = reportAt(decoded.cooked, node.loc?.start?.line);
|
|
526
|
+
const edits = selectorEdits(decoded.cooked, false, report, useTailwind);
|
|
527
|
+
if (useTailwind) edits.push(...tailwindEdits(decoded.cooked, report));
|
|
528
|
+
if (edits.length === 0) return;
|
|
529
|
+
const edited = applyCookedEdits(inner, decoded, edits);
|
|
530
|
+
if (edited === inner) return;
|
|
531
|
+
changed = true;
|
|
532
|
+
if (!inJsxAttribute) sampleRaw ??= raw;
|
|
533
|
+
path.replace(j.jsxText(quote + edited + quote));
|
|
534
|
+
}
|
|
535
|
+
});
|
|
51
536
|
});
|
|
52
537
|
root.find(j.TemplateLiteral).forEach((path) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
538
|
+
const literal = path.node;
|
|
539
|
+
const useTailwind = tailwind.has(path.node);
|
|
540
|
+
sites.push({
|
|
541
|
+
pos: startOf(literal),
|
|
542
|
+
run: () => {
|
|
543
|
+
const quasis = literal.quasis;
|
|
544
|
+
const original = quasis.map((q) => q.value.raw).join(PLACEHOLDER);
|
|
545
|
+
const firstLine = literal.loc?.start?.line;
|
|
546
|
+
const placeholderAt = [];
|
|
547
|
+
for (let k = original.indexOf(PLACEHOLDER); k !== -1; k = original.indexOf(PLACEHOLDER, k + 1)) placeholderAt.push(k);
|
|
548
|
+
const report = (index, message) => {
|
|
549
|
+
let line = "";
|
|
550
|
+
if (firstLine !== void 0) {
|
|
551
|
+
let n = lineAt(original, index, firstLine);
|
|
552
|
+
placeholderAt.forEach((at, k) => {
|
|
553
|
+
const loc = literal.expressions[k]?.loc;
|
|
554
|
+
if (at < index && loc?.start && loc.end) n += loc.end.line - loc.start.line;
|
|
555
|
+
});
|
|
556
|
+
line = `line ${n}: `;
|
|
557
|
+
}
|
|
558
|
+
api.report(line + message.replaceAll(PLACEHOLDER, PLACEHOLDER_TEXT));
|
|
559
|
+
};
|
|
560
|
+
let rewritten = rewriteSelectors(original, false, report, useTailwind);
|
|
561
|
+
if (useTailwind) rewritten = rewriteTailwind(rewritten, report);
|
|
562
|
+
if (rewritten === original) return;
|
|
563
|
+
const parts = rewritten.split(PLACEHOLDER);
|
|
564
|
+
if (parts.length !== quasis.length) return;
|
|
565
|
+
quasis.forEach((quasi, k) => {
|
|
566
|
+
const part = parts[k];
|
|
567
|
+
if (part === quasi.value.raw) return;
|
|
568
|
+
if (quasi.value.cooked === quasi.value.raw) quasi.value.cooked = part;
|
|
569
|
+
quasi.value.raw = part;
|
|
570
|
+
});
|
|
571
|
+
changed = true;
|
|
60
572
|
}
|
|
61
|
-
}
|
|
573
|
+
});
|
|
62
574
|
});
|
|
63
|
-
|
|
575
|
+
for (const site of sites.sort((a, b) => a.pos - b.pos)) site.run();
|
|
576
|
+
return changed ? root.toSource(printOptions(fileInfo.source, sampleRaw)) : fileInfo.source;
|
|
64
577
|
}
|
|
65
|
-
export { transform as default };
|
|
578
|
+
export { ATTR_MAP, CSS_VAR_MAP, UNMAPPED_ATTRS, transform as default, transformStylesheet };
|