@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.
Files changed (128) hide show
  1. package/lib/build/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
  2. package/lib/build/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
  3. package/lib/build/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
  4. package/lib/build/less/components/box.less +2 -0
  5. package/lib/build/less/components/link.less +14 -19
  6. package/lib/build/less/components/notice.less +1 -1
  7. package/lib/build/less/components/rich-text-editor.less +24 -0
  8. package/lib/build/less/components/scrollbar.less +22 -0
  9. package/lib/build/less/dialtone.less +7 -0
  10. package/lib/dist/dialtone-default-theme.css +334 -293
  11. package/lib/dist/dialtone-default-theme.min.css +1 -1
  12. package/lib/dist/dialtone-docs.json +1 -1
  13. package/lib/dist/dialtone.css +50 -15
  14. package/lib/dist/dialtone.min.css +1 -1
  15. package/lib/dist/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
  16. package/lib/dist/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
  17. package/lib/dist/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
  18. package/lib/dist/tokens/tokens-101-dark.css +141 -135
  19. package/lib/dist/tokens/tokens-101-light.css +164 -158
  20. package/lib/dist/tokens/tokens-102-dark.css +141 -135
  21. package/lib/dist/tokens/tokens-102-light.css +164 -158
  22. package/lib/dist/tokens/tokens-103-dark.css +141 -135
  23. package/lib/dist/tokens/tokens-103-light.css +164 -158
  24. package/lib/dist/tokens/tokens-104-dark.css +141 -135
  25. package/lib/dist/tokens/tokens-104-light.css +164 -158
  26. package/lib/dist/tokens/tokens-105-dark.css +141 -135
  27. package/lib/dist/tokens/tokens-105-light.css +164 -158
  28. package/lib/dist/tokens/tokens-106-dark.css +141 -135
  29. package/lib/dist/tokens/tokens-106-light.css +164 -158
  30. package/lib/dist/tokens/tokens-107-dark.css +141 -135
  31. package/lib/dist/tokens/tokens-107-light.css +164 -158
  32. package/lib/dist/tokens/tokens-108-dark.css +141 -135
  33. package/lib/dist/tokens/tokens-108-light.css +164 -158
  34. package/lib/dist/tokens/tokens-109-dark.css +141 -135
  35. package/lib/dist/tokens/tokens-109-light.css +164 -158
  36. package/lib/dist/tokens/tokens-110-dark.css +141 -135
  37. package/lib/dist/tokens/tokens-110-light.css +164 -158
  38. package/lib/dist/tokens/tokens-111-dark.css +141 -135
  39. package/lib/dist/tokens/tokens-111-light.css +164 -158
  40. package/lib/dist/tokens/tokens-112-dark.css +141 -135
  41. package/lib/dist/tokens/tokens-112-light.css +164 -158
  42. package/lib/dist/tokens/tokens-113-dark.css +141 -135
  43. package/lib/dist/tokens/tokens-113-light.css +164 -158
  44. package/lib/dist/tokens/tokens-114-dark.css +141 -135
  45. package/lib/dist/tokens/tokens-114-light.css +164 -158
  46. package/lib/dist/tokens/tokens-115-dark.css +141 -135
  47. package/lib/dist/tokens/tokens-115-light.css +164 -158
  48. package/lib/dist/tokens/tokens-116-dark.css +141 -135
  49. package/lib/dist/tokens/tokens-116-light.css +164 -158
  50. package/lib/dist/tokens/tokens-117-dark.css +141 -135
  51. package/lib/dist/tokens/tokens-117-light.css +164 -158
  52. package/lib/dist/tokens/tokens-118-dark.css +141 -135
  53. package/lib/dist/tokens/tokens-118-light.css +164 -158
  54. package/lib/dist/tokens/tokens-119-dark.css +141 -135
  55. package/lib/dist/tokens/tokens-119-light.css +164 -158
  56. package/lib/dist/tokens/tokens-120-dark.css +141 -135
  57. package/lib/dist/tokens/tokens-120-light.css +164 -158
  58. package/lib/dist/tokens/tokens-121-dark.css +141 -135
  59. package/lib/dist/tokens/tokens-121-light.css +164 -158
  60. package/lib/dist/tokens/tokens-122-dark.css +141 -135
  61. package/lib/dist/tokens/tokens-122-light.css +164 -158
  62. package/lib/dist/tokens/tokens-123-dark.css +141 -135
  63. package/lib/dist/tokens/tokens-123-light.css +164 -158
  64. package/lib/dist/tokens/tokens-124-dark.css +141 -135
  65. package/lib/dist/tokens/tokens-124-light.css +164 -158
  66. package/lib/dist/tokens/tokens-125-dark.css +141 -135
  67. package/lib/dist/tokens/tokens-125-light.css +164 -158
  68. package/lib/dist/tokens/tokens-126-dark.css +141 -135
  69. package/lib/dist/tokens/tokens-126-light.css +164 -158
  70. package/lib/dist/tokens/tokens-127-dark.css +141 -135
  71. package/lib/dist/tokens/tokens-127-light.css +164 -158
  72. package/lib/dist/tokens/tokens-128-dark.css +141 -135
  73. package/lib/dist/tokens/tokens-128-light.css +164 -158
  74. package/lib/dist/tokens/tokens-129-dark.css +141 -135
  75. package/lib/dist/tokens/tokens-129-light.css +164 -158
  76. package/lib/dist/tokens/tokens-130-dark.css +141 -135
  77. package/lib/dist/tokens/tokens-130-light.css +164 -158
  78. package/lib/dist/tokens/tokens-131-dark.css +141 -135
  79. package/lib/dist/tokens/tokens-131-light.css +164 -158
  80. package/lib/dist/tokens/tokens-132-dark.css +141 -135
  81. package/lib/dist/tokens/tokens-132-light.css +164 -158
  82. package/lib/dist/tokens/tokens-133-dark.css +141 -135
  83. package/lib/dist/tokens/tokens-133-light.css +164 -158
  84. package/lib/dist/tokens/tokens-134-dark.css +141 -135
  85. package/lib/dist/tokens/tokens-134-light.css +164 -158
  86. package/lib/dist/tokens/tokens-135-dark.css +141 -135
  87. package/lib/dist/tokens/tokens-135-light.css +164 -158
  88. package/lib/dist/tokens/tokens-136-dark.css +141 -135
  89. package/lib/dist/tokens/tokens-136-light.css +164 -158
  90. package/lib/dist/tokens/tokens-137-dark.css +141 -135
  91. package/lib/dist/tokens/tokens-137-light.css +164 -158
  92. package/lib/dist/tokens/tokens-aegean-dark.css +164 -158
  93. package/lib/dist/tokens/tokens-aegean-light.css +184 -178
  94. package/lib/dist/tokens/tokens-base-dark.css +98 -98
  95. package/lib/dist/tokens/tokens-base-light.css +98 -98
  96. package/lib/dist/tokens/tokens-botany-dark.css +149 -143
  97. package/lib/dist/tokens/tokens-botany-light.css +171 -165
  98. package/lib/dist/tokens/tokens-buttercream-dark.css +148 -142
  99. package/lib/dist/tokens/tokens-buttercream-light.css +171 -165
  100. package/lib/dist/tokens/tokens-ceruleo-dark.css +164 -158
  101. package/lib/dist/tokens/tokens-ceruleo-light.css +184 -178
  102. package/lib/dist/tokens/tokens-contrast-high-dark.css +3 -3
  103. package/lib/dist/tokens/tokens-contrast-high-light.css +10 -10
  104. package/lib/dist/tokens/tokens-debug-dp.css +28 -22
  105. package/lib/dist/tokens/tokens-dp-dark.css +166 -160
  106. package/lib/dist/tokens/tokens-dp-light.css +186 -180
  107. package/lib/dist/tokens/tokens-expressive-dark.css +163 -157
  108. package/lib/dist/tokens/tokens-expressive-light.css +186 -180
  109. package/lib/dist/tokens/tokens-expressive-sm-dark.css +163 -157
  110. package/lib/dist/tokens/tokens-expressive-sm-light.css +186 -180
  111. package/lib/dist/tokens/tokens-high-desert-dark.css +144 -138
  112. package/lib/dist/tokens/tokens-high-desert-light.css +164 -158
  113. package/lib/dist/tokens/tokens-melon-dark.css +145 -139
  114. package/lib/dist/tokens/tokens-melon-light.css +165 -159
  115. package/lib/dist/tokens/tokens-plum-dark.css +153 -147
  116. package/lib/dist/tokens/tokens-plum-light.css +164 -158
  117. package/lib/dist/tokens/tokens-prota-deuter-dark.css +292 -262
  118. package/lib/dist/tokens/tokens-prota-deuter-light.css +315 -285
  119. package/lib/dist/tokens/tokens-sunflower-dark.css +154 -148
  120. package/lib/dist/tokens/tokens-sunflower-light.css +174 -168
  121. package/lib/dist/tokens/tokens-tmo-dark.css +165 -159
  122. package/lib/dist/tokens/tokens-tmo-light.css +185 -179
  123. package/lib/dist/tokens/tokens-trita-dark.css +239 -197
  124. package/lib/dist/tokens/tokens-trita-light.css +263 -221
  125. package/lib/dist/tokens/tokens-verdant-haze-dark.css +144 -138
  126. package/lib/dist/tokens/tokens-verdant-haze-light.css +164 -158
  127. package/lib/dist/tokens-docs.json +1 -1
  128. 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
+ });