@taiga-ui/cdk
Version:
Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance
236 lines • 14.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrateMultiSelect = migrateMultiSelect;
const insert_todo_1 = require("../../../../utils/insert-todo");
const elements_1 = require("../../../../utils/templates/elements");
const template_resource_1 = require("../../../../utils/templates/template-resource");
const control_state_attrs_1 = require("../../../utils/templates/control-state-attrs");
const replace_tag_1 = require("../../../utils/templates/replace-tag");
const DOCS_LINK = 'https://taiga-ui.dev/components/input-chip';
const VALUE_CONTENT_ATTR = 'valueContent';
const CONTENT_ATTR = 'content';
const TEXTFIELD_LABEL_OUTSIDE_ATTR = 'tuiTextfieldLabelOutside';
const AUTO_COLOR_ATTR = 'autoColor';
const AUTO_COLOR_TODO = `<!-- ${insert_todo_1.TODO_MARK} [autoColor] was removed. Use tuiChip with auto-color appearance instead. See https://taiga-ui.dev/components/chip#auto-color -->\n`;
const PLACEHOLDER_ATTR = 'placeholder';
const PLACEHOLDER_BINDING_ATTR = '[placeholder]';
const EDITABLE_ATTR = 'editable';
const EDITABLE_BINDING_ATTR = '[editable]';
const TAG_VALIDATOR_BINDING_ATTR = '[tagValidator]';
const SEARCH_CHANGE_OUTPUT = '(searchChange)';
// [search] has no template-level v5 equivalent (requires viewChild + signal)
const MANUAL_SEARCH_BINDING_ATTRS = new Set(['[(search)]', '[search]', 'search']);
const CONTROL_ATTR_NAMES = [
'formControlName',
'[formControl]',
'formControl',
'[(ngModel)]',
'[ngModel]',
'ngModel',
];
const CONTROL_ATTRS = new Set(CONTROL_ATTR_NAMES.map((name) => name.toLowerCase()));
function migrateMultiSelect({ resource, recorder, fileSystem, }) {
const template = (0, template_resource_1.getTemplateFromTemplateResource)(resource, fileSystem);
const templateOffset = (0, template_resource_1.getTemplateOffset)(resource);
const elements = (0, elements_1.findElementsByTagName)(template, 'tui-multi-select');
elements.forEach((element) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const startOffset = (_a = element.sourceCodeLocation) === null || _a === void 0 ? void 0 : _a.startOffset;
const controlAttrs = element.attrs.filter((attr) => CONTROL_ATTRS.has(attr.name.toLowerCase()));
const controlStateAttrs = (0, control_state_attrs_1.getControlStateAttrs)(element);
renameAttr(recorder, templateOffset, element, `[${VALUE_CONTENT_ATTR}]`, `[${CONTENT_ATTR}]`);
renameAttr(recorder, templateOffset, element, VALUE_CONTENT_ATTR, CONTENT_ATTR);
removeAttr(recorder, templateOffset, element, `[${TEXTFIELD_LABEL_OUTSIDE_ATTR}]`, template);
removeAttr(recorder, templateOffset, element, TEXTFIELD_LABEL_OUTSIDE_ATTR, template);
const hasAutoColor = element.attrs.some((attr) => [
`[${AUTO_COLOR_ATTR}]`.toLowerCase(),
AUTO_COLOR_ATTR.toLowerCase(),
].includes(attr.name.toLowerCase()));
removeAttr(recorder, templateOffset, element, `[${AUTO_COLOR_ATTR}]`, template);
removeAttr(recorder, templateOffset, element, AUTO_COLOR_ATTR, template);
if (hasAutoColor && typeof startOffset === 'number') {
const lineStart = template.lastIndexOf('\n', startOffset) + 1;
const indent = (_c = (_b = /^[ \t]*/.exec(template.slice(lineStart, startOffset))) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : '';
recorder.insertLeft(templateOffset + startOffset, `${AUTO_COLOR_TODO}${indent}`);
}
const placeholder = extractPlaceholder(element);
removeAttr(recorder, templateOffset, element, PLACEHOLDER_ATTR, template);
removeAttr(recorder, templateOffset, element, PLACEHOLDER_BINDING_ATTR, template);
const selectLike = extractSelectLike(element);
removeAttr(recorder, templateOffset, element, EDITABLE_ATTR, template);
removeAttr(recorder, templateOffset, element, EDITABLE_BINDING_ATTR, template);
// Extract [tagValidator] → <tui-input-chip *tuiItem="let ctx" [appearance]="...">
const tagValidatorExpr = extractTagValidator(element);
if (tagValidatorExpr !== null) {
removeAttr(recorder, templateOffset, element, TAG_VALIDATOR_BINDING_ATTR, template);
}
// (searchChange) → (input) on <input> with type narrowing
const searchChangeExpr = extractSearchChange(element);
if (searchChangeExpr !== null) {
removeAttr(recorder, templateOffset, element, SEARCH_CHANGE_OUTPUT, template);
}
// [search] / [(search)] / search — no template-level v5 equivalent, remove from element
const hasManualSearchAttrs = element.attrs.some((attr) => MANUAL_SEARCH_BINDING_ATTRS.has(attr.name.toLowerCase()));
if (hasManualSearchAttrs) {
for (const attrName of MANUAL_SEARCH_BINDING_ATTRS) {
removeAttr(recorder, templateOffset, element, attrName, template);
}
}
// Remove form control attrs from element (they go on <input>)
controlAttrs.forEach((attr) => {
removeAttr(recorder, templateOffset, element, attr.name, template);
});
(0, control_state_attrs_1.removeControlStateAttrs)(recorder, templateOffset, element, template, controlStateAttrs);
const hasChevron = element.attrs.some((attr) => attr.name.toLowerCase() === 'tuiChevron'.toLowerCase());
(0, replace_tag_1.replaceTag)(recorder, element.sourceCodeLocation, 'tui-multi-select', 'tui-textfield', template, templateOffset, ['multi', ...(hasChevron ? [] : ['tuiChevron'])]);
// Check if <input> already exists inside
const existingInputs = element.childNodes.filter((node) => node.nodeName === 'input');
if (existingInputs.length) {
existingInputs.forEach((input) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const hasInputChip = input.attrs.some((attr) => attr.name.toLowerCase() === 'tuiInputChip'.toLowerCase());
if (!hasInputChip) {
const inputStartOffset = ((_e = (_c = (_b = (_a = input.sourceCodeLocation) === null || _a === void 0 ? void 0 : _a.startTag) === null || _b === void 0 ? void 0 : _b.startOffset) !== null && _c !== void 0 ? _c : (_d = input.sourceCodeLocation) === null || _d === void 0 ? void 0 : _d.startOffset) !== null && _e !== void 0 ? _e : 0) + '<input'.length;
recorder.insertRight(templateOffset + inputStartOffset, ' tuiInputChip');
}
const formAttrs = formatControlAttrs(controlAttrs);
const controlStateStr = (0, control_state_attrs_1.stringifyControlStateAttrs)(controlStateAttrs);
const extras = `${formAttrs ? ` ${formAttrs}` : ''}${controlStateStr}`;
if (!extras) {
return;
}
const insertOffset = ((_k = (_h = (_g = (_f = input.sourceCodeLocation) === null || _f === void 0 ? void 0 : _f.startTag) === null || _g === void 0 ? void 0 : _g.startOffset) !== null && _h !== void 0 ? _h : (_j = input.sourceCodeLocation) === null || _j === void 0 ? void 0 : _j.startOffset) !== null && _k !== void 0 ? _k : 0) + '<input'.length;
recorder.insertRight(templateOffset + insertOffset, extras);
});
return;
}
// Insert new <input tuiInputChip /> before first element child
const formAttrs = formatControlAttrs(controlAttrs);
const placeholderAttr = formatPlaceholderAttr(placeholder);
const selectLikeAttr = selectLike === true ? ' tuiSelectLike' : '';
// selectLike is a string when [editable]="someExpr" was dynamic
const readonlyAttr = typeof selectLike === 'string' ? ` [readOnly]="!${selectLike}"` : '';
const searchChangeAttr = searchChangeExpr
? ` (input)="${searchChangeExpr}(($event.target as HTMLInputElement).value)"`
: '';
const manualSearchTodo = hasManualSearchAttrs
? `<!-- ${insert_todo_1.TODO_MARK} [search] was removed. Use (input) on <input tuiInputChip (input)="onSearch($any($event).target.value)"> to track changes; no direct equivalent for programmatic writes. See: ${DOCS_LINK} -->\n`
: '';
const searchChangeTodo = searchChangeExpr
? `<!-- ${insert_todo_1.TODO_MARK} (searchChange) was replaced by (input) on <input tuiInputChip (input)="onSearch($any($event).target.value)">. See: ${DOCS_LINK} -->\n`
: '';
const firstElementChildOffset = (_e = (_d = element.childNodes.find((node) => node.nodeName !== '#text')) === null || _d === void 0 ? void 0 : _d.sourceCodeLocation) === null || _e === void 0 ? void 0 : _e.startOffset;
const insertOffset = (_h = firstElementChildOffset !== null && firstElementChildOffset !== void 0 ? firstElementChildOffset : (_g = (_f = element.sourceCodeLocation) === null || _f === void 0 ? void 0 : _f.endTag) === null || _g === void 0 ? void 0 : _g.startOffset) !== null && _h !== void 0 ? _h : 0;
const chipItemTemplate = tagValidatorExpr === null
? ''
: `<tui-input-chip *tuiItem="let ctx" [appearance]="${tagValidatorExpr}(ctx.item) ? '' : 'negative'" />\n`;
const controlStateStr = (0, control_state_attrs_1.stringifyControlStateAttrs)(controlStateAttrs);
const inputTemplate = `${manualSearchTodo}${searchChangeTodo}<input tuiInputChip${selectLikeAttr}${readonlyAttr}${placeholderAttr ? ` ${placeholderAttr}` : ''}${formAttrs ? ` ${formAttrs}` : ''}${searchChangeAttr}${controlStateStr} />\n${chipItemTemplate}`;
recorder.insertRight(templateOffset + insertOffset, inputTemplate);
});
}
/**
* Returns:
* true — [editable]="false" → add tuiSelectLike
* false — [editable]="true" or absent → nothing to do
* string (expr) — [editable]="someExpr" → add [readOnly]="!someExpr" on <input>
*/
function extractSelectLike(element) {
const bindingAttr = element.attrs.find((attr) => attr.name.toLowerCase() === EDITABLE_BINDING_ATTR);
if (bindingAttr) {
if (bindingAttr.value === 'false') {
return true;
}
if (bindingAttr.value === 'true') {
return false;
}
// Dynamic expression — return it so caller can generate [readOnly]="!expr"
return bindingAttr.value;
}
const plainAttr = element.attrs.find((attr) => attr.name.toLowerCase() === EDITABLE_ATTR);
return plainAttr ? plainAttr.value === 'false' : false;
}
/**
* Extracts the handler expression from (searchChange)="handler($event)".
* Returns just "handler" so we can generate (input)="handler(($event.target as HTMLInputElement).value)".
* Returns null if the attr is absent.
*/
function extractSearchChange(element) {
const attr = element.attrs.find((a) => a.name.toLowerCase() === SEARCH_CHANGE_OUTPUT.toLowerCase());
if (!attr) {
return null;
}
// Strip trailing ($event) call if present: "fn($event)" → "fn"
return attr.value.replace(/\(\$event\)\s*$/, '').trim();
}
/**
* Extracts the expression from [tagValidator]="expr".
* Returns the expression string, or null if attr is absent.
* The expression is used to generate:
* <tui-input-chip *tuiItem="let ctx" [appearance]="expr(ctx.item) ? '' : 'negative'" />
*/
function extractTagValidator(element) {
var _a;
const attr = element.attrs.find((a) => a.name.toLowerCase() === TAG_VALIDATOR_BINDING_ATTR.toLowerCase());
return (_a = attr === null || attr === void 0 ? void 0 : attr.value) !== null && _a !== void 0 ? _a : null;
}
function extractPlaceholder(element) {
const bindingAttr = element.attrs.find((attr) => attr.name.toLowerCase() === PLACEHOLDER_BINDING_ATTR);
if (bindingAttr) {
return { attr: PLACEHOLDER_BINDING_ATTR, value: bindingAttr.value };
}
const plainAttr = element.attrs.find((attr) => attr.name.toLowerCase() === PLACEHOLDER_ATTR);
return plainAttr ? { attr: PLACEHOLDER_ATTR, value: plainAttr.value } : null;
}
function formatPlaceholderAttr(placeholder) {
return placeholder ? `${placeholder.attr}="${placeholder.value}"` : '';
}
function renameAttr(recorder, templateOffset, element, from, to) {
var _a, _b;
const attrLocation = (_b = (_a = element.sourceCodeLocation) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b[from.toLowerCase()];
if (!attrLocation) {
return;
}
recorder.remove(templateOffset + attrLocation.startOffset, from.length);
recorder.insertRight(templateOffset + attrLocation.startOffset, to);
}
function removeAttr(recorder, templateOffset, element, name, template) {
var _a, _b;
const attrLocation = (_b = (_a = element.sourceCodeLocation) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b[name.toLowerCase()];
if (!attrLocation) {
return;
}
const lineStart = template.lastIndexOf('\n', attrLocation.startOffset) + 1;
const lineEnd = template.indexOf('\n', attrLocation.endOffset);
const attrText = template.slice(attrLocation.startOffset, attrLocation.endOffset);
const lineText = lineEnd === -1 ? '' : template.slice(lineStart, lineEnd);
if (lineText.trim() === attrText.trim() && lineEnd !== -1) {
recorder.remove(templateOffset + lineStart, lineEnd - lineStart + 1);
return;
}
recorder.remove(templateOffset + attrLocation.startOffset - 1, attrLocation.endOffset - attrLocation.startOffset + 1);
}
function formatControlAttrs(attrs) {
return attrs
.map(({ name, value }) => `${normalizeAttrName(name)}="${value}"`)
.join(' ');
}
function normalizeAttrName(name) {
switch (name.toLowerCase()) {
case '[formControl]'.toLowerCase():
return '[formControl]';
case '[ngModel]'.toLowerCase():
return '[ngModel]';
case 'formControl'.toLowerCase():
return 'formControl';
case 'formControlName'.toLowerCase():
return 'formControlName';
case '[(ngmodel)]':
return '[(ngModel)]';
case 'ngmodel':
return 'ngModel';
default:
return name;
}
}
//# sourceMappingURL=migrate-multi-select.js.map