@koobiq/components 20.1.0 → 20.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/accordion/_accordion-theme.scss +1 -1
  2. package/accordion/accordion-tokens.scss +1 -0
  3. package/accordion/{accordion-trigger.component.scss → accordion-trigger.scss} +4 -0
  4. package/accordion/{accordion.component.scss → accordion.scss} +3 -3
  5. package/accordion/index.d.ts +215 -119
  6. package/alert/_alert-theme.scss +14 -0
  7. package/alert/alert.component.scss +24 -13
  8. package/alert/index.d.ts +63 -29
  9. package/autocomplete/autocomplete.scss +2 -1
  10. package/autocomplete/index.d.ts +35 -29
  11. package/badge/index.d.ts +2 -3
  12. package/clamped-text/index.d.ts +3 -2
  13. package/core/index.d.ts +116 -11
  14. package/core/styles/_core.scss +2 -0
  15. package/core/styles/common/_animation.scss +31 -24
  16. package/core/styles/common/_connected-overlay.scss +36 -0
  17. package/core/styles/common/_select.scss +7 -2
  18. package/dropdown/dropdown-item.scss +16 -0
  19. package/dropdown/dropdown-tokens.scss +2 -1
  20. package/dropdown/dropdown.scss +9 -0
  21. package/dropdown/index.d.ts +73 -6
  22. package/fesm2022/koobiq-components-accordion.mjs +406 -303
  23. package/fesm2022/koobiq-components-accordion.mjs.map +1 -1
  24. package/fesm2022/koobiq-components-actions-panel.mjs +1 -1
  25. package/fesm2022/koobiq-components-actions-panel.mjs.map +1 -1
  26. package/fesm2022/koobiq-components-alert.mjs +88 -48
  27. package/fesm2022/koobiq-components-alert.mjs.map +1 -1
  28. package/fesm2022/koobiq-components-app-switcher.mjs +1 -1
  29. package/fesm2022/koobiq-components-app-switcher.mjs.map +1 -1
  30. package/fesm2022/koobiq-components-autocomplete.mjs +37 -23
  31. package/fesm2022/koobiq-components-autocomplete.mjs.map +1 -1
  32. package/fesm2022/koobiq-components-badge.mjs +5 -4
  33. package/fesm2022/koobiq-components-badge.mjs.map +1 -1
  34. package/fesm2022/koobiq-components-breadcrumbs.mjs +1 -1
  35. package/fesm2022/koobiq-components-breadcrumbs.mjs.map +1 -1
  36. package/fesm2022/koobiq-components-clamped-text.mjs +5 -0
  37. package/fesm2022/koobiq-components-clamped-text.mjs.map +1 -1
  38. package/fesm2022/koobiq-components-code-block.mjs +1 -1
  39. package/fesm2022/koobiq-components-code-block.mjs.map +1 -1
  40. package/fesm2022/koobiq-components-core.mjs +98 -4
  41. package/fesm2022/koobiq-components-core.mjs.map +1 -1
  42. package/fesm2022/koobiq-components-datepicker.mjs +10 -7
  43. package/fesm2022/koobiq-components-datepicker.mjs.map +1 -1
  44. package/fesm2022/koobiq-components-dropdown.mjs +99 -20
  45. package/fesm2022/koobiq-components-dropdown.mjs.map +1 -1
  46. package/fesm2022/koobiq-components-file-upload.mjs +2 -2
  47. package/fesm2022/koobiq-components-file-upload.mjs.map +1 -1
  48. package/fesm2022/koobiq-components-filter-bar.mjs +203 -32
  49. package/fesm2022/koobiq-components-filter-bar.mjs.map +1 -1
  50. package/fesm2022/koobiq-components-form-field.mjs +4 -4
  51. package/fesm2022/koobiq-components-form-field.mjs.map +1 -1
  52. package/fesm2022/koobiq-components-icon.mjs +14 -5
  53. package/fesm2022/koobiq-components-icon.mjs.map +1 -1
  54. package/fesm2022/koobiq-components-inline-edit.mjs +4 -2
  55. package/fesm2022/koobiq-components-inline-edit.mjs.map +1 -1
  56. package/fesm2022/koobiq-components-navbar.mjs +117 -22
  57. package/fesm2022/koobiq-components-navbar.mjs.map +1 -1
  58. package/fesm2022/koobiq-components-notification-center.mjs +19 -4
  59. package/fesm2022/koobiq-components-notification-center.mjs.map +1 -1
  60. package/fesm2022/koobiq-components-popover.mjs +5 -0
  61. package/fesm2022/koobiq-components-popover.mjs.map +1 -1
  62. package/fesm2022/koobiq-components-search-expandable.mjs +43 -8
  63. package/fesm2022/koobiq-components-search-expandable.mjs.map +1 -1
  64. package/fesm2022/koobiq-components-select.mjs +36 -45
  65. package/fesm2022/koobiq-components-select.mjs.map +1 -1
  66. package/fesm2022/koobiq-components-split-button.mjs +3 -10
  67. package/fesm2022/koobiq-components-split-button.mjs.map +1 -1
  68. package/fesm2022/koobiq-components-tabs.mjs +29 -17
  69. package/fesm2022/koobiq-components-tabs.mjs.map +1 -1
  70. package/fesm2022/koobiq-components-tags.mjs +1 -1
  71. package/fesm2022/koobiq-components-tags.mjs.map +1 -1
  72. package/fesm2022/koobiq-components-timepicker.mjs +3 -4
  73. package/fesm2022/koobiq-components-timepicker.mjs.map +1 -1
  74. package/fesm2022/koobiq-components-timezone.mjs +3 -5
  75. package/fesm2022/koobiq-components-timezone.mjs.map +1 -1
  76. package/fesm2022/koobiq-components-toast.mjs +1 -1
  77. package/fesm2022/koobiq-components-toast.mjs.map +1 -1
  78. package/fesm2022/koobiq-components-tooltip.mjs +118 -6
  79. package/fesm2022/koobiq-components-tooltip.mjs.map +1 -1
  80. package/fesm2022/koobiq-components-tree-select.mjs +39 -31
  81. package/fesm2022/koobiq-components-tree-select.mjs.map +1 -1
  82. package/filter-bar/_filter-bar-theme.scss +0 -4
  83. package/filter-bar/filter-bar-tokens.scss +3 -0
  84. package/filter-bar/filters.scss +3 -2
  85. package/filter-bar/index.d.ts +62 -3
  86. package/filter-bar/pipes/base-pipe.scss +0 -11
  87. package/filter-bar/pipes/pipe-input.scss +10 -0
  88. package/icon/icon-button.scss +3 -1
  89. package/icon/index.d.ts +15 -2
  90. package/navbar/_navbar-item_vertical.scss +7 -5
  91. package/navbar/index.d.ts +38 -4
  92. package/navbar/navbar-brand.scss +17 -7
  93. package/notification-center/index.d.ts +2 -1
  94. package/package.json +3 -3
  95. package/prebuilt-themes/dark-theme.css +1 -1
  96. package/prebuilt-themes/light-theme.css +1 -1
  97. package/prebuilt-themes/theme.css +1 -1
  98. package/schematics/collection.json +10 -0
  99. package/schematics/migrations/alert-signals/README.md +64 -0
  100. package/schematics/migrations/alert-signals/data.js +70 -0
  101. package/schematics/migrations/alert-signals/index.js +350 -0
  102. package/schematics/migrations/alert-signals/schema.json +20 -0
  103. package/schematics/migrations/autocomplete-panel-width-auto/README.md +68 -0
  104. package/schematics/migrations/autocomplete-panel-width-auto/index.js +70 -0
  105. package/schematics/migrations/autocomplete-panel-width-auto/schema.json +15 -0
  106. package/schematics/migrations/filter-bar-signals/README.md +1 -1
  107. package/schematics/migrations.json +11 -1
  108. package/schematics/ng-add/index.js +1 -1
  109. package/schematics/utils/angular-parsing.js +11 -2
  110. package/search-expandable/index.d.ts +3 -0
  111. package/select/index.d.ts +51 -24
  112. package/tabs/_tabs-common.scss +60 -31
  113. package/tabs/_tabs-theme.scss +12 -0
  114. package/tabs/index.d.ts +2 -2
  115. package/tabs/tab-header.scss +14 -0
  116. package/tabs/tab-nav-bar.scss +4 -0
  117. package/tabs/tabs-tokens.scss +1 -0
  118. package/timezone/index.d.ts +1 -3
  119. package/tooltip/index.d.ts +73 -4
  120. package/tree-select/index.d.ts +46 -22
