@dialpad/dialtone-css 8.80.0-next.3 → 8.80.0-next.5
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/lib/build/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
- package/lib/build/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
- package/lib/build/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
- package/lib/build/less/components/box.less +2 -0
- package/lib/build/less/components/link.less +14 -19
- package/lib/build/less/components/notice.less +1 -1
- package/lib/build/less/components/rich-text-editor.less +24 -0
- package/lib/build/less/components/scrollbar.less +22 -0
- package/lib/build/less/dialtone.less +7 -0
- package/lib/dist/dialtone-default-theme.css +334 -293
- package/lib/dist/dialtone-default-theme.min.css +1 -1
- package/lib/dist/dialtone-docs.json +1 -1
- package/lib/dist/dialtone.css +50 -15
- package/lib/dist/dialtone.min.css +1 -1
- package/lib/dist/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
- package/lib/dist/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
- package/lib/dist/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
- package/lib/dist/tokens/tokens-101-dark.css +141 -135
- package/lib/dist/tokens/tokens-101-light.css +164 -158
- package/lib/dist/tokens/tokens-102-dark.css +141 -135
- package/lib/dist/tokens/tokens-102-light.css +164 -158
- package/lib/dist/tokens/tokens-103-dark.css +141 -135
- package/lib/dist/tokens/tokens-103-light.css +164 -158
- package/lib/dist/tokens/tokens-104-dark.css +141 -135
- package/lib/dist/tokens/tokens-104-light.css +164 -158
- package/lib/dist/tokens/tokens-105-dark.css +141 -135
- package/lib/dist/tokens/tokens-105-light.css +164 -158
- package/lib/dist/tokens/tokens-106-dark.css +141 -135
- package/lib/dist/tokens/tokens-106-light.css +164 -158
- package/lib/dist/tokens/tokens-107-dark.css +141 -135
- package/lib/dist/tokens/tokens-107-light.css +164 -158
- package/lib/dist/tokens/tokens-108-dark.css +141 -135
- package/lib/dist/tokens/tokens-108-light.css +164 -158
- package/lib/dist/tokens/tokens-109-dark.css +141 -135
- package/lib/dist/tokens/tokens-109-light.css +164 -158
- package/lib/dist/tokens/tokens-110-dark.css +141 -135
- package/lib/dist/tokens/tokens-110-light.css +164 -158
- package/lib/dist/tokens/tokens-111-dark.css +141 -135
- package/lib/dist/tokens/tokens-111-light.css +164 -158
- package/lib/dist/tokens/tokens-112-dark.css +141 -135
- package/lib/dist/tokens/tokens-112-light.css +164 -158
- package/lib/dist/tokens/tokens-113-dark.css +141 -135
- package/lib/dist/tokens/tokens-113-light.css +164 -158
- package/lib/dist/tokens/tokens-114-dark.css +141 -135
- package/lib/dist/tokens/tokens-114-light.css +164 -158
- package/lib/dist/tokens/tokens-115-dark.css +141 -135
- package/lib/dist/tokens/tokens-115-light.css +164 -158
- package/lib/dist/tokens/tokens-116-dark.css +141 -135
- package/lib/dist/tokens/tokens-116-light.css +164 -158
- package/lib/dist/tokens/tokens-117-dark.css +141 -135
- package/lib/dist/tokens/tokens-117-light.css +164 -158
- package/lib/dist/tokens/tokens-118-dark.css +141 -135
- package/lib/dist/tokens/tokens-118-light.css +164 -158
- package/lib/dist/tokens/tokens-119-dark.css +141 -135
- package/lib/dist/tokens/tokens-119-light.css +164 -158
- package/lib/dist/tokens/tokens-120-dark.css +141 -135
- package/lib/dist/tokens/tokens-120-light.css +164 -158
- package/lib/dist/tokens/tokens-121-dark.css +141 -135
- package/lib/dist/tokens/tokens-121-light.css +164 -158
- package/lib/dist/tokens/tokens-122-dark.css +141 -135
- package/lib/dist/tokens/tokens-122-light.css +164 -158
- package/lib/dist/tokens/tokens-123-dark.css +141 -135
- package/lib/dist/tokens/tokens-123-light.css +164 -158
- package/lib/dist/tokens/tokens-124-dark.css +141 -135
- package/lib/dist/tokens/tokens-124-light.css +164 -158
- package/lib/dist/tokens/tokens-125-dark.css +141 -135
- package/lib/dist/tokens/tokens-125-light.css +164 -158
- package/lib/dist/tokens/tokens-126-dark.css +141 -135
- package/lib/dist/tokens/tokens-126-light.css +164 -158
- package/lib/dist/tokens/tokens-127-dark.css +141 -135
- package/lib/dist/tokens/tokens-127-light.css +164 -158
- package/lib/dist/tokens/tokens-128-dark.css +141 -135
- package/lib/dist/tokens/tokens-128-light.css +164 -158
- package/lib/dist/tokens/tokens-129-dark.css +141 -135
- package/lib/dist/tokens/tokens-129-light.css +164 -158
- package/lib/dist/tokens/tokens-130-dark.css +141 -135
- package/lib/dist/tokens/tokens-130-light.css +164 -158
- package/lib/dist/tokens/tokens-131-dark.css +141 -135
- package/lib/dist/tokens/tokens-131-light.css +164 -158
- package/lib/dist/tokens/tokens-132-dark.css +141 -135
- package/lib/dist/tokens/tokens-132-light.css +164 -158
- package/lib/dist/tokens/tokens-133-dark.css +141 -135
- package/lib/dist/tokens/tokens-133-light.css +164 -158
- package/lib/dist/tokens/tokens-134-dark.css +141 -135
- package/lib/dist/tokens/tokens-134-light.css +164 -158
- package/lib/dist/tokens/tokens-135-dark.css +141 -135
- package/lib/dist/tokens/tokens-135-light.css +164 -158
- package/lib/dist/tokens/tokens-136-dark.css +141 -135
- package/lib/dist/tokens/tokens-136-light.css +164 -158
- package/lib/dist/tokens/tokens-137-dark.css +141 -135
- package/lib/dist/tokens/tokens-137-light.css +164 -158
- package/lib/dist/tokens/tokens-aegean-dark.css +164 -158
- package/lib/dist/tokens/tokens-aegean-light.css +184 -178
- package/lib/dist/tokens/tokens-base-dark.css +98 -98
- package/lib/dist/tokens/tokens-base-light.css +98 -98
- package/lib/dist/tokens/tokens-botany-dark.css +149 -143
- package/lib/dist/tokens/tokens-botany-light.css +171 -165
- package/lib/dist/tokens/tokens-buttercream-dark.css +148 -142
- package/lib/dist/tokens/tokens-buttercream-light.css +171 -165
- package/lib/dist/tokens/tokens-ceruleo-dark.css +164 -158
- package/lib/dist/tokens/tokens-ceruleo-light.css +184 -178
- package/lib/dist/tokens/tokens-contrast-high-dark.css +3 -3
- package/lib/dist/tokens/tokens-contrast-high-light.css +10 -10
- package/lib/dist/tokens/tokens-debug-dp.css +28 -22
- package/lib/dist/tokens/tokens-dp-dark.css +166 -160
- package/lib/dist/tokens/tokens-dp-light.css +186 -180
- package/lib/dist/tokens/tokens-expressive-dark.css +163 -157
- package/lib/dist/tokens/tokens-expressive-light.css +186 -180
- package/lib/dist/tokens/tokens-expressive-sm-dark.css +163 -157
- package/lib/dist/tokens/tokens-expressive-sm-light.css +186 -180
- package/lib/dist/tokens/tokens-high-desert-dark.css +144 -138
- package/lib/dist/tokens/tokens-high-desert-light.css +164 -158
- package/lib/dist/tokens/tokens-melon-dark.css +145 -139
- package/lib/dist/tokens/tokens-melon-light.css +165 -159
- package/lib/dist/tokens/tokens-plum-dark.css +153 -147
- package/lib/dist/tokens/tokens-plum-light.css +164 -158
- package/lib/dist/tokens/tokens-prota-deuter-dark.css +292 -262
- package/lib/dist/tokens/tokens-prota-deuter-light.css +315 -285
- package/lib/dist/tokens/tokens-sunflower-dark.css +154 -148
- package/lib/dist/tokens/tokens-sunflower-light.css +174 -168
- package/lib/dist/tokens/tokens-tmo-dark.css +165 -159
- package/lib/dist/tokens/tokens-tmo-light.css +185 -179
- package/lib/dist/tokens/tokens-trita-dark.css +239 -197
- package/lib/dist/tokens/tokens-trita-light.css +263 -221
- package/lib/dist/tokens/tokens-verdant-haze-dark.css +144 -138
- package/lib/dist/tokens/tokens-verdant-haze-light.css +164 -158
- package/lib/dist/tokens-docs.json +1 -1
- package/package.json +11 -4
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Migration script for DtChip `interactive` prop default change.
|
|
5
|
+
*
|
|
6
|
+
* DLT-3195 DtChip `interactive` prop default changed from `true` → `false`.
|
|
7
|
+
* Chips that need click/keyboard behavior must now explicitly set
|
|
8
|
+
* `:interactive="true"`.
|
|
9
|
+
*
|
|
10
|
+
* This script:
|
|
11
|
+
* - Adds `:interactive="true"` to <dt-chip> tags that have a click event
|
|
12
|
+
* listener (@click, v-on:click) or an object v-on binding (v-on="…"),
|
|
13
|
+
* since those clearly need interactive behavior.
|
|
14
|
+
* - Skips chips that already set the `interactive` prop (any form).
|
|
15
|
+
* - Warns about remaining chips with no `interactive` prop and no detected
|
|
16
|
+
* click handler — these may be display-only (no change needed) or may
|
|
17
|
+
* need `:interactive="true"` added manually.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* npx dialtone-migrate-chip-interactive [options]
|
|
21
|
+
*
|
|
22
|
+
* Options:
|
|
23
|
+
* --cwd <path> Working directory (default: cwd)
|
|
24
|
+
* --dry-run Show changes without applying them
|
|
25
|
+
* --yes Apply all changes without prompting
|
|
26
|
+
* --help Show help
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import fs from 'fs/promises';
|
|
30
|
+
import { realpathSync } from 'node:fs';
|
|
31
|
+
import path from 'path';
|
|
32
|
+
import readline from 'readline';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Constants
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
// Quote-aware attribute body. Matches sequences of non-quote/non-gt chars
|
|
40
|
+
// optionally followed by a fully-quoted attribute value, so `>` inside a
|
|
41
|
+
// quoted value like `:class="a > b"` does not prematurely terminate the tag.
|
|
42
|
+
const QUOTE_AWARE_ATTRS = '(?:[^>"\']|"[^"]*"|\'[^\']*\')*';
|
|
43
|
+
|
|
44
|
+
// Matches `<dt-chip` or `<DtChip` opening tags (including self-closing).
|
|
45
|
+
// Group 1: tag name; group 2: attributes (quote-aware); group 3: closer (`>` or `/>`).
|
|
46
|
+
const CHIP_TAG_RE = new RegExp(
|
|
47
|
+
`(<(?:dt-chip|DtChip)\\b)(${QUOTE_AWARE_ATTRS})(\\s*\\/?>)`,
|
|
48
|
+
'g',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Detects that the `interactive` prop is already present in any form:
|
|
52
|
+
// interactive, :interactive, v-bind:interactive
|
|
53
|
+
const HAS_INTERACTIVE_RE = /(?:^|\s)(?::|v-bind:)?interactive(?:\s*=|\s|\/|>)/;
|
|
54
|
+
|
|
55
|
+
// Detects a click event listener:
|
|
56
|
+
// @click, v-on:click, @click.stop, @click.prevent, etc.
|
|
57
|
+
const HAS_CLICK_RE = /(?:^|\s)(?:@click|v-on:click)(?:\s*=|\s*\.|\/|>|\s)/;
|
|
58
|
+
|
|
59
|
+
// Detects an object-form v-on binding (v-on="…") which may contain click.
|
|
60
|
+
// We treat this conservatively as "may be interactive" and add the prop.
|
|
61
|
+
const HAS_VON_OBJECT_RE = /(?:^|\s)v-on\s*=\s*(?:"[^"]*"|'[^']*')/;
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Transform
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Strip quoted attribute values from an attrs string before regex testing.
|
|
69
|
+
* Replaces "…" and '…' with empty equivalents so keywords that happen to
|
|
70
|
+
* appear inside a quoted value (e.g. :title=" @click is cool") don't
|
|
71
|
+
* false-positive against HAS_CLICK_RE / HAS_INTERACTIVE_RE / HAS_VON_OBJECT_RE.
|
|
72
|
+
* Real attribute tokens like @click="handler" survive as @click="" and still match.
|
|
73
|
+
*/
|
|
74
|
+
function stripQuotedValues (attrs) {
|
|
75
|
+
return attrs.replace(/"[^"]*"|'[^']*'/g, match => (match[0] === '"' ? '""' : '\'\''));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find the position to insert `:interactive="true"` — immediately after the
|
|
80
|
+
* tag name so it appears first in the attribute list (consistent with existing
|
|
81
|
+
* Dialtone convention where `:interactive` is an early structural prop).
|
|
82
|
+
*
|
|
83
|
+
* Returns the index within `attrs` where the insertion should happen.
|
|
84
|
+
* We insert after any leading whitespace on the attribute string.
|
|
85
|
+
*/
|
|
86
|
+
function insertInteractiveProp (attrs) {
|
|
87
|
+
const leadingSpace = attrs.match(/^\s*/)[0];
|
|
88
|
+
const rest = attrs.slice(leadingSpace.length);
|
|
89
|
+
// Preserve the original leading whitespace, then prepend the prop
|
|
90
|
+
return `${leadingSpace}:interactive="true" ${rest}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Transform a single file's content.
|
|
95
|
+
* Returns { transformed, warnings } where warnings are strings.
|
|
96
|
+
*/
|
|
97
|
+
export function transformContent (content, opts = {}) {
|
|
98
|
+
const filePath = opts.filePath || '<input>';
|
|
99
|
+
const warnings = [];
|
|
100
|
+
|
|
101
|
+
// Fast path: skip files with no dt-chip / DtChip reference at all.
|
|
102
|
+
if (!/(?:dt-chip|DtChip)/i.test(content)) {
|
|
103
|
+
return { transformed: content, warnings };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Mask inert content (HTML comments, <script>, <style>) so we don't
|
|
107
|
+
// accidentally match tag-like text inside them.
|
|
108
|
+
const { masked, segments, token } = maskInertContent(content);
|
|
109
|
+
|
|
110
|
+
let out = masked;
|
|
111
|
+
const replacements = [];
|
|
112
|
+
|
|
113
|
+
// Reset lastIndex before iterating
|
|
114
|
+
CHIP_TAG_RE.lastIndex = 0;
|
|
115
|
+
|
|
116
|
+
let m;
|
|
117
|
+
while ((m = CHIP_TAG_RE.exec(out)) !== null) {
|
|
118
|
+
const [fullMatch, openTag, attrs, closer] = m;
|
|
119
|
+
const matchStart = m.index;
|
|
120
|
+
const matchEnd = matchStart + fullMatch.length;
|
|
121
|
+
|
|
122
|
+
// Strip quoted values before regex testing so keywords inside quoted
|
|
123
|
+
// attribute values don't produce false positives.
|
|
124
|
+
const attrsForTest = stripQuotedValues(attrs);
|
|
125
|
+
|
|
126
|
+
// Already has the interactive prop — nothing to do.
|
|
127
|
+
if (HAS_INTERACTIVE_RE.test(attrsForTest)) continue;
|
|
128
|
+
|
|
129
|
+
const hasClick = HAS_CLICK_RE.test(attrsForTest);
|
|
130
|
+
const hasVOnObject = HAS_VON_OBJECT_RE.test(attrsForTest);
|
|
131
|
+
|
|
132
|
+
if (hasClick || hasVOnObject) {
|
|
133
|
+
// Auto-add :interactive="true"
|
|
134
|
+
const newAttrs = insertInteractiveProp(attrs);
|
|
135
|
+
replacements.push({
|
|
136
|
+
start: matchStart,
|
|
137
|
+
end: matchEnd,
|
|
138
|
+
text: `${openTag}${newAttrs}${closer}`,
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
// No click handler and no interactive prop.
|
|
142
|
+
// Warn: this chip will now render as a <span>. May be intentional
|
|
143
|
+
// (display-only) or may need :interactive="true" manually.
|
|
144
|
+
warnings.push(
|
|
145
|
+
`${filePath}: <dt-chip> has no interactive prop and no @click handler — ` +
|
|
146
|
+
`will now render as a non-interactive <span>. ` +
|
|
147
|
+
`Add :interactive="true" if this chip should be clickable.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Apply replacements in reverse order to preserve indices
|
|
153
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
154
|
+
for (const r of replacements) {
|
|
155
|
+
out = out.slice(0, r.start) + r.text + out.slice(r.end);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { transformed: unmaskInertContent(out, segments, token), warnings };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Inert-content masking (same pattern as dialtone_migrate_link_rendering)
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
function maskInertContent (content) {
|
|
166
|
+
const token = Math.random().toString(36).slice(2, 10);
|
|
167
|
+
const innerRe = /<!--[\s\S]*?-->|<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>/g;
|
|
168
|
+
const segments = [];
|
|
169
|
+
const masked = content.replace(innerRe, (match) => {
|
|
170
|
+
const placeholder = ` DT_MIGRATE_INERT_${token}_${segments.length} `;
|
|
171
|
+
segments.push(match);
|
|
172
|
+
return placeholder;
|
|
173
|
+
});
|
|
174
|
+
return { masked, segments, token };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function unmaskInertContent (masked, segments, token) {
|
|
178
|
+
return masked.replace(new RegExp(` DT_MIGRATE_INERT_${token}_(\\d+) `, 'g'), (_, idx) => segments[Number(idx)]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// File walker
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
function isIgnoredPath (fullPath, ignore) {
|
|
186
|
+
const segments = fullPath.split(path.sep);
|
|
187
|
+
return ignore.some(ig => {
|
|
188
|
+
if (ig.includes('/')) {
|
|
189
|
+
const parts = ig.split('/');
|
|
190
|
+
for (let i = 0; i + parts.length <= segments.length; i++) {
|
|
191
|
+
if (parts.every((p, j) => segments[i + j] === p)) return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return segments.includes(ig);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function findFiles (dir, extensions, ignore = []) {
|
|
200
|
+
const results = [];
|
|
201
|
+
async function walk (currentDir) {
|
|
202
|
+
let entries;
|
|
203
|
+
try {
|
|
204
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
205
|
+
} catch {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
210
|
+
if (isIgnoredPath(fullPath, ignore)) continue;
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
await walk(fullPath);
|
|
213
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
214
|
+
results.push(fullPath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
await walk(dir);
|
|
219
|
+
return results;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// CLI plumbing
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
function printHelp () {
|
|
227
|
+
console.log(`
|
|
228
|
+
Usage: npx dialtone-migrate-chip-interactive [options]
|
|
229
|
+
|
|
230
|
+
Migrates DtChip usage after the \`interactive\` prop default changed from
|
|
231
|
+
\`true\` to \`false\` (DLT-3195).
|
|
232
|
+
|
|
233
|
+
Chips with a @click handler or v-on object binding automatically receive
|
|
234
|
+
:interactive="true". All other chips without an existing interactive prop
|
|
235
|
+
are listed as warnings for manual review.
|
|
236
|
+
|
|
237
|
+
Options:
|
|
238
|
+
--cwd <path> Working directory (default: cwd)
|
|
239
|
+
--dry-run Show changes without applying them
|
|
240
|
+
--yes Apply all changes without prompting
|
|
241
|
+
--help Show help
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
npx dialtone-migrate-chip-interactive
|
|
245
|
+
npx dialtone-migrate-chip-interactive --dry-run
|
|
246
|
+
npx dialtone-migrate-chip-interactive --cwd ./src
|
|
247
|
+
npx dialtone-migrate-chip-interactive --yes
|
|
248
|
+
`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseArgs (args) {
|
|
252
|
+
const cwdIndex = args.indexOf('--cwd');
|
|
253
|
+
return {
|
|
254
|
+
help: args.includes('--help'),
|
|
255
|
+
dryRun: args.includes('--dry-run'),
|
|
256
|
+
autoYes: args.includes('--yes'),
|
|
257
|
+
cwd: cwdIndex !== -1 && args[cwdIndex + 1]
|
|
258
|
+
? path.resolve(args[cwdIndex + 1])
|
|
259
|
+
: process.cwd(),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function prompt (question) {
|
|
264
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
265
|
+
return new Promise(resolve => {
|
|
266
|
+
rl.question(question, answer => {
|
|
267
|
+
rl.close();
|
|
268
|
+
resolve(answer.trim().toLowerCase());
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function scanFiles (cwd) {
|
|
274
|
+
const extensions = ['.vue'];
|
|
275
|
+
const ignore = ['node_modules', 'dist', '.git', '.vuepress/public', '.vuepress/.temp', '.vuepress/.cache'];
|
|
276
|
+
const files = await findFiles(cwd, extensions, ignore);
|
|
277
|
+
|
|
278
|
+
const changes = [];
|
|
279
|
+
const allWarnings = [];
|
|
280
|
+
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
const content = await fs.readFile(file, 'utf8');
|
|
283
|
+
const { transformed, warnings } = transformContent(content, {
|
|
284
|
+
filePath: path.relative(cwd, file),
|
|
285
|
+
});
|
|
286
|
+
if (transformed !== content) {
|
|
287
|
+
changes.push({ file, content, transformed });
|
|
288
|
+
}
|
|
289
|
+
if (warnings.length) allWarnings.push(...warnings);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { changes, allWarnings };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function applyChanges (changes, autoYes) {
|
|
296
|
+
if (!autoYes) {
|
|
297
|
+
const answer = await prompt('\nApply changes? (y/N) ');
|
|
298
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
299
|
+
console.log('Cancelled.');
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const { file, transformed } of changes) {
|
|
304
|
+
await fs.writeFile(file, transformed, 'utf8');
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function printWarnings (warnings) {
|
|
310
|
+
if (!warnings.length) return;
|
|
311
|
+
console.log('\nWarnings — manual review required:\n');
|
|
312
|
+
for (const w of warnings) console.log(` ${w}`);
|
|
313
|
+
console.log();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function printChangeSummary (changes, cwd) {
|
|
317
|
+
console.log(`\nFound changes in ${changes.length} file(s):\n`);
|
|
318
|
+
for (const { file } of changes) {
|
|
319
|
+
console.log(` ${path.relative(cwd, file)}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function main () {
|
|
324
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
325
|
+
if (opts.help) {
|
|
326
|
+
printHelp();
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`\nScanning ${opts.cwd} for DtChip usages...`);
|
|
331
|
+
|
|
332
|
+
const { changes, allWarnings } = await scanFiles(opts.cwd);
|
|
333
|
+
|
|
334
|
+
printWarnings(allWarnings);
|
|
335
|
+
|
|
336
|
+
if (changes.length === 0) {
|
|
337
|
+
console.log(allWarnings.length
|
|
338
|
+
? 'No automated code changes needed. See manual review items above.'
|
|
339
|
+
: 'No DtChip usages found. Nothing to migrate.');
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
printChangeSummary(changes, opts.cwd);
|
|
344
|
+
|
|
345
|
+
if (opts.dryRun) {
|
|
346
|
+
console.log('\n--dry-run: No files were modified.');
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const applied = await applyChanges(changes, opts.autoYes);
|
|
351
|
+
if (applied) console.log(`\nMigrated ${changes.length} file(s).\n`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const isDirectRun = (() => {
|
|
355
|
+
try {
|
|
356
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
357
|
+
} catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
})();
|
|
361
|
+
|
|
362
|
+
if (isDirectRun) {
|
|
363
|
+
main().catch(err => {
|
|
364
|
+
console.error(err);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DLT-3195 — dialtone-migrate-chip-interactive tests.
|
|
3
|
+
*
|
|
4
|
+
* One assertion per test; data-driven via for..of where multiple cases share a concept.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { transformContent } from './index.mjs';
|
|
10
|
+
|
|
11
|
+
function run (input) {
|
|
12
|
+
const { transformed } = transformContent(input, { filePath: 'test.vue' });
|
|
13
|
+
return transformed;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function warnings (input) {
|
|
17
|
+
return transformContent(input, { filePath: 'test.vue' }).warnings;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Auto-add :interactive="true" for chips with click handlers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe('quoted value false-positive prevention', () => {
|
|
25
|
+
it('does not false-positive on @click inside a quoted attribute value', () => {
|
|
26
|
+
const input = '<dt-chip :title=" @click is cool">Label</dt-chip>';
|
|
27
|
+
assert.equal(run(input), input);
|
|
28
|
+
assert.equal(warnings(input).length, 1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('does not false-positive on interactive inside a quoted attribute value', () => {
|
|
32
|
+
// HAS_INTERACTIVE_RE must not fire on this — chip still needs fixing
|
|
33
|
+
const input = '<dt-chip :aria-label="set interactive prop" @click="go">Label</dt-chip>';
|
|
34
|
+
const expected = '<dt-chip :interactive="true" :aria-label="set interactive prop" @click="go">Label</dt-chip>';
|
|
35
|
+
assert.equal(run(input), expected);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('real :interactive prop still prevents insertion', () => {
|
|
39
|
+
const input = '<dt-chip :interactive="false" @click="go">Label</dt-chip>';
|
|
40
|
+
assert.equal(run(input), input);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('quote-aware attribute parsing — > inside quoted value', () => {
|
|
45
|
+
it('handles > inside a quoted attribute value before @click', () => {
|
|
46
|
+
const input = '<dt-chip :class="a > b" @click="onClick">Label</dt-chip>';
|
|
47
|
+
const expected = '<dt-chip :interactive="true" :class="a > b" @click="onClick">Label</dt-chip>';
|
|
48
|
+
assert.equal(run(input), expected);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not warn when @click is present but attrs contain quoted >', () => {
|
|
52
|
+
const input = '<dt-chip :class="a > b" @click="onClick">Label</dt-chip>';
|
|
53
|
+
assert.equal(warnings(input).length, 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles > inside a single-quoted attribute value', () => {
|
|
57
|
+
const input = '<dt-chip :title="x > y" @click="onClick">Label</dt-chip>';
|
|
58
|
+
const expected = '<dt-chip :interactive="true" :title="x > y" @click="onClick">Label</dt-chip>';
|
|
59
|
+
assert.equal(run(input), expected);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('chips with @click — auto-add :interactive="true"', () => {
|
|
64
|
+
const cases = [
|
|
65
|
+
[
|
|
66
|
+
'single-line chip with @click',
|
|
67
|
+
'<dt-chip @click="handleClick">Label</dt-chip>',
|
|
68
|
+
'<dt-chip :interactive="true" @click="handleClick">Label</dt-chip>',
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
'chip with @click.stop modifier',
|
|
72
|
+
'<dt-chip @click.stop="handleClick">Label</dt-chip>',
|
|
73
|
+
'<dt-chip :interactive="true" @click.stop="handleClick">Label</dt-chip>',
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
'chip with @click.prevent modifier',
|
|
77
|
+
'<dt-chip @click.prevent="handleClick">Label</dt-chip>',
|
|
78
|
+
'<dt-chip :interactive="true" @click.prevent="handleClick">Label</dt-chip>',
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
'chip with v-on:click',
|
|
82
|
+
'<dt-chip v-on:click="handleClick">Label</dt-chip>',
|
|
83
|
+
'<dt-chip :interactive="true" v-on:click="handleClick">Label</dt-chip>',
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
'chip with other props and @click',
|
|
87
|
+
'<dt-chip :size="200" :disabled="isDisabled" @click="handleClick">Label</dt-chip>',
|
|
88
|
+
'<dt-chip :interactive="true" :size="200" :disabled="isDisabled" @click="handleClick">Label</dt-chip>',
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
'self-closing chip with @click',
|
|
92
|
+
'<dt-chip @click="handleClick" />',
|
|
93
|
+
'<dt-chip :interactive="true" @click="handleClick" />',
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
'PascalCase DtChip with @click',
|
|
97
|
+
'<DtChip @click="handleClick">Label</DtChip>',
|
|
98
|
+
'<DtChip :interactive="true" @click="handleClick">Label</DtChip>',
|
|
99
|
+
],
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const [label, input, expected] of cases) {
|
|
103
|
+
it(label, () => {
|
|
104
|
+
assert.equal(run(input), expected);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('chips with v-on object binding — auto-add :interactive="true"', () => {
|
|
110
|
+
const cases = [
|
|
111
|
+
[
|
|
112
|
+
'chip with v-on object binding (double quotes)',
|
|
113
|
+
'<dt-chip v-on="chipListeners">Label</dt-chip>',
|
|
114
|
+
'<dt-chip :interactive="true" v-on="chipListeners">Label</dt-chip>',
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
'chip with v-on object binding (single quotes)',
|
|
118
|
+
'<dt-chip v-on=\'chipListeners\'>Label</dt-chip>',
|
|
119
|
+
'<dt-chip :interactive="true" v-on=\'chipListeners\'>Label</dt-chip>',
|
|
120
|
+
],
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
for (const [label, input, expected] of cases) {
|
|
124
|
+
it(label, () => {
|
|
125
|
+
assert.equal(run(input), expected);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Skip chips that already have the interactive prop
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
describe('chips that already have interactive prop — no change', () => {
|
|
135
|
+
const cases = [
|
|
136
|
+
[
|
|
137
|
+
'already has :interactive="true"',
|
|
138
|
+
'<dt-chip :interactive="true" @click="handleClick">Label</dt-chip>',
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
'already has :interactive="false"',
|
|
142
|
+
'<dt-chip :interactive="false">Label</dt-chip>',
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
'already has plain interactive',
|
|
146
|
+
'<dt-chip interactive>Label</dt-chip>',
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
'already has v-bind:interactive',
|
|
150
|
+
'<dt-chip v-bind:interactive="isInteractive">Label</dt-chip>',
|
|
151
|
+
],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
for (const [label, input] of cases) {
|
|
155
|
+
it(label, () => {
|
|
156
|
+
assert.equal(run(input), input);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Chips with no click handler — warn, no change
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
describe('chips with no click handler — no change, emit warning', () => {
|
|
166
|
+
it('display-only chip produces no output change', () => {
|
|
167
|
+
const input = '<dt-chip>Label</dt-chip>';
|
|
168
|
+
assert.equal(run(input), input);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('display-only chip emits a warning', () => {
|
|
172
|
+
const input = '<dt-chip>Label</dt-chip>';
|
|
173
|
+
assert.equal(warnings(input).length, 1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('warning message mentions the file path', () => {
|
|
177
|
+
const { warnings: w } = transformContent('<dt-chip>Label</dt-chip>', { filePath: 'src/MyComponent.vue' });
|
|
178
|
+
assert.ok(w[0].includes('src/MyComponent.vue'));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('chip with @close only (no @click) emits warning, no auto-change', () => {
|
|
182
|
+
const input = '<dt-chip @close="onRemove">Label</dt-chip>';
|
|
183
|
+
assert.equal(run(input), input);
|
|
184
|
+
assert.equal(warnings(input).length, 1);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Multiple chips in one file
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
describe('multiple chips in one file', () => {
|
|
193
|
+
it('adds :interactive to the clickable chip only', () => {
|
|
194
|
+
const input = [
|
|
195
|
+
'<dt-chip @click="onClick">Clickable</dt-chip>',
|
|
196
|
+
'<dt-chip>Display</dt-chip>',
|
|
197
|
+
].join('\n');
|
|
198
|
+
const expected = [
|
|
199
|
+
'<dt-chip :interactive="true" @click="onClick">Clickable</dt-chip>',
|
|
200
|
+
'<dt-chip>Display</dt-chip>',
|
|
201
|
+
].join('\n');
|
|
202
|
+
assert.equal(run(input), expected);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('warns once per display-only chip', () => {
|
|
206
|
+
const input = [
|
|
207
|
+
'<dt-chip>Label A</dt-chip>',
|
|
208
|
+
'<dt-chip>Label B</dt-chip>',
|
|
209
|
+
].join('\n');
|
|
210
|
+
assert.equal(warnings(input).length, 2);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Inert content masking — should not match chips in comments or script
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
describe('inert content masking', () => {
|
|
219
|
+
it('does not transform chip inside HTML comment', () => {
|
|
220
|
+
const input = '<!-- <dt-chip @click="x">hidden</dt-chip> -->';
|
|
221
|
+
assert.equal(run(input), input);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('does not transform chip inside <script>', () => {
|
|
225
|
+
const input = '<script>\nconst example = `<dt-chip @click="x">Label</dt-chip>`;\n</script>';
|
|
226
|
+
assert.equal(run(input), input);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Fast path — no-op on files with no dt-chip reference
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
describe('fast path', () => {
|
|
235
|
+
it('returns unchanged content when no dt-chip present', () => {
|
|
236
|
+
const input = '<dt-button @click="x">Click</dt-button>';
|
|
237
|
+
assert.equal(run(input), input);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('emits no warnings when no dt-chip present', () => {
|
|
241
|
+
const input = '<dt-button @click="x">Click</dt-button>';
|
|
242
|
+
assert.equal(warnings(input).length, 0);
|
|
243
|
+
});
|
|
244
|
+
});
|