@dialpad/dialtone-css 8.80.0-next.2 → 8.80.0-next.4
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_link_rendering/button-nav-test-examples.vue +92 -0
- package/lib/build/js/dialtone_migrate_link_rendering/button-nav.test.mjs +272 -0
- package/lib/build/js/dialtone_migrate_link_rendering/helpers.mjs +25 -0
- package/lib/build/js/dialtone_migrate_link_rendering/index.mjs +1041 -0
- package/lib/build/js/dialtone_migrate_link_rendering/link-nav-test-examples.vue +97 -0
- package/lib/build/js/dialtone_migrate_link_rendering/link-nav.test.mjs +194 -0
- package/lib/build/js/dialtone_migrate_link_rendering/underline-test-examples.vue +57 -0
- package/lib/build/js/dialtone_migrate_link_rendering/underline.test.mjs +161 -0
- package/lib/build/js/dialtone_migrate_props/index.mjs +794 -0
- package/lib/build/js/dialtone_migrate_props/test.mjs +959 -0
- package/lib/build/js/dialtone_migration_helper/configs/base-to-semantic.mjs +8 -8
- package/lib/build/js/dialtone_migration_helper/configs/size-to-layout.mjs +10 -0
- package/lib/build/js/dialtone_migration_helper/configs/success-to-positive.mjs +73 -0
- package/lib/build/js/dialtone_migration_helper/configs/utility-class-to-token-stops.mjs +92 -12
- package/lib/build/js/dialtone_migration_helper/tests/base-to-semantic-test-examples.vue +10 -10
- package/lib/build/js/dialtone_migration_helper/tests/base-to-semantic.test.mjs +8 -8
- package/lib/build/js/dialtone_migration_helper/tests/size-to-layout-test-examples.vue +16 -0
- package/lib/build/js/dialtone_migration_helper/tests/size-to-layout.test.mjs +87 -0
- package/lib/build/js/dialtone_migration_helper/tests/success-to-positive-test-examples.vue +166 -0
- package/lib/build/js/dialtone_migration_helper/tests/success-to-positive.test.mjs +287 -0
- package/lib/build/js/dialtone_migration_helper/tests/utility-class-to-token-stops-radius-examples.vue +66 -0
- package/lib/build/js/dialtone_migration_helper/tests/utility-class-to-token-stops.test.mjs +170 -0
- package/lib/build/less/components/badge.less +1 -1
- package/lib/build/less/components/banner.less +1 -1
- package/lib/build/less/components/box.less +9 -22
- package/lib/build/less/components/description-list.less +4 -0
- package/lib/build/less/components/forms.less +4 -2
- package/lib/build/less/components/input.less +2 -2
- package/lib/build/less/components/link.less +18 -4
- package/lib/build/less/components/modal.less +1 -1
- package/lib/build/less/components/notice.less +3 -3
- package/lib/build/less/components/progress_circle.less +10 -2
- package/lib/build/less/components/prose.less +512 -0
- package/lib/build/less/components/rich-text-editor.less +31 -0
- package/lib/build/less/components/selects.less +1 -1
- package/lib/build/less/components/text.less +2 -0
- package/lib/build/less/components/toast.less +1 -1
- package/lib/build/less/dialtone.less +1 -0
- package/lib/build/less/recipes/leftbar_row.less +2 -2
- package/lib/build/less/recipes/settings_menu_button.less +1 -1
- package/lib/build/less/recipes/top_banner_info.less +1 -1
- package/lib/build/less/utilities/backgrounds.less +12 -0
- package/lib/build/less/utilities/borders.less +56 -89
- package/lib/build/less/utilities/colors.less +8 -0
- package/lib/build/less/utilities/effects.less +1 -0
- package/lib/build/less/utilities/flex.less +145 -18
- package/lib/build/less/utilities/grid.less +40 -152
- package/lib/build/less/utilities/layout.less +19 -7
- package/lib/build/less/utilities/sizing.less +148 -143
- package/lib/build/less/variables/visual-styles.less +2 -1
- package/lib/dist/dialtone-default-theme.css +2840 -1807
- package/lib/dist/dialtone-default-theme.min.css +1 -1
- package/lib/dist/dialtone-docs.json +1 -1
- package/lib/dist/dialtone.css +2747 -1756
- package/lib/dist/dialtone.min.css +1 -1
- package/lib/dist/js/dialtone_migrate_link_rendering/button-nav-test-examples.vue +92 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/button-nav.test.mjs +272 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/helpers.mjs +25 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/index.mjs +1041 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/link-nav-test-examples.vue +97 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/link-nav.test.mjs +194 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/underline-test-examples.vue +57 -0
- package/lib/dist/js/dialtone_migrate_link_rendering/underline.test.mjs +161 -0
- package/lib/dist/js/dialtone_migrate_props/index.mjs +794 -0
- package/lib/dist/js/dialtone_migrate_props/test.mjs +959 -0
- package/lib/dist/js/dialtone_migration_helper/configs/base-to-semantic.mjs +8 -8
- package/lib/dist/js/dialtone_migration_helper/configs/size-to-layout.mjs +10 -0
- package/lib/dist/js/dialtone_migration_helper/configs/success-to-positive.mjs +73 -0
- package/lib/dist/js/dialtone_migration_helper/configs/utility-class-to-token-stops.mjs +92 -12
- package/lib/dist/js/dialtone_migration_helper/tests/base-to-semantic-test-examples.vue +10 -10
- package/lib/dist/js/dialtone_migration_helper/tests/base-to-semantic.test.mjs +8 -8
- package/lib/dist/js/dialtone_migration_helper/tests/size-to-layout-test-examples.vue +16 -0
- package/lib/dist/js/dialtone_migration_helper/tests/size-to-layout.test.mjs +87 -0
- package/lib/dist/js/dialtone_migration_helper/tests/success-to-positive-test-examples.vue +166 -0
- package/lib/dist/js/dialtone_migration_helper/tests/success-to-positive.test.mjs +287 -0
- package/lib/dist/js/dialtone_migration_helper/tests/utility-class-to-token-stops-radius-examples.vue +66 -0
- package/lib/dist/js/dialtone_migration_helper/tests/utility-class-to-token-stops.test.mjs +170 -0
- package/lib/dist/tokens/tokens-101-dark.css +81 -45
- package/lib/dist/tokens/tokens-101-light.css +75 -39
- package/lib/dist/tokens/tokens-102-dark.css +81 -45
- package/lib/dist/tokens/tokens-102-light.css +75 -39
- package/lib/dist/tokens/tokens-103-dark.css +81 -45
- package/lib/dist/tokens/tokens-103-light.css +75 -39
- package/lib/dist/tokens/tokens-104-dark.css +81 -45
- package/lib/dist/tokens/tokens-104-light.css +75 -39
- package/lib/dist/tokens/tokens-105-dark.css +81 -45
- package/lib/dist/tokens/tokens-105-light.css +75 -39
- package/lib/dist/tokens/tokens-106-dark.css +81 -45
- package/lib/dist/tokens/tokens-106-light.css +75 -39
- package/lib/dist/tokens/tokens-107-dark.css +81 -45
- package/lib/dist/tokens/tokens-107-light.css +75 -39
- package/lib/dist/tokens/tokens-108-dark.css +81 -45
- package/lib/dist/tokens/tokens-108-light.css +75 -39
- package/lib/dist/tokens/tokens-109-dark.css +81 -45
- package/lib/dist/tokens/tokens-109-light.css +75 -39
- package/lib/dist/tokens/tokens-110-dark.css +81 -45
- package/lib/dist/tokens/tokens-110-light.css +75 -39
- package/lib/dist/tokens/tokens-111-dark.css +81 -45
- package/lib/dist/tokens/tokens-111-light.css +75 -39
- package/lib/dist/tokens/tokens-112-dark.css +81 -45
- package/lib/dist/tokens/tokens-112-light.css +75 -39
- package/lib/dist/tokens/tokens-113-dark.css +81 -45
- package/lib/dist/tokens/tokens-113-light.css +75 -39
- package/lib/dist/tokens/tokens-114-dark.css +81 -45
- package/lib/dist/tokens/tokens-114-light.css +75 -39
- package/lib/dist/tokens/tokens-115-dark.css +81 -45
- package/lib/dist/tokens/tokens-115-light.css +75 -39
- package/lib/dist/tokens/tokens-116-dark.css +81 -45
- package/lib/dist/tokens/tokens-116-light.css +75 -39
- package/lib/dist/tokens/tokens-117-dark.css +81 -45
- package/lib/dist/tokens/tokens-117-light.css +75 -39
- package/lib/dist/tokens/tokens-118-dark.css +81 -45
- package/lib/dist/tokens/tokens-118-light.css +75 -39
- package/lib/dist/tokens/tokens-119-dark.css +81 -45
- package/lib/dist/tokens/tokens-119-light.css +75 -39
- package/lib/dist/tokens/tokens-120-dark.css +81 -45
- package/lib/dist/tokens/tokens-120-light.css +75 -39
- package/lib/dist/tokens/tokens-121-dark.css +81 -45
- package/lib/dist/tokens/tokens-121-light.css +75 -39
- package/lib/dist/tokens/tokens-122-dark.css +81 -45
- package/lib/dist/tokens/tokens-122-light.css +75 -39
- package/lib/dist/tokens/tokens-123-dark.css +81 -45
- package/lib/dist/tokens/tokens-123-light.css +75 -39
- package/lib/dist/tokens/tokens-124-dark.css +81 -45
- package/lib/dist/tokens/tokens-124-light.css +75 -39
- package/lib/dist/tokens/tokens-125-dark.css +81 -45
- package/lib/dist/tokens/tokens-125-light.css +75 -39
- package/lib/dist/tokens/tokens-126-dark.css +81 -45
- package/lib/dist/tokens/tokens-126-light.css +75 -39
- package/lib/dist/tokens/tokens-127-dark.css +81 -45
- package/lib/dist/tokens/tokens-127-light.css +75 -39
- package/lib/dist/tokens/tokens-128-dark.css +81 -45
- package/lib/dist/tokens/tokens-128-light.css +75 -39
- package/lib/dist/tokens/tokens-129-dark.css +81 -45
- package/lib/dist/tokens/tokens-129-light.css +75 -39
- package/lib/dist/tokens/tokens-130-dark.css +81 -45
- package/lib/dist/tokens/tokens-130-light.css +75 -39
- package/lib/dist/tokens/tokens-131-dark.css +81 -45
- package/lib/dist/tokens/tokens-131-light.css +75 -39
- package/lib/dist/tokens/tokens-132-dark.css +81 -45
- package/lib/dist/tokens/tokens-132-light.css +75 -39
- package/lib/dist/tokens/tokens-133-dark.css +81 -45
- package/lib/dist/tokens/tokens-133-light.css +75 -39
- package/lib/dist/tokens/tokens-134-dark.css +81 -45
- package/lib/dist/tokens/tokens-134-light.css +75 -39
- package/lib/dist/tokens/tokens-135-dark.css +81 -45
- package/lib/dist/tokens/tokens-135-light.css +75 -39
- package/lib/dist/tokens/tokens-136-dark.css +81 -45
- package/lib/dist/tokens/tokens-136-light.css +75 -39
- package/lib/dist/tokens/tokens-137-dark.css +81 -45
- package/lib/dist/tokens/tokens-137-light.css +75 -39
- package/lib/dist/tokens/tokens-aegean-dark.css +81 -45
- package/lib/dist/tokens/tokens-aegean-light.css +75 -39
- package/lib/dist/tokens/tokens-base-dark.css +18 -12
- package/lib/dist/tokens/tokens-base-light.css +18 -12
- package/lib/dist/tokens/tokens-botany-dark.css +81 -45
- package/lib/dist/tokens/tokens-botany-light.css +75 -39
- package/lib/dist/tokens/tokens-buttercream-dark.css +81 -45
- package/lib/dist/tokens/tokens-buttercream-light.css +75 -39
- package/lib/dist/tokens/tokens-ceruleo-dark.css +81 -45
- package/lib/dist/tokens/tokens-ceruleo-light.css +75 -39
- package/lib/dist/tokens/tokens-debug-base.css +6 -0
- package/lib/dist/tokens/tokens-debug-dp.css +39 -3
- package/lib/dist/tokens/tokens-dp-dark.css +81 -45
- package/lib/dist/tokens/tokens-dp-light.css +75 -39
- package/lib/dist/tokens/tokens-expressive-dark.css +81 -45
- package/lib/dist/tokens/tokens-expressive-light.css +75 -39
- package/lib/dist/tokens/tokens-expressive-sm-dark.css +81 -45
- package/lib/dist/tokens/tokens-expressive-sm-light.css +75 -39
- package/lib/dist/tokens/tokens-high-desert-dark.css +81 -45
- package/lib/dist/tokens/tokens-high-desert-light.css +75 -39
- package/lib/dist/tokens/tokens-melon-dark.css +81 -45
- package/lib/dist/tokens/tokens-melon-light.css +75 -39
- package/lib/dist/tokens/tokens-plum-dark.css +81 -45
- package/lib/dist/tokens/tokens-plum-light.css +75 -39
- package/lib/dist/tokens/tokens-prota-deuter-dark.css +79 -43
- package/lib/dist/tokens/tokens-prota-deuter-light.css +74 -38
- package/lib/dist/tokens/tokens-sunflower-dark.css +81 -45
- package/lib/dist/tokens/tokens-sunflower-light.css +75 -39
- package/lib/dist/tokens/tokens-tmo-dark.css +81 -45
- package/lib/dist/tokens/tokens-tmo-light.css +75 -39
- package/lib/dist/tokens/tokens-trita-dark.css +81 -45
- package/lib/dist/tokens/tokens-trita-light.css +75 -39
- package/lib/dist/tokens/tokens-verdant-haze-dark.css +81 -45
- package/lib/dist/tokens/tokens-verdant-haze-light.css +75 -39
- package/lib/dist/tokens-docs.json +1 -1
- package/package.json +10 -5
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable max-lines */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Migration script for Dialtone component prop/slot/event breaking changes.
|
|
6
|
+
*
|
|
7
|
+
* Covers these changes:
|
|
8
|
+
* DLT-3161 avatar: clickable → interactive
|
|
9
|
+
* DLT-3157 kind/validation-state values: danger→critical, error→critical, success→positive
|
|
10
|
+
* also: tone="success" → tone="positive" (dt-text, dt-link)
|
|
11
|
+
* also: link-kind="success" → link-kind="positive" (dt-button)
|
|
12
|
+
* DLT-3159 positive boolean props: hide-close→show-close, hide-icon→show-icon, etc.
|
|
13
|
+
* DLT-3282 show prop → open (modal, toast, tooltip) + update:show → update:open
|
|
14
|
+
* DLT-3283 slot renames: titleOverride→header, labelSlot→label, headingSlot→heading
|
|
15
|
+
* DLT-3284 title → headerText on banner, notice, toast, modal; title-id → header-id on banner, notice, toast only
|
|
16
|
+
* DLT-3159 label-visible → show-label (checkbox, combobox, input, select-menu, toggle, etc.)
|
|
17
|
+
* DLT-3160 checkbox-group: selectedValues → modelValue; @input/@change → @update:model-value on form inputs
|
|
18
|
+
* DLT-3100 rootClass removed — warns with file locations, cannot auto-migrate
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* npx dialtone-migrate-props [options]
|
|
22
|
+
*
|
|
23
|
+
* Options:
|
|
24
|
+
* --cwd <path> Working directory (default: current directory)
|
|
25
|
+
* --dry-run Show changes without applying them
|
|
26
|
+
* --yes Apply all changes without prompting
|
|
27
|
+
* --help Show help
|
|
28
|
+
*
|
|
29
|
+
* Examples:
|
|
30
|
+
* npx dialtone-migrate-props
|
|
31
|
+
* npx dialtone-migrate-props --dry-run
|
|
32
|
+
* npx dialtone-migrate-props --cwd ./src
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import fs from 'fs/promises';
|
|
36
|
+
import { realpathSync } from 'node:fs';
|
|
37
|
+
import path from 'path';
|
|
38
|
+
import readline from 'readline';
|
|
39
|
+
import { fileURLToPath } from 'node:url';
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Utilities
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function kebabToCamel (str) {
|
|
46
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function escapeRe (str) {
|
|
50
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Transformation Data
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
// Components where `show` → `open` and `update:show` → `update:open`
|
|
58
|
+
const SHOW_TO_OPEN_COMPONENTS = new Set(['dt-modal', 'dt-toast', 'dt-tooltip']);
|
|
59
|
+
|
|
60
|
+
// Direct prop renames per component (kebab-case keys; camelCase handled automatically)
|
|
61
|
+
const COMPONENT_PROP_RENAMES = {
|
|
62
|
+
'dt-avatar': {
|
|
63
|
+
clickable: 'interactive',
|
|
64
|
+
},
|
|
65
|
+
'dt-banner': {
|
|
66
|
+
title: 'header-text',
|
|
67
|
+
'title-id': 'header-id',
|
|
68
|
+
},
|
|
69
|
+
'dt-notice': {
|
|
70
|
+
title: 'header-text',
|
|
71
|
+
'title-id': 'header-id',
|
|
72
|
+
},
|
|
73
|
+
'dt-toast': {
|
|
74
|
+
title: 'header-text',
|
|
75
|
+
'title-id': 'header-id',
|
|
76
|
+
},
|
|
77
|
+
'dt-modal': {
|
|
78
|
+
title: 'header-text',
|
|
79
|
+
'banner-title': 'banner-header-text',
|
|
80
|
+
},
|
|
81
|
+
'dt-checkbox': { 'label-visible': 'show-label' },
|
|
82
|
+
'dt-combobox': { 'label-visible': 'show-label' },
|
|
83
|
+
'dt-combobox-multi-select': { 'label-visible': 'show-label' },
|
|
84
|
+
'dt-combobox-with-popover': { 'label-visible': 'show-label' },
|
|
85
|
+
'dt-input': { 'label-visible': 'show-label' },
|
|
86
|
+
'dt-select-menu': { 'label-visible': 'show-label' },
|
|
87
|
+
'dt-toggle': { 'label-visible': 'show-label' },
|
|
88
|
+
'dt-radio-group': { 'label-visible': 'show-label' },
|
|
89
|
+
'dt-checkbox-group': { 'selected-values': 'model-value' },
|
|
90
|
+
'dt-link': { kind: 'tone' },
|
|
91
|
+
'dt-popover': { 'hide-on-click': 'close-on-click' },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Removed class props per component: old-prop → class attribute directly
|
|
95
|
+
const ROOT_CLASS_RENAMES = {
|
|
96
|
+
'dt-input': 'root-class',
|
|
97
|
+
'dt-checkbox': 'root-class',
|
|
98
|
+
'dt-radio': 'root-class',
|
|
99
|
+
'dt-select-menu': 'root-class',
|
|
100
|
+
'dt-toggle': 'wrapper-class',
|
|
101
|
+
'dt-card': 'container-class',
|
|
102
|
+
'dt-breadcrumb-item': 'root-class',
|
|
103
|
+
'dt-split-button': 'root-class',
|
|
104
|
+
'dt-feed-item-pill': 'wrapper-class',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Inverted boolean props per component: `hide-X` → `:show-X="false"`
|
|
108
|
+
const INVERTED_BOOL_PROPS = {
|
|
109
|
+
'dt-banner': { 'hide-close': 'show-close', 'hide-icon': 'show-icon', 'hide-action': 'show-action' },
|
|
110
|
+
'dt-chip': { 'hide-close': 'show-close' },
|
|
111
|
+
'dt-modal': { 'hide-close': 'show-close' },
|
|
112
|
+
'dt-notice': { 'hide-close': 'show-close', 'hide-icon': 'show-icon', 'hide-action': 'show-action' },
|
|
113
|
+
'dt-toast': { 'hide-close': 'show-close', 'hide-icon': 'show-icon', 'hide-action': 'show-action' },
|
|
114
|
+
'dt-filter-pill': { 'hide-clear': 'show-clear' },
|
|
115
|
+
'dt-notice-action': { 'hide-close': 'show-close', 'hide-action': 'show-action' },
|
|
116
|
+
'dt-pagination': { 'hide-edges': 'show-edges' },
|
|
117
|
+
'dt-segmented-control': { 'hide-divider': 'show-divider' },
|
|
118
|
+
'dt-rich-text-editor': { 'prevent-typing': 'allow-typing', 'hide-link-bubble-menu': 'show-link-bubble-menu' },
|
|
119
|
+
'dt-recipe-message-input': { 'prevent-typing': 'allow-typing' },
|
|
120
|
+
'dt-recipe-contact-centers-row': { 'hide-actions': 'show-actions' },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Prop value renames applied to any dt-* component
|
|
124
|
+
const PROP_VALUE_RENAMES = [
|
|
125
|
+
{ prop: 'kind', oldValue: 'danger', newValue: 'critical' },
|
|
126
|
+
{ prop: 'kind', oldValue: 'error', newValue: 'critical' },
|
|
127
|
+
{ prop: 'kind', oldValue: 'success', newValue: 'positive' },
|
|
128
|
+
{ prop: 'validation-state', oldValue: 'error', newValue: 'critical' },
|
|
129
|
+
{ prop: 'validation-state', oldValue: 'success', newValue: 'positive' },
|
|
130
|
+
{ prop: 'type', oldValue: 'success', newValue: 'positive' },
|
|
131
|
+
{ prop: 'banner-kind', oldValue: 'error', newValue: 'critical' },
|
|
132
|
+
{ prop: 'banner-kind', oldValue: 'success', newValue: 'positive' },
|
|
133
|
+
// dt-text uses `tone` for foreground tokens; dt-link migrates kind → tone in the
|
|
134
|
+
// same pass, so any tag that ends up with tone="success" should land on positive.
|
|
135
|
+
{ prop: 'tone', oldValue: 'success', newValue: 'positive' },
|
|
136
|
+
{ prop: 'tone', oldValue: 'success-strong', newValue: 'positive-strong' },
|
|
137
|
+
// dt-button has a separate `link-kind` prop for link-styled buttons; its
|
|
138
|
+
// pre-DLT-3157 value `success` aliases to `positive`.
|
|
139
|
+
{ prop: 'link-kind', oldValue: 'danger', newValue: 'critical' },
|
|
140
|
+
{ prop: 'link-kind', oldValue: 'success', newValue: 'positive' },
|
|
141
|
+
// dt-box uses `surface` and `bc` for its surface and border-color modifiers.
|
|
142
|
+
// The validator only declares positive*; legacy success* values trigger a warning.
|
|
143
|
+
{ prop: 'surface', oldValue: 'success', newValue: 'positive' },
|
|
144
|
+
{ prop: 'surface', oldValue: 'success-subtle', newValue: 'positive-subtle' },
|
|
145
|
+
{ prop: 'surface', oldValue: 'success-strong', newValue: 'positive-strong' },
|
|
146
|
+
{ prop: 'surface', oldValue: 'success-opaque', newValue: 'positive-opaque' },
|
|
147
|
+
{ prop: 'surface', oldValue: 'success-subtle-opaque', newValue: 'positive-subtle-opaque' },
|
|
148
|
+
{ prop: 'bc', oldValue: 'success', newValue: 'positive' },
|
|
149
|
+
{ prop: 'bc', oldValue: 'success-subtle', newValue: 'positive-subtle' },
|
|
150
|
+
{ prop: 'bc', oldValue: 'success-strong', newValue: 'positive-strong' },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// Slot renames — applied globally (names are unique enough across the system)
|
|
154
|
+
const SLOT_RENAMES = [
|
|
155
|
+
{ old: 'titleOverride', new: 'header' },
|
|
156
|
+
{ old: 'labelSlot', new: 'label' },
|
|
157
|
+
{ old: 'headingSlot', new: 'heading' },
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
// v-model arg renames per component (null = drop arg, becomes default v-model)
|
|
161
|
+
const VMODEL_ARG_RENAMES = {
|
|
162
|
+
'dt-checkbox-group': { 'selected-values': null, selectedValues: null },
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// @update:* event renames per component
|
|
166
|
+
const UPDATE_EVENT_RENAMES = {
|
|
167
|
+
'dt-checkbox-group': { 'selected-values': 'model-value', selectedValues: 'modelValue' },
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Component events removed from emits and replaced by @update:model-value
|
|
171
|
+
const EMIT_EVENT_RENAMES = {
|
|
172
|
+
'dt-input': { input: 'update:model-value' },
|
|
173
|
+
'dt-radio': { input: 'update:model-value' },
|
|
174
|
+
'dt-radio-group': { input: 'update:model-value' },
|
|
175
|
+
'dt-select-menu': { input: 'update:model-value', change: 'update:model-value' },
|
|
176
|
+
'dt-toggle': { change: 'update:model-value' },
|
|
177
|
+
'dt-combobox-multi-select': { input: 'update:model-value' },
|
|
178
|
+
'dt-rich-text-editor': { input: 'update:model-value' },
|
|
179
|
+
'dt-input-group': { input: 'update:model-value' },
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Regex matching any Dialtone component opening tag, handling > inside quoted attribute values.
|
|
183
|
+
// Alternation `(?:"[^"]*"|'[^']*')` consumes quoted strings before the bare [^>]* can stop at >.
|
|
184
|
+
const DT_TAG_RE = /<(dt-[\w-]+|Dt\w+)\b((?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>]*)(?:\/?>)/g;
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Per-tag Sub-Transformers
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* `show` → `open` plus `@update:show` / `v-model:show` on overlay components.
|
|
192
|
+
*/
|
|
193
|
+
function applyShowToOpen (tag, canonical) {
|
|
194
|
+
if (!SHOW_TO_OPEN_COMPONENTS.has(canonical)) return { tag, count: 0 };
|
|
195
|
+
let result = tag;
|
|
196
|
+
let count = 0;
|
|
197
|
+
|
|
198
|
+
// :show="…" or show="…" but NOT show-close / show-icon / show-action etc.
|
|
199
|
+
result = result.replace(/(\b:?)(show)(?=[=\s/>])/g, (match, colon, _prop, offset, str) => {
|
|
200
|
+
if (str[offset + match.length] === '-') return match;
|
|
201
|
+
count++;
|
|
202
|
+
return `${colon}open`;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const updateBefore = result;
|
|
206
|
+
result = result.replace(/@update:show\b/g, '@update:open');
|
|
207
|
+
count += (result !== updateBefore) ? 1 : 0;
|
|
208
|
+
|
|
209
|
+
const vmodelBefore = result;
|
|
210
|
+
result = result.replace(/v-model:show\b/g, 'v-model:open');
|
|
211
|
+
count += (result !== vmodelBefore) ? 1 : 0;
|
|
212
|
+
|
|
213
|
+
return { tag: result, count };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Direct 1:1 prop renames for a specific component.
|
|
218
|
+
*/
|
|
219
|
+
function applyPropRenames (tag, canonical) {
|
|
220
|
+
const renames = COMPONENT_PROP_RENAMES[canonical];
|
|
221
|
+
if (!renames) return { tag, count: 0 };
|
|
222
|
+
|
|
223
|
+
let result = tag;
|
|
224
|
+
let count = 0;
|
|
225
|
+
|
|
226
|
+
for (const [oldProp, newProp] of Object.entries(renames)) {
|
|
227
|
+
const oldCamel = kebabToCamel(oldProp);
|
|
228
|
+
const newCamel = kebabToCamel(newProp);
|
|
229
|
+
|
|
230
|
+
// Static kebab: `old-prop="…"` or bare boolean
|
|
231
|
+
const before1 = result;
|
|
232
|
+
result = result.replace(new RegExp(`(?<!:)\\b${escapeRe(oldProp)}(?=[=\\s/>])`, 'g'), newProp);
|
|
233
|
+
if (result !== before1) { count++; continue; }
|
|
234
|
+
|
|
235
|
+
// Bound kebab: `:old-prop="…"`
|
|
236
|
+
const before2 = result;
|
|
237
|
+
result = result.replace(new RegExp(`(?<=:)${escapeRe(oldProp)}(?==)`, 'g'), newProp);
|
|
238
|
+
if (result !== before2) { count++; continue; }
|
|
239
|
+
|
|
240
|
+
// Bound camelCase: `:oldProp="…"` / Static camelCase: `oldProp="…"` or bare boolean
|
|
241
|
+
if (oldCamel !== oldProp) {
|
|
242
|
+
const before3 = result;
|
|
243
|
+
result = result.replace(new RegExp(`(?<=:)${escapeRe(oldCamel)}(?==)`, 'g'), newCamel);
|
|
244
|
+
if (result !== before3) { count++; continue; }
|
|
245
|
+
const before4 = result;
|
|
246
|
+
result = result.replace(new RegExp(`(?<!:)\\b${escapeRe(oldCamel)}(?=[=\\s/>])`, 'g'), newCamel);
|
|
247
|
+
if (result !== before4) count++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { tag: result, count };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Apply one inverted-boolean rename: hide-X → :show-X="false".
|
|
256
|
+
* Returns { tag, count, warning? }
|
|
257
|
+
*/
|
|
258
|
+
function applyOneInvertedProp (tag, oldProp, newProp) {
|
|
259
|
+
const oldCamel = kebabToCamel(oldProp);
|
|
260
|
+
const newCamel = kebabToCamel(newProp);
|
|
261
|
+
|
|
262
|
+
// `:hide-prop="false"` or `:hideXxx="false"` → remove (default is true)
|
|
263
|
+
for (const p of [escapeRe(oldProp), escapeRe(oldCamel)]) {
|
|
264
|
+
const before = tag;
|
|
265
|
+
tag = tag.replace(new RegExp(`:${p}="false"`, 'g'), '');
|
|
266
|
+
if (tag !== before) return { tag, count: 1 };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// `:hide-prop="true"` → `:show-prop="false"`
|
|
270
|
+
const trueBefore = tag;
|
|
271
|
+
tag = tag.replace(new RegExp(`:${escapeRe(oldProp)}="true"`, 'g'), `:${newProp}="false"`);
|
|
272
|
+
if (tag !== trueBefore) return { tag, count: 1 };
|
|
273
|
+
|
|
274
|
+
// `:hideXxx="true"` → `:showXxx="false"`
|
|
275
|
+
const camelTrueBefore = tag;
|
|
276
|
+
tag = tag.replace(new RegExp(`:${escapeRe(oldCamel)}="true"`, 'g'), `:${newCamel}="false"`);
|
|
277
|
+
if (tag !== camelTrueBefore) return { tag, count: 1 };
|
|
278
|
+
|
|
279
|
+
// Bare boolean: `hide-prop` → `:show-prop="false"`
|
|
280
|
+
const bareBefore = tag;
|
|
281
|
+
tag = tag.replace(new RegExp(`(?<!:)\\b${escapeRe(oldProp)}(?=[\\s/>])`, 'g'), `:${newProp}="false"`);
|
|
282
|
+
if (tag !== bareBefore) return { tag, count: 1 };
|
|
283
|
+
|
|
284
|
+
// Bound expression: cannot auto-invert — emit warning (check both kebab and camelCase forms)
|
|
285
|
+
for (const [attrProp, attrNew] of [[oldProp, newProp], [oldCamel, newCamel]]) {
|
|
286
|
+
const exprMatch = new RegExp(`:${escapeRe(attrProp)}="([^"]*)"`, 'g').exec(tag);
|
|
287
|
+
if (exprMatch) {
|
|
288
|
+
return {
|
|
289
|
+
tag,
|
|
290
|
+
count: 0,
|
|
291
|
+
warning: `Cannot auto-invert :${attrProp}="${exprMatch[1]}" → :${attrNew}. ` +
|
|
292
|
+
`Replace manually with :${attrNew}="!(${exprMatch[1]})"`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { tag, count: 0 };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Inverted boolean props for a specific component (hide-X → :show-X="false").
|
|
302
|
+
*/
|
|
303
|
+
function applyInvertedBoolProps (tag, canonical) {
|
|
304
|
+
const invertedMap = INVERTED_BOOL_PROPS[canonical];
|
|
305
|
+
if (!invertedMap) return { tag, count: 0, warnings: [] };
|
|
306
|
+
|
|
307
|
+
let result = tag;
|
|
308
|
+
let count = 0;
|
|
309
|
+
const warnings = [];
|
|
310
|
+
|
|
311
|
+
for (const [oldProp, newProp] of Object.entries(invertedMap)) {
|
|
312
|
+
const { tag: updated, count: c, warning } = applyOneInvertedProp(result, oldProp, newProp);
|
|
313
|
+
result = updated;
|
|
314
|
+
count += c;
|
|
315
|
+
if (warning) warnings.push(warning);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { tag: result, count, warnings };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* `kind` and `validation-state` value renames on any dt-* component.
|
|
323
|
+
*/
|
|
324
|
+
function applyPropValueRenames (tag) {
|
|
325
|
+
let result = tag;
|
|
326
|
+
let count = 0;
|
|
327
|
+
|
|
328
|
+
for (const { prop, oldValue, newValue } of PROP_VALUE_RENAMES) {
|
|
329
|
+
const propCamel = kebabToCamel(prop);
|
|
330
|
+
|
|
331
|
+
for (const p of [prop, propCamel]) {
|
|
332
|
+
// Static: `prop="oldValue"`
|
|
333
|
+
const before1 = result;
|
|
334
|
+
result = result.replace(
|
|
335
|
+
new RegExp(`(?<!:)\\b${escapeRe(p)}="${escapeRe(oldValue)}"`, 'g'),
|
|
336
|
+
`${p}="${newValue}"`,
|
|
337
|
+
);
|
|
338
|
+
if (result !== before1) count++;
|
|
339
|
+
|
|
340
|
+
// Bound string literal: `:prop="'oldValue'"`
|
|
341
|
+
const before2 = result;
|
|
342
|
+
result = result.replace(
|
|
343
|
+
new RegExp(`:${escapeRe(p)}="'${escapeRe(oldValue)}'"`, 'g'),
|
|
344
|
+
`:${p}="'${newValue}'"`,
|
|
345
|
+
);
|
|
346
|
+
if (result !== before2) count++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { tag: result, count };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Renames a removed class prop (root-class, wrapper-class, container-class) to `class`.
|
|
355
|
+
* Static values are merged into an existing `class="…"` if present.
|
|
356
|
+
* Dynamic bindings warn if `:class` already exists (cannot safely merge expressions).
|
|
357
|
+
*/
|
|
358
|
+
function applyRootClassRename (tag, canonical) {
|
|
359
|
+
const oldProp = ROOT_CLASS_RENAMES[canonical];
|
|
360
|
+
if (!oldProp) return { tag, count: 0, warnings: [] };
|
|
361
|
+
|
|
362
|
+
const oldCamel = kebabToCamel(oldProp);
|
|
363
|
+
let result = tag;
|
|
364
|
+
let count = 0;
|
|
365
|
+
const warnings = [];
|
|
366
|
+
|
|
367
|
+
for (const p of [oldProp, oldCamel]) {
|
|
368
|
+
// Dynamic: `:old-prop="expr"` → `:class="expr"` (warn if :class already exists)
|
|
369
|
+
const dynRe = new RegExp(`:${escapeRe(p)}="([^"]*)"`);
|
|
370
|
+
const dynMatch = dynRe.exec(result);
|
|
371
|
+
if (dynMatch) {
|
|
372
|
+
if (/:class="/.test(result)) {
|
|
373
|
+
warnings.push(
|
|
374
|
+
`Cannot auto-merge :${p}="${dynMatch[1]}" into :class on <${canonical}> — merge manually.`,
|
|
375
|
+
);
|
|
376
|
+
} else {
|
|
377
|
+
result = result.replace(dynMatch[0], `:class="${dynMatch[1]}"`);
|
|
378
|
+
count++;
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Static: `old-prop="value"` → `class="value"` (merge if class already exists)
|
|
384
|
+
const staticRe = new RegExp(`(?<!:)\\b${escapeRe(p)}="([^"]*)"`);
|
|
385
|
+
const staticMatch = staticRe.exec(result);
|
|
386
|
+
if (staticMatch) {
|
|
387
|
+
const addedVal = staticMatch[1];
|
|
388
|
+
const existingClassMatch = /(?<![:\w-])class="([^"]*)"/.exec(result);
|
|
389
|
+
if (existingClassMatch) {
|
|
390
|
+
result = result.replace(staticMatch[0], '');
|
|
391
|
+
result = result.replace(existingClassMatch[0], `class="${existingClassMatch[1]} ${addedVal}"`);
|
|
392
|
+
} else {
|
|
393
|
+
result = result.replace(staticMatch[0], `class="${addedVal}"`);
|
|
394
|
+
}
|
|
395
|
+
count++;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return { tag: result, count, warnings };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function applyVModelArgRenames (tag, canonical) {
|
|
404
|
+
const vmodelMap = VMODEL_ARG_RENAMES[canonical];
|
|
405
|
+
if (!vmodelMap) return { tag, count: 0 };
|
|
406
|
+
let result = tag;
|
|
407
|
+
let count = 0;
|
|
408
|
+
for (const [oldArg, newArg] of Object.entries(vmodelMap)) {
|
|
409
|
+
const before = result;
|
|
410
|
+
const replacement = newArg ? `v-model:${newArg}` : 'v-model';
|
|
411
|
+
result = result.replace(new RegExp(`v-model:${escapeRe(oldArg)}\\b`, 'g'), replacement);
|
|
412
|
+
if (result !== before) count++;
|
|
413
|
+
}
|
|
414
|
+
return { tag: result, count };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function applyUpdateEventRenames (tag, canonical) {
|
|
418
|
+
const eventMap = UPDATE_EVENT_RENAMES[canonical];
|
|
419
|
+
if (!eventMap) return { tag, count: 0 };
|
|
420
|
+
let result = tag;
|
|
421
|
+
let count = 0;
|
|
422
|
+
for (const [oldEvt, newEvt] of Object.entries(eventMap)) {
|
|
423
|
+
const before = result;
|
|
424
|
+
result = result.replace(new RegExp(`@update:${escapeRe(oldEvt)}\\b`, 'g'), `@update:${newEvt}`);
|
|
425
|
+
if (result !== before) count++;
|
|
426
|
+
}
|
|
427
|
+
return { tag: result, count };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* `v-model:oldArg` → `v-model` and `@update:oldEvent` → `@update:newEvent` per component.
|
|
432
|
+
*/
|
|
433
|
+
function applyVModelAndEventRenames (tag, canonical) {
|
|
434
|
+
const r1 = applyVModelArgRenames(tag, canonical);
|
|
435
|
+
const r2 = applyUpdateEventRenames(r1.tag, canonical);
|
|
436
|
+
return { tag: r2.tag, count: r1.count + r2.count };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Remove duplicate `@eventName` bindings within a single tag string, keeping
|
|
441
|
+
* the first occurrence. This handles the case where multiple source events
|
|
442
|
+
* (e.g. `@input` and `@change`) both map to the same target event.
|
|
443
|
+
*/
|
|
444
|
+
function deduplicateEventBindings (tag) {
|
|
445
|
+
const seen = new Set();
|
|
446
|
+
let removedCount = 0;
|
|
447
|
+
const cleaned = tag.replace(/ @([\w:.-]+)(?:=(?:"[^"]*"|'[^']*'))?/g, (match, eventName) => {
|
|
448
|
+
if (seen.has(eventName)) {
|
|
449
|
+
removedCount++;
|
|
450
|
+
return '';
|
|
451
|
+
}
|
|
452
|
+
seen.add(eventName);
|
|
453
|
+
return match;
|
|
454
|
+
});
|
|
455
|
+
return { tag: cleaned, removedCount };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Legacy `@input` / `@change` component events → `@update:model-value`.
|
|
460
|
+
* When multiple source events map to the same target, the first is kept and
|
|
461
|
+
* subsequent duplicates are removed.
|
|
462
|
+
*/
|
|
463
|
+
function applyEmitEventRenames (tag, canonical) {
|
|
464
|
+
const eventMap = EMIT_EVENT_RENAMES[canonical];
|
|
465
|
+
if (!eventMap) return { tag, count: 0 };
|
|
466
|
+
let result = tag;
|
|
467
|
+
let count = 0;
|
|
468
|
+
for (const [oldEvt, newEvt] of Object.entries(eventMap)) {
|
|
469
|
+
const before = result;
|
|
470
|
+
result = result.replace(new RegExp(`@${escapeRe(oldEvt)}\\b`, 'g'), `@${newEvt}`);
|
|
471
|
+
if (result !== before) count++;
|
|
472
|
+
}
|
|
473
|
+
const { tag: deduped, removedCount } = deduplicateEventBindings(result);
|
|
474
|
+
return { tag: deduped, count: count - removedCount };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Warn about removed rootClass / root-class prop on components not in ROOT_CLASS_RENAMES.
|
|
479
|
+
*/
|
|
480
|
+
function detectRootClass (tag, tagName, canonical) {
|
|
481
|
+
if (ROOT_CLASS_RENAMES[canonical]) return [];
|
|
482
|
+
const re = /\broot-class(?:=|\b)|\brootClass(?:=|\b)/g;
|
|
483
|
+
const matches = tag.match(re);
|
|
484
|
+
if (!matches) return [];
|
|
485
|
+
return matches.map(() =>
|
|
486
|
+
`rootClass / root-class has been removed from <${tagName}>` +
|
|
487
|
+
' — apply classes directly on the component element or use a wrapper.',
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// Per-tag Entry Point
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Returns the canonical kebab-case component name for config lookups.
|
|
497
|
+
* PascalCase → kebab-case: DtAvatar → dt-avatar
|
|
498
|
+
*/
|
|
499
|
+
function normalizeTagName (tag) {
|
|
500
|
+
if (tag.startsWith('dt-')) return tag;
|
|
501
|
+
return tag.replace(/(?<=[a-z])([A-Z])/g, '-$1').toLowerCase();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function transformTag (fullTag, tagName) {
|
|
505
|
+
const canonical = normalizeTagName(tagName);
|
|
506
|
+
let tag = fullTag;
|
|
507
|
+
let count = 0;
|
|
508
|
+
const warnings = [];
|
|
509
|
+
|
|
510
|
+
const r1 = applyVModelAndEventRenames(tag, canonical);
|
|
511
|
+
tag = r1.tag; count += r1.count;
|
|
512
|
+
|
|
513
|
+
const r2 = applyShowToOpen(tag, canonical);
|
|
514
|
+
tag = r2.tag; count += r2.count;
|
|
515
|
+
|
|
516
|
+
const r3 = applyPropValueRenames(tag);
|
|
517
|
+
tag = r3.tag; count += r3.count;
|
|
518
|
+
|
|
519
|
+
const r4 = applyPropRenames(tag, canonical);
|
|
520
|
+
tag = r4.tag; count += r4.count;
|
|
521
|
+
|
|
522
|
+
const r5 = applyInvertedBoolProps(tag, canonical);
|
|
523
|
+
tag = r5.tag; count += r5.count;
|
|
524
|
+
warnings.push(...r5.warnings);
|
|
525
|
+
|
|
526
|
+
const r6 = applyRootClassRename(tag, canonical);
|
|
527
|
+
tag = r6.tag; count += r6.count;
|
|
528
|
+
warnings.push(...r6.warnings);
|
|
529
|
+
|
|
530
|
+
const r7 = applyEmitEventRenames(tag, canonical);
|
|
531
|
+
tag = r7.tag; count += r7.count;
|
|
532
|
+
|
|
533
|
+
warnings.push(...detectRootClass(tag, tagName, canonical));
|
|
534
|
+
|
|
535
|
+
return { result: tag, count, warnings };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Full-file Slot Transforms
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
function transformSlots (content) {
|
|
543
|
+
let result = content;
|
|
544
|
+
let count = 0;
|
|
545
|
+
|
|
546
|
+
for (const { old: oldName, new: newName } of SLOT_RENAMES) {
|
|
547
|
+
const patterns = [
|
|
548
|
+
[new RegExp(`#${escapeRe(oldName)}\\b`, 'g'), `#${newName}`],
|
|
549
|
+
[new RegExp(`v-slot:${escapeRe(oldName)}\\b`, 'g'), `v-slot:${newName}`],
|
|
550
|
+
[new RegExp(`slot="${escapeRe(oldName)}"`, 'g'), `slot="${newName}"`],
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
for (const [re, replacement] of patterns) {
|
|
554
|
+
const before = result;
|
|
555
|
+
result = result.replace(re, replacement);
|
|
556
|
+
if (result !== before) count += (before.match(re) || []).length;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { result, count };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ---------------------------------------------------------------------------
|
|
564
|
+
// Main Transform
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
|
|
567
|
+
function transformContent (content) {
|
|
568
|
+
let transformed = content;
|
|
569
|
+
let totalCount = 0;
|
|
570
|
+
const allWarnings = [];
|
|
571
|
+
|
|
572
|
+
const { result: slotResult, count: slotCount } = transformSlots(transformed);
|
|
573
|
+
transformed = slotResult;
|
|
574
|
+
totalCount += slotCount;
|
|
575
|
+
|
|
576
|
+
DT_TAG_RE.lastIndex = 0;
|
|
577
|
+
transformed = transformed.replace(DT_TAG_RE, (fullMatch, tagName) => {
|
|
578
|
+
const { result, count, warnings } = transformTag(fullMatch, tagName);
|
|
579
|
+
totalCount += count;
|
|
580
|
+
if (warnings.length) allWarnings.push(...warnings);
|
|
581
|
+
return result;
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
return { transformed, count: totalCount, warnings: allWarnings };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export { transformContent };
|
|
588
|
+
|
|
589
|
+
// ---------------------------------------------------------------------------
|
|
590
|
+
// File Finder
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
|
|
593
|
+
async function findFiles (dir, extensions, ignore = []) {
|
|
594
|
+
const results = [];
|
|
595
|
+
|
|
596
|
+
async function walk (currentDir) {
|
|
597
|
+
try {
|
|
598
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
599
|
+
for (const entry of entries) {
|
|
600
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
601
|
+
if (ignore.some(ig => fullPath.includes(ig))) continue;
|
|
602
|
+
if (entry.isDirectory()) {
|
|
603
|
+
await walk(fullPath);
|
|
604
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
605
|
+
results.push(fullPath);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} catch {
|
|
609
|
+
// skip unreadable dirs
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
await walk(dir);
|
|
614
|
+
return results;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ---------------------------------------------------------------------------
|
|
618
|
+
// CLI
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
|
|
621
|
+
function printHelp () {
|
|
622
|
+
console.log(`
|
|
623
|
+
Usage: npx dialtone-migrate-props [options]
|
|
624
|
+
|
|
625
|
+
Migrates Dialtone component prop/slot/event breaking changes introduced in
|
|
626
|
+
DLT-3100, DLT-3157, DLT-3159, DLT-3160, DLT-3161, DLT-3282, DLT-3283, DLT-3284.
|
|
627
|
+
|
|
628
|
+
Migrations applied:
|
|
629
|
+
clickable → interactive (dt-avatar)
|
|
630
|
+
selected-values → model-value (dt-checkbox-group)
|
|
631
|
+
v-model:selected-values → v-model (dt-checkbox-group)
|
|
632
|
+
@update:selected-values → @update:model-value (dt-checkbox-group)
|
|
633
|
+
@input → @update:model-value (dt-input, dt-radio, dt-radio-group, dt-combobox-multi-select, dt-rich-text-editor, dt-input-group)
|
|
634
|
+
@change → @update:model-value (dt-toggle, dt-select-menu)
|
|
635
|
+
show → open (dt-modal, dt-toast, dt-tooltip)
|
|
636
|
+
@update:show → @update:open (dt-modal, dt-toast, dt-tooltip)
|
|
637
|
+
v-model:show → v-model:open (dt-modal, dt-toast, dt-tooltip)
|
|
638
|
+
title → header-text (dt-banner, dt-notice, dt-toast, dt-modal)
|
|
639
|
+
title-id → header-id (dt-banner, dt-notice, dt-toast)
|
|
640
|
+
banner-title → banner-header-text (dt-modal)
|
|
641
|
+
label-visible → show-label (dt-checkbox, dt-combobox, dt-input, etc.)
|
|
642
|
+
hide-close → :show-close="false" (dt-banner, dt-chip, dt-modal, dt-notice, dt-notice-action, dt-toast)
|
|
643
|
+
hide-icon → :show-icon="false" (dt-banner, dt-notice, dt-toast)
|
|
644
|
+
hide-action → :show-action="false" (dt-banner, dt-notice, dt-notice-action, dt-toast)
|
|
645
|
+
hide-clear → :show-clear="false" (dt-filter-pill)
|
|
646
|
+
hide-edges → :show-edges="false" (dt-pagination)
|
|
647
|
+
hide-divider → :show-divider="false" (dt-segmented-control)
|
|
648
|
+
prevent-typing → :allow-typing="false" (dt-rich-text-editor, dt-recipe-message-input)
|
|
649
|
+
hide-link-bubble-menu → :show-link-bubble-menu="false" (dt-rich-text-editor)
|
|
650
|
+
hide-actions → :show-actions="false" (dt-recipe-contact-centers-row)
|
|
651
|
+
hide-on-click → close-on-click (dt-popover, same semantics — not inverted)
|
|
652
|
+
root-class → class (dt-input, dt-checkbox, dt-radio, dt-select-menu, dt-breadcrumb-item, dt-split-button)
|
|
653
|
+
wrapper-class → class (dt-toggle, dt-feed-item-pill)
|
|
654
|
+
container-class → class (dt-card)
|
|
655
|
+
kind="danger" → kind="critical" (any dt-* component)
|
|
656
|
+
kind="error" → kind="critical" (any dt-* component)
|
|
657
|
+
kind="success" → kind="positive" (any dt-* component)
|
|
658
|
+
type="success" → type="positive" (dt-badge)
|
|
659
|
+
tone="success" → tone="positive" (dt-text, dt-link)
|
|
660
|
+
link-kind="danger" → link-kind="critical" (dt-button)
|
|
661
|
+
link-kind="success" → link-kind="positive" (dt-button)
|
|
662
|
+
kind → tone (dt-link, with value renames applied in same pass)
|
|
663
|
+
banner-kind="error" → banner-kind="critical" (dt-modal)
|
|
664
|
+
banner-kind="success" → banner-kind="positive" (dt-modal)
|
|
665
|
+
validation-state="error" → validation-state="critical"
|
|
666
|
+
validation-state="success" → validation-state="positive"
|
|
667
|
+
#titleOverride → #header (slots)
|
|
668
|
+
#labelSlot → #label (slots)
|
|
669
|
+
#headingSlot → #heading (slots)
|
|
670
|
+
rootClass / root-class → (warns — manual fix required for unknown components)
|
|
671
|
+
|
|
672
|
+
Options:
|
|
673
|
+
--cwd <path> Working directory (default: current directory)
|
|
674
|
+
--dry-run Show changes without applying them
|
|
675
|
+
--yes Apply all changes without prompting
|
|
676
|
+
--help Show help
|
|
677
|
+
`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function prompt (question) {
|
|
681
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
682
|
+
return new Promise(resolve => {
|
|
683
|
+
rl.question(question, answer => {
|
|
684
|
+
rl.close();
|
|
685
|
+
resolve(answer.trim().toLowerCase());
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseArgs (args) {
|
|
691
|
+
const cwdIndex = args.indexOf('--cwd');
|
|
692
|
+
return {
|
|
693
|
+
help: args.includes('--help'),
|
|
694
|
+
dryRun: args.includes('--dry-run'),
|
|
695
|
+
autoYes: args.includes('--yes'),
|
|
696
|
+
cwd: cwdIndex !== -1 && args[cwdIndex + 1]
|
|
697
|
+
? path.resolve(args[cwdIndex + 1])
|
|
698
|
+
: process.cwd(),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async function scanFiles (cwd) {
|
|
703
|
+
const extensions = ['.vue', '.md', '.html', '.js', '.ts', '.jsx', '.tsx'];
|
|
704
|
+
const ignore = ['node_modules', 'dist', '.git', '.vuepress/public'];
|
|
705
|
+
const files = await findFiles(cwd, extensions, ignore);
|
|
706
|
+
|
|
707
|
+
const changes = [];
|
|
708
|
+
const allWarnings = [];
|
|
709
|
+
|
|
710
|
+
for (const file of files) {
|
|
711
|
+
const content = await fs.readFile(file, 'utf8');
|
|
712
|
+
const { transformed, count, warnings } = transformContent(content);
|
|
713
|
+
if (count > 0) changes.push({ file, content, transformed, count });
|
|
714
|
+
if (warnings.length) allWarnings.push(...warnings.map(w => `${path.relative(cwd, file)}: ${w}`));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return { changes, allWarnings };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async function applyChanges (changes, autoYes) {
|
|
721
|
+
if (!autoYes) {
|
|
722
|
+
const answer = await prompt('\nApply changes? (y/N) ');
|
|
723
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
724
|
+
console.log('Cancelled.');
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
for (const { file, transformed } of changes) {
|
|
729
|
+
await fs.writeFile(file, transformed, 'utf8');
|
|
730
|
+
}
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function printWarnings (warnings) {
|
|
735
|
+
if (!warnings.length) return;
|
|
736
|
+
console.log('⚠ Manual action required:\n');
|
|
737
|
+
for (const w of warnings) console.log(` ${w}`);
|
|
738
|
+
console.log();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function printChangeSummary (changes, cwd) {
|
|
742
|
+
const total = changes.reduce((sum, c) => sum + c.count, 0);
|
|
743
|
+
console.log(`Found ${total} change(s) across ${changes.length} file(s):\n`);
|
|
744
|
+
for (const { file, count } of changes) {
|
|
745
|
+
console.log(` ${path.relative(cwd, file)} (${count} change${count > 1 ? 's' : ''})`);
|
|
746
|
+
}
|
|
747
|
+
return total;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function main () {
|
|
751
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
752
|
+
if (opts.help) { printHelp(); process.exit(0); }
|
|
753
|
+
|
|
754
|
+
console.log(`\nScanning ${opts.cwd} for Dialtone prop/slot/event usage...\n`);
|
|
755
|
+
|
|
756
|
+
const { changes, allWarnings } = await scanFiles(opts.cwd);
|
|
757
|
+
printWarnings(allWarnings);
|
|
758
|
+
|
|
759
|
+
if (changes.length === 0 && allWarnings.length === 0) {
|
|
760
|
+
console.log('No matching usage found. Nothing to migrate.');
|
|
761
|
+
process.exit(0);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (changes.length === 0) {
|
|
765
|
+
console.log('No automated code changes needed. See manual action items above.');
|
|
766
|
+
process.exit(0);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const total = printChangeSummary(changes, opts.cwd);
|
|
770
|
+
|
|
771
|
+
if (opts.dryRun) {
|
|
772
|
+
const warnNote = allWarnings.length ? ` ${allWarnings.length} warning(s) require manual action.` : '';
|
|
773
|
+
console.log(`\n--dry-run: No files were modified.${warnNote}\n`);
|
|
774
|
+
process.exit(0);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const applied = await applyChanges(changes, opts.autoYes);
|
|
778
|
+
if (applied) console.log(`\nMigrated ${total} reference(s) across ${changes.length} file(s).\n`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const isDirectRun = (() => {
|
|
782
|
+
try {
|
|
783
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
784
|
+
} catch {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
})();
|
|
788
|
+
|
|
789
|
+
if (isDirectRun) {
|
|
790
|
+
main().catch(err => {
|
|
791
|
+
console.error(err);
|
|
792
|
+
process.exit(1);
|
|
793
|
+
});
|
|
794
|
+
}
|