@@ -0,0 +1,64 @@
1
+ # alert-signals
2
+
3
+ Migration schematic invoked automatically by `ng update @koobiq/components`.
4
+ Migrates consumers of `KbqAlert` to its finished signal-based public API.
5
+
6
+ ## Why
7
+
8
+ `KbqAlert` finished its migration to signals and tightened its public surface. Some breaking changes can be
9
+ auto-fixed, others require manual attention. Template _bindings_ (`[compact]`, `[alertStyle]`, `[alertColor]`)
10
+ keep working — only programmatic reads/writes and template-reference reads break.
11
+
12
+ | Member | Before | After | Auto-fix |
13
+ | ---------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------- | -------------- |
14
+ | `compact` | `boolean` input | `InputSignalWithTransform` (`booleanAttribute`) | ✅ read → call |
15
+ | `alertStyle` | `KbqAlertStyles \| string` | `InputSignal<'default' \| 'colored'>` | ✅ read → call |
16
+ | `alertColor` | `get`/`set` accessor | read-only `InputSignalWithTransform`, **value changed** | ⚠️ warn |
17
+ | `icon` / `iconItem` / `button` / `title` / `control` / `closeButton` / `isColored` | `public` | `protected` | ⚠️ warn |
18
+
19
+ ## What it does (auto-fix)
20
+
21
+ The schematic walks every `.ts` and `.html` file in the project (skipping `node_modules` and `dist`) and, for
22
+ files that reference the alert:
23
+
24
+ - **TypeScript reads.** For a receiver whose static type is annotated `KbqAlert` (method/function params, class
25
+ fields — including `@ViewChild(KbqAlert) x: KbqAlert` and constructor parameter-properties — and typed locals),
26
+ a read of a value-safe signal member becomes a call:
27
+ - `alert.compact` → `alert.compact()` (incl. optional chain `alert?.compact` → `alert.compact()`)
28
+ - `alert.alertStyle` → `alert.alertStyle()`
29
+ - **Template reference reads.** For a `#ref` bound to `<kbq-alert>`, reads of `compact`/`alertStyle` through that
30
+ ref are rewritten in the same template (external `.html` and inline `template:` strings): `ref.compact` →
31
+ `ref.compact()`.
32
+
33
+ All rewrites are idempotent — running twice does not double the call. `compact`/`alertStyle` keep the same value,
34
+ so appending `()` is safe.
35
+
36
+ ## What it does _not_ do (warn-only)
37
+
38
+ These changes can't be rewritten safely and are surfaced as warnings (in both `fix` and dry-run mode):
39
+
40
+ | Change | Manual migration |
41
+ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `alert.alertColor` read | Now a read-only `InputSignal`: read as `alert.alertColor()`. **Value changed** — the getter used to return the CSS class `kbq-alert_<color>`, it now returns the raw color (`error`). Update any code that expected the class string. |
43
+ | `alert.alertColor = …` write | The input is read-only. Drive it with the `[alertColor]` binding (or a signal in your own component), not an assignment. |
44
+ | `alert.icon` / `iconItem` / `button` / `title` / `control` / `closeButton` / `isColored` | Now `protected` — inaccessible from outside. Refactor to avoid reading component internals. |
45
+ | `[alertColor]` / `[alertStyle]` with a custom string | The inputs are narrowed to the enum literals (`'error' \| 'warning' \| 'success' \| 'info'`, `'default' \| 'colored'`). Replace typos/custom strings with a valid value. |
46
+ | `<kbq-alert compact>` (bare attribute) | Now applies the `booleanAttribute` transform, so the bare attribute really enables compact (it used to be a silent no-op). Verify this is intended. |
47
+ | Projected content wrapped in an element | Content queries are now `descendants: false`. Project `kbq-alert-title` / `-control` / `-close-button` / the icon as **direct children** of `<kbq-alert>`, not inside a wrapper. |
48
+ | `viewChild(KbqAlert)` / `contentChild(KbqAlert)` | Returns the instance whose `compact`/`alertStyle` are signals — reads are a double call (`this.alert().compact()`). Verify manually. |
49
+ | `KbqAlertModule` | No longer re-exports `A11yModule` / `PlatformModule`. Import `@angular/cdk/a11y` / `@angular/cdk/platform` directly if you relied on them. |
50
+
51
+ ## Running it manually
52
+
53
+ ```
54
+ ng generate @koobiq/components:alert-signals --project my-app
55
+ ```
56
+
57
+ Pass `--fix=false` to see what would change without writing files.
58
+
59
+ ## Limitations
60
+
61
+ Receivers are matched by explicit `KbqAlert` type annotation only (no cross-package type inference), so
62
+ aliased/inferred receivers (`const a = this.alert; a.compact`) are left untouched. `alertColor` is intentionally
63
+ **not** auto-rewritten because its value semantics changed. After running, **always inspect the diff** and act on
64
+ the warnings before committing.
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Data for the `alert-signals` migration.
5
+ *
6
+ * `KbqAlert` finished its migration to a signal-based public API. Two members are safe, value-preserving
7
+ * property → signal reads (only the call syntax changed) and are auto-fixed. `alertColor` also changed its
8
+ * *value* semantics and became read-only, and the content-query members became `protected` — those cannot be
9
+ * rewritten safely and are surfaced as warnings.
10
+ *
11
+ * - `alert.compact` → `alert.compact()` (value unchanged — auto-fixed)
12
+ * - `alert.alertStyle` → `alert.alertStyle()` (value unchanged — auto-fixed)
13
+ * - `alert.alertColor` → read-only signal; getter now returns the raw color, not `kbq-alert_<color>` (warn)
14
+ * - `alert.icon` / `iconItem` / `button` / `title` / `control` / `closeButton` / `isColored` → `protected` (warn)
15
+ *
16
+ * Template *bindings* (`[compact]`, `[alertStyle]`, `[alertColor]`) keep working — only programmatic reads and
17
+ * template-reference reads break.
18
+ */
19
+ /** Members of `KbqAlert` whose value is unchanged; a read must become a call. Auto-fixed. */
20
+ const SIGNAL_MEMBERS = ['compact', 'alertStyle'];
21
+ /**
22
+ * Signal members that are writable via `.set(...)`. `KbqAlert.compact`/`alertStyle` are `input()` (read-only),
23
+ * so this is empty — a programmatic write is left untouched and covered by the warnings.
24
+ */
25
+ const WRITABLE_MEMBERS = new Set();
26
+ /** TypeScript type annotation that marks a receiver as an alert. */
27
+ const ALERT_TYPE = 'KbqAlert';
28
+ /** Element selector whose template reference variables (`#ref`) point at an alert. */
29
+ const ALERT_ELEMENT = 'kbq-alert';
30
+ /**
31
+ * `alertColor` became a read-only `InputSignal` AND changed its value: the getter used to return the CSS class
32
+ * `kbq-alert_<color>`, it now returns the raw color (e.g. `error`). Because a mechanical `()` append would
33
+ * silently change the value, this is warned on rather than auto-fixed.
34
+ */
35
+ const VALUE_CHANGED_MEMBERS = ['alertColor'];
36
+ /** Members that moved from `public` to `protected` and can no longer be read from outside the component. */
37
+ const PROTECTED_MEMBERS = [
38
+ 'icon',
39
+ 'iconItem',
40
+ 'button',
41
+ 'title',
42
+ 'control',
43
+ 'closeButton',
44
+ 'isColored'
45
+ ];
46
+ /**
47
+ * File-scoped patterns that can't be auto-fixed reliably — surfaced with file locations (in both `fix` and
48
+ * dry-run mode). Only evaluated for files that reference the alert, so the patterns stay scoped.
49
+ */
50
+ const warnPatterns = [
51
+ {
52
+ pattern: '(?:viewChild|ViewChild|contentChild|ContentChild)[^\\n;]*\\bKbqAlert\\b',
53
+ message: 'A KbqAlert view/content query returns the component instance, whose `compact`/`alertStyle` are now ' +
54
+ 'signals — reading them is a double call, e.g. `this.alert().compact()`. Verify query reads manually.'
55
+ },
56
+ {
57
+ pattern: '\\bKbqAlertModule\\b',
58
+ message: 'KbqAlertModule no longer re-exports A11yModule / PlatformModule. If you relied on them transitively ' +
59
+ '(e.g. cdkTrapFocus), import @angular/cdk/a11y or @angular/cdk/platform directly. Prefer importing the ' +
60
+ 'standalone KbqAlert and its directives over the NgModule.'
61
+ }
62
+ ];
63
+
64
+ exports.ALERT_ELEMENT = ALERT_ELEMENT;
65
+ exports.ALERT_TYPE = ALERT_TYPE;
66
+ exports.PROTECTED_MEMBERS = PROTECTED_MEMBERS;
67
+ exports.SIGNAL_MEMBERS = SIGNAL_MEMBERS;
68
+ exports.VALUE_CHANGED_MEMBERS = VALUE_CHANGED_MEMBERS;
69
+ exports.WRITABLE_MEMBERS = WRITABLE_MEMBERS;
70
+ exports.warnPatterns = warnPatterns;
@@ -0,0 +1,350 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var ts = require('typescript');
6
+ var utils_ast = require('../../utils/ast.js');
7
+ var utils_messages = require('../../utils/messages.js');
8
+ var utils_packageConfig = require('../../utils/package-config.js');
9
+ var utils_typescript = require('../../utils/typescript.js');
10
+ var migrations_alertSignals_data = require('./data.js');
11
+ require('@angular-devkit/schematics');
12
+ require('@schematics/angular/utility');
13
+
14
+ const TS_EXT = '.ts';
15
+ const HTML_EXT = '.html';
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
18
+ }
19
+ /** Applies text-span edits to `content`, right-to-left, so earlier edits don't shift later offsets. */
20
+ function applyEdits(content, edits) {
21
+ const sorted = [...edits].sort((a, b) => b.start - a.start || b.end - a.end);
22
+ let result = content;
23
+ for (const { start, end, text } of sorted) {
24
+ result = result.slice(0, start) + text + result.slice(end);
25
+ }
26
+ return result;
27
+ }
28
+ const isFunctionLike = (node) => ts.isFunctionDeclaration(node) ||
29
+ ts.isMethodDeclaration(node) ||
30
+ ts.isConstructorDeclaration(node) ||
31
+ ts.isArrowFunction(node) ||
32
+ ts.isFunctionExpression(node) ||
33
+ ts.isGetAccessorDeclaration(node) ||
34
+ ts.isSetAccessorDeclaration(node);
35
+ /** Walks up from `node` to the nearest ancestor matching `predicate`. */
36
+ function findAncestor(node, predicate) {
37
+ let current = node.parent;
38
+ while (current) {
39
+ if (predicate(current))
40
+ return current;
41
+ current = current.parent;
42
+ }
43
+ return undefined;
44
+ }
45
+ /** Whether a type annotation refers to the alert (`KbqAlert`). */
46
+ function isAlertType(type) {
47
+ return (!!type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === migrations_alertSignals_data.ALERT_TYPE);
48
+ }
49
+ const FIELD_MODIFIERS = new Set([
50
+ ts.SyntaxKind.PrivateKeyword,
51
+ ts.SyntaxKind.PublicKeyword,
52
+ ts.SyntaxKind.ProtectedKeyword,
53
+ ts.SyntaxKind.ReadonlyKeyword
54
+ ]);
55
+ /**
56
+ * Collects the receivers whose static type is an alert, by explicit annotation only (no cross-package type
57
+ * resolution): method/function params, class fields (incl. `@ViewChild(KbqAlert) x: KbqAlert` and constructor
58
+ * parameter-properties) and typed locals.
59
+ */
60
+ function collectReceivers(sourceFile) {
61
+ const receivers = [];
62
+ const add = (text, scope) => receivers.push({ text, start: scope.getStart(sourceFile), end: scope.getEnd() });
63
+ const visit = (node) => {
64
+ if (ts.isParameter(node) && ts.isIdentifier(node.name) && isAlertType(node.type)) {
65
+ add(node.name.text, findAncestor(node, isFunctionLike) ?? sourceFile);
66
+ // A constructor parameter-property is also a class field, reachable as `this.<name>`.
67
+ if (node.modifiers?.some((modifier) => FIELD_MODIFIERS.has(modifier.kind))) {
68
+ const owner = findAncestor(node, ts.isClassDeclaration);
69
+ if (owner)
70
+ add(`this.${node.name.text}`, owner);
71
+ }
72
+ }
73
+ else if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name) && isAlertType(node.type)) {
74
+ const owner = findAncestor(node, ts.isClassDeclaration);
75
+ if (owner)
76
+ add(`this.${node.name.text}`, owner);
77
+ }
78
+ else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && isAlertType(node.type)) {
79
+ add(node.name.text, findAncestor(node, isFunctionLike) ?? sourceFile);
80
+ }
81
+ node.forEachChild(visit);
82
+ };
83
+ visit(sourceFile);
84
+ return receivers;
85
+ }
86
+ /** Whether a property access on a receiver is within one of the receiver's scopes. */
87
+ function inReceiverScope(node, sourceFile, receivers) {
88
+ const receiverText = node.expression.getText(sourceFile);
89
+ const start = node.getStart(sourceFile);
90
+ const end = node.getEnd();
91
+ return receivers.some((r) => r.text === receiverText && start >= r.start && end <= r.end);
92
+ }
93
+ /** Classifies a matched property access and appends the resulting edit(s). */
94
+ function classifyAccess(node, sourceFile, edits) {
95
+ const parent = node.parent;
96
+ // Already migrated: `x.compact()` (call) or `x.compact.set(...)` — leave alone (idempotent).
97
+ if (ts.isCallExpression(parent) && parent.expression === node)
98
+ return;
99
+ if (ts.isPropertyAccessExpression(parent) && parent.expression === node && parent.name.text === 'set')
100
+ return;
101
+ // Write target: `x.compact = RHS`. `KbqAlert` signal members are `input()` (read-only), so there is no
102
+ // writable member — leave the write untouched (it becomes a compile error the consumer fixes by hand).
103
+ if (ts.isBinaryExpression(parent) &&
104
+ parent.left === node &&
105
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
106
+ if (migrations_alertSignals_data.WRITABLE_MEMBERS.has(node.name.text)) {
107
+ const rhs = parent.right;
108
+ edits.push({ start: node.getEnd(), end: rhs.getStart(sourceFile), text: '.set(' });
109
+ edits.push({ start: rhs.getEnd(), end: rhs.getEnd(), text: ')' });
110
+ }
111
+ return;
112
+ }
113
+ // Read (incl. optional chain `x?.compact`): append `()`.
114
+ edits.push({ start: node.getEnd(), end: node.getEnd(), text: '()' });
115
+ }
116
+ /** Collects edits for every read/write of a value-safe signal member on a known alert receiver. */
117
+ function collectAccessEdits(sourceFile, receivers) {
118
+ const edits = [];
119
+ const visit = (node) => {
120
+ if (ts.isPropertyAccessExpression(node) &&
121
+ ts.isIdentifier(node.name) &&
122
+ migrations_alertSignals_data.SIGNAL_MEMBERS.includes(node.name.text) &&
123
+ inReceiverScope(node, sourceFile, receivers)) {
124
+ classifyAccess(node, sourceFile, edits);
125
+ }
126
+ node.forEachChild(visit);
127
+ };
128
+ visit(sourceFile);
129
+ return edits;
130
+ }
131
+ /** Collects the manual-migration members (`alertColor`, now-`protected` queries) read on an alert receiver. */
132
+ function collectReceiverWarnings(sourceFile, receivers) {
133
+ const valueChanged = new Set();
134
+ const protectedAccess = new Set();
135
+ const visit = (node) => {
136
+ if (ts.isPropertyAccessExpression(node) &&
137
+ ts.isIdentifier(node.name) &&
138
+ inReceiverScope(node, sourceFile, receivers)) {
139
+ const name = node.name.text;
140
+ if (migrations_alertSignals_data.VALUE_CHANGED_MEMBERS.includes(name))
141
+ valueChanged.add(name);
142
+ else if (migrations_alertSignals_data.PROTECTED_MEMBERS.includes(name))
143
+ protectedAccess.add(name);
144
+ }
145
+ node.forEachChild(visit);
146
+ };
147
+ visit(sourceFile);
148
+ return { valueChanged, protectedAccess };
149
+ }
150
+ /** Pass A — rewrite value-safe programmatic reads of alert signal members in TypeScript code. */
151
+ function migrateTsExpressions(content, fileName) {
152
+ const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
153
+ const receivers = collectReceivers(sourceFile);
154
+ if (receivers.length === 0)
155
+ return content;
156
+ const edits = collectAccessEdits(sourceFile, receivers);
157
+ return edits.length > 0 ? applyEdits(content, edits) : content;
158
+ }
159
+ /** Emits precise, receiver-scoped warnings for the members that can't be auto-fixed. */
160
+ function warnReceiverMembers(context, filePath, content) {
161
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
162
+ const receivers = collectReceivers(sourceFile);
163
+ if (receivers.length === 0)
164
+ return;
165
+ const { valueChanged, protectedAccess } = collectReceiverWarnings(sourceFile, receivers);
166
+ if (valueChanged.size > 0) {
167
+ utils_messages.logMessage(context.logger, [
168
+ `[alert-signals] ${filePath}`,
169
+ ` \`alertColor\` is now a read-only InputSignal — read it as \`alert.alertColor()\`. Its value also`,
170
+ ` changed: the getter used to return the CSS class \`kbq-alert_<color>\`, it now returns the raw color`,
171
+ ` (e.g. \`error\`). Programmatic writes (\`alert.alertColor = …\`) are no longer possible — bind`,
172
+ ` \`[alertColor]\` in the template instead. Migrate this by hand.`
173
+ ]);
174
+ }
175
+ if (protectedAccess.size > 0) {
176
+ utils_messages.logMessage(context.logger, [
177
+ `[alert-signals] ${filePath}`,
178
+ ` These KbqAlert members are now \`protected\` and can't be read from outside the component: ` +
179
+ `${[...protectedAccess].join(', ')}. Refactor to avoid reading them.`
180
+ ]);
181
+ }
182
+ }
183
+ /** Collects template reference variable names bound to a `<kbq-alert>` element. */
184
+ class AlertRefCollector {
185
+ constructor() {
186
+ this.refs = new Set();
187
+ }
188
+ visitElement(element) {
189
+ if (element.name === migrations_alertSignals_data.ALERT_ELEMENT) {
190
+ for (const attr of element.attrs ?? []) {
191
+ if (typeof attr.name !== 'string')
192
+ continue;
193
+ if (attr.name.startsWith('#'))
194
+ this.refs.add(attr.name.slice(1));
195
+ else if (attr.name.startsWith('ref-'))
196
+ this.refs.add(attr.name.slice(4));
197
+ }
198
+ }
199
+ this.visitChildren(element);
200
+ }
201
+ visitBlock(block) {
202
+ this.visitChildren(block);
203
+ }
204
+ visitChildren(node) {
205
+ for (const child of node.children ?? []) {
206
+ child.visit(this);
207
+ }
208
+ }
209
+ visitAttribute() { }
210
+ visitText() { }
211
+ visitComment() { }
212
+ visitExpansion() { }
213
+ visitExpansionCase() { }
214
+ visitBlockParameter() { }
215
+ visitLetDeclaration() { }
216
+ }
217
+ /** Rewrites `ref.member` reads to `ref.member()` for the given refs, scoped to those exact identifiers. */
218
+ function rewriteRefReads(template, refs) {
219
+ const members = migrations_alertSignals_data.SIGNAL_MEMBERS.join('|');
220
+ let content = template;
221
+ let changed = false;
222
+ for (const ref of refs) {
223
+ // `\bref\.(member)\b(?!\s*\()` — skip anything already invoked, so the rewrite is idempotent.
224
+ const pattern = new RegExp(`\\b(${escapeRegExp(ref)})\\.(${members})\\b(?!\\s*\\()`, 'g');
225
+ const next = content.replace(pattern, '$1.$2()');
226
+ if (next !== content) {
227
+ content = next;
228
+ changed = true;
229
+ }
230
+ }
231
+ return { content, changed };
232
+ }
233
+ /** Pass B (core) — parse a template, discover alert refs, rewrite their value-safe signal reads. */
234
+ async function migrateTemplate(template) {
235
+ if (!template.includes(migrations_alertSignals_data.ALERT_ELEMENT))
236
+ return { content: template, changed: false };
237
+ const parsed = await utils_typescript.parseTemplate(template);
238
+ if (!parsed.tree)
239
+ return { content: template, changed: false };
240
+ const collector = new AlertRefCollector();
241
+ utils_ast.visitAll(collector, parsed.tree.rootNodes);
242
+ if (collector.refs.size === 0)
243
+ return { content: template, changed: false };
244
+ return rewriteRefReads(template, [...collector.refs]);
245
+ }
246
+ /** Interior `[start, end]` ranges of inline `@Component({ template: '…' })` string literals. */
247
+ function collectInlineTemplateRanges(sourceFile) {
248
+ const ranges = [];
249
+ utils_typescript.forEachClass(sourceFile, (node) => {
250
+ const decorator = ts
251
+ .getDecorators(node)
252
+ ?.find((dec) => ts.isCallExpression(dec.expression) &&
253
+ ts.isIdentifier(dec.expression.expression) &&
254
+ dec.expression.expression.text === 'Component');
255
+ if (!decorator || !ts.isCallExpression(decorator.expression))
256
+ return;
257
+ const [arg] = decorator.expression.arguments;
258
+ if (!arg || !ts.isObjectLiteralExpression(arg))
259
+ return;
260
+ for (const prop of arg.properties) {
261
+ if (ts.isPropertyAssignment(prop) &&
262
+ (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name)) &&
263
+ prop.name.text === 'template' &&
264
+ ts.isStringLiteralLike(prop.initializer) &&
265
+ prop.initializer.text) {
266
+ // +1 / -1 to exclude the opening/closing quote characters.
267
+ ranges.push({ start: prop.initializer.getStart(sourceFile) + 1, end: prop.initializer.getEnd() - 1 });
268
+ }
269
+ }
270
+ });
271
+ return ranges;
272
+ }
273
+ /** Pass B (inline) — rewrite alert ref reads inside inline component templates. */
274
+ async function migrateInlineTemplates(content, fileName) {
275
+ const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
276
+ const ranges = collectInlineTemplateRanges(sourceFile).sort((a, b) => b.start - a.start);
277
+ let result = content;
278
+ for (const { start, end } of ranges) {
279
+ const { content: rewritten, changed } = await migrateTemplate(result.slice(start, end));
280
+ if (changed) {
281
+ result = result.slice(0, start) + rewritten + result.slice(end);
282
+ }
283
+ }
284
+ return result;
285
+ }
286
+ function logWarnings(context, filePath, content) {
287
+ for (const { pattern, message } of migrations_alertSignals_data.warnPatterns) {
288
+ if (new RegExp(pattern).test(content)) {
289
+ utils_messages.logMessage(context.logger, [`[alert-signals] ${filePath}`, ` ${message}`]);
290
+ }
291
+ }
292
+ }
293
+ /** A `.ts` file is an alert consumer if it names the type/module or imports the package. */
294
+ function referencesAlert(content) {
295
+ return /\bKbqAlert\b/.test(content) || content.includes('@koobiq/components/alert');
296
+ }
297
+ function alertSignals(options) {
298
+ return async (tree, context) => {
299
+ const { project, fix } = options;
300
+ const projectDefinition = await utils_packageConfig.setupOptions(project, tree);
301
+ const root = projectDefinition?.root ?? '';
302
+ const rootDir = root ? tree.getDir(root) : tree.root;
303
+ const tsPaths = [];
304
+ const htmlPaths = [];
305
+ rootDir.visit((filePath) => {
306
+ if (filePath.includes('node_modules') || filePath.includes('/dist/'))
307
+ return;
308
+ if (filePath.endsWith(TS_EXT))
309
+ tsPaths.push(filePath);
310
+ else if (filePath.endsWith(HTML_EXT))
311
+ htmlPaths.push(filePath);
312
+ });
313
+ let touched = 0;
314
+ const commit = (filePath, original, updated) => {
315
+ if (updated === original)
316
+ return;
317
+ touched++;
318
+ if (fix) {
319
+ tree.overwrite(filePath, updated);
320
+ }
321
+ else {
322
+ utils_messages.logMessage(context.logger, [`[alert-signals] would update ${filePath} (run with --fix to apply)`]);
323
+ }
324
+ };
325
+ for (const filePath of tsPaths) {
326
+ const original = tree.read(filePath)?.toString();
327
+ if (!original || !referencesAlert(original))
328
+ continue;
329
+ logWarnings(context, filePath, original);
330
+ warnReceiverMembers(context, filePath, original);
331
+ let content = migrateTsExpressions(original, filePath);
332
+ content = await migrateInlineTemplates(content, filePath);
333
+ commit(filePath, original, content);
334
+ }
335
+ for (const filePath of htmlPaths) {
336
+ const original = tree.read(filePath)?.toString();
337
+ if (!original)
338
+ continue;
339
+ const { content, changed } = await migrateTemplate(original);
340
+ if (changed)
341
+ commit(filePath, original, content);
342
+ }
343
+ utils_messages.logMessage(context.logger, [
344
+ `[alert-signals] processed tree under "${root || '<workspace root>'}", ` +
345
+ `${fix ? 'updated' : 'would update'} ${touched} file(s).`
346
+ ]);
347
+ };
348
+ }
349
+
350
+ exports.default = alertSignals;
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "koobiq-components-alert-signals",
4
+ "title": "Koobiq components alert signals migration",
5
+ "type": "object",
6
+ "properties": {
7
+ "project": {
8
+ "type": "string",
9
+ "description": "Name of the project to migrate. If omitted, the migration runs over the whole tree.",
10
+ "$default": {
11
+ "$source": "projectName"
12
+ }
13
+ },
14
+ "fix": {
15
+ "type": "boolean",
16
+ "default": true,
17
+ "description": "When true, applies all auto-fix replacements. When false, prints what would change without writing."
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,68 @@
1
+ ### Autocomplete Panel Width Auto schematics
2
+
3
+ This schematic migrates `KbqAutocomplete` templates away from the old meaning of `panelWidth="auto"`. It includes:
4
+
5
+ - Replacement of `panelWidth="auto"` with `panelWidth="fit-content"`
6
+
7
+ `panelWidth="auto"` used to be passed through to CSS verbatim, so the panel shrank to fit its content. It now
8
+ means "match the width of the host", which is what it already means on `kbq-select` and `kbq-tree-select`.
9
+ `fit-content` preserves the old behaviour.
10
+
11
+ The change is silent — `auto` still type-checks and still renders — so templates that are not migrated will
12
+ lay out differently without any error.
13
+
14
+ [Params](schema.ts)
15
+
16
+ Usage for Angular Cli:
17
+
18
+ ```shell
19
+ ng g @koobiq/components:autocomplete-panel-width-auto --project <your project>
20
+ ```
21
+
22
+ Usage for Nx:
23
+
24
+ ```shell
25
+ nx g @koobiq/components:autocomplete-panel-width-auto --project <your project>
26
+ ```
27
+
28
+ ### Run locally
29
+
30
+ Build package
31
+
32
+ ```shell
33
+ yarn run build:schematics
34
+ ```
35
+
36
+ Run command (for example, for `koobiq-docs` project)
37
+
38
+ ```shell
39
+ ng g ./dist/components/schematics/collection.json:autocomplete-panel-width-auto --project koobiq-docs
40
+ ```
41
+
42
+ ### Result
43
+
44
+ #### Before
45
+
46
+ ```ts
47
+ import { Component } from '@angular/core';
48
+
49
+ @Component({
50
+ template: `
51
+ <kbq-autocomplete panelWidth="auto">...</kbq-autocomplete>
52
+ `
53
+ })
54
+ export class MyComp {}
55
+ ```
56
+
57
+ #### After
58
+
59
+ ```ts
60
+ import { Component } from '@angular/core';
61
+
62
+ @Component({
63
+ template: `
64
+ <kbq-autocomplete panelWidth="fit-content">...</kbq-autocomplete>
65
+ `
66
+ })
67
+ export class MyComp {}
68
+ ```
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var node_path = require('node:path');
6
+ var process = require('node:process');
7
+ var ts = require('typescript');
8
+ var utils_angularParsing = require('../../utils/angular-parsing.js');
9
+ var utils_packageConfig = require('../../utils/package-config.js');
10
+ var utils_typescript = require('../../utils/typescript.js');
11
+ require('path');
12
+ require('../../utils/ast.js');
13
+ require('@angular-devkit/schematics');
14
+ require('@schematics/angular/utility');
15
+ require('../../utils/messages.js');
16
+
17
+ function _interopNamespaceDefault(e) {
18
+ var n = Object.create(null);
19
+ if (e) {
20
+ Object.keys(e).forEach(function (k) {
21
+ if (k !== 'default') {
22
+ var d = Object.getOwnPropertyDescriptor(e, k);
23
+ Object.defineProperty(n, k, d.get ? d : {
24
+ enumerable: true,
25
+ get: function () { return e[k]; }
26
+ });
27
+ }
28
+ });
29
+ }
30
+ n.default = e;
31
+ return Object.freeze(n);
32
+ }
33
+
34
+ var process__namespace = /*#__PURE__*/_interopNamespaceDefault(process);
35
+
36
+ const migrationData = {
37
+ elementName: 'kbq-autocomplete',
38
+ attrs: {
39
+ key: {
40
+ from: 'panelWidth',
41
+ to: 'panelWidth'
42
+ },
43
+ value: {
44
+ replacements: [{ from: 'auto', to: 'fit-content' }]
45
+ }
46
+ }
47
+ };
48
+ function migrate(options) {
49
+ return async (tree, context) => {
50
+ const { project } = options;
51
+ const { tsPaths, templatePaths, projectDefinition } = await utils_packageConfig.getParsingInfo(project, tree);
52
+ const program = ts.createProgram(Array.from(tsPaths, (item) => node_path.resolve(projectDefinition.root, `.${item}`)), {
53
+ baseUrl: projectDefinition.root,
54
+ rootDir: projectDefinition.root,
55
+ _enableTemplateTypeChecker: true, // Required for the template type checker to work.
56
+ compileNonExportedClasses: true, // We want to migrate non-exported classes too.
57
+ // Avoid checking libraries to speed up the migration.
58
+ skipLibCheck: true,
59
+ skipDefaultLibCheck: true
60
+ });
61
+ // Update external html
62
+ await utils_angularParsing.migrateTemplate(tree, Array.from(templatePaths), context, migrationData);
63
+ // Update inline html
64
+ await utils_angularParsing.migrateTs(tree, program.getSourceFiles().filter((sourceFile) => utils_typescript.canMigrateFile(sourceFile, program)), process__namespace.cwd(), context, migrationData);
65
+ context.logger.warn('Warning! Run linter in updated files since line breaks or indents may be broken.');
66
+ };
67
+ }
68
+
69
+ exports.default = migrate;
70
+ exports.migrationData = migrationData;
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "koobiq-components-autocomplete-panel-width-auto",
4
+ "title": "koobiq-components autocomplete-panel-width-auto schematic",
5
+ "type": "object",
6
+ "properties": {
7
+ "project": {
8
+ "type": "string",
9
+ "description": "Name of the project",
10
+ "$default": {
11
+ "$source": "projectName"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -1,6 +1,6 @@
1
1
  # filter-bar-signals
2
2
 
3
- Migration schematic invoked automatically by `ng update @koobiq/components@21`.
3
+ Migration schematic invoked automatically by `ng update @koobiq/components@20`.
4
4
  Migrates consumers of `KbqFilterBar` to its signal-based public API.
5
5
 
6
6
  ## Why