tailjng 0.1.12 → 0.1.14
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/cli/component-manager.js +3 -1
- package/cli/file-operations.js +95 -4
- package/cli/settings/path-utils.js +35 -0
- package/package.json +1 -1
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +58 -62
- package/src/lib/components/filter/filter-complete/complete-filter.component.ts +23 -4
- package/src/lib/components/filter/filter-complete/complete-filter.types.ts +68 -1
- package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -16
- package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +1 -0
- package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +113 -14
- package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +66 -5
- package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +66 -5
package/cli/component-manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// component-manager.js
|
|
2
2
|
|
|
3
|
-
const { copyComponentFiles } = require("./file-operations");
|
|
3
|
+
const { copyComponentFiles, resetInstalledSharedFolders } = require("./file-operations");
|
|
4
4
|
const { installDependencies } = require("./dependency-manager");
|
|
5
5
|
const { COLORS } = require("./settings/colors");
|
|
6
6
|
const { collectDependencyTree } = require("./settings/lib-utils");
|
|
@@ -50,6 +50,7 @@ function logInstallPlan(componentName, componentList) {
|
|
|
50
50
|
|
|
51
51
|
async function addComponent(componentName, componentList) {
|
|
52
52
|
resetOverwritePolicy();
|
|
53
|
+
resetInstalledSharedFolders();
|
|
53
54
|
installedComponentsGlobal.clear();
|
|
54
55
|
|
|
55
56
|
const componentData = componentList[componentName];
|
|
@@ -96,6 +97,7 @@ async function addComponent(componentName, componentList) {
|
|
|
96
97
|
|
|
97
98
|
async function installAllComponents(componentList) {
|
|
98
99
|
resetOverwritePolicy();
|
|
100
|
+
resetInstalledSharedFolders();
|
|
99
101
|
installedComponentsGlobal.clear();
|
|
100
102
|
|
|
101
103
|
const allNames = Object.keys(componentList);
|
package/cli/file-operations.js
CHANGED
|
@@ -5,14 +5,29 @@ const path = require("path")
|
|
|
5
5
|
const { COLORS } = require("./settings/colors")
|
|
6
6
|
const { generateHeaderComment } = require("./settings/header-generator")
|
|
7
7
|
const { askOverwrite } = require("./settings/prompt-utils")
|
|
8
|
-
const {
|
|
8
|
+
const { getPackageRoot } = require("./settings/lib-utils")
|
|
9
|
+
const {
|
|
10
|
+
buildTargetPath,
|
|
11
|
+
buildSharedFolderTargetPath,
|
|
12
|
+
getSharedFolderInfo,
|
|
13
|
+
parseComponentPath,
|
|
14
|
+
} = require("./settings/path-utils")
|
|
15
|
+
|
|
16
|
+
/** Avoid copying the same group `shared/` folder twice per install session. */
|
|
17
|
+
const installedSharedFolders = new Set()
|
|
9
18
|
|
|
10
19
|
const TEXT_EXTENSIONS = new Set([".ts", ".js", ".html", ".css", ".scss", ".json", ".md"])
|
|
20
|
+
const LIB_PACKAGE_IMPORT_RE =
|
|
21
|
+
/from\s+['"](?:\.\.\/)+(?:interfaces|services|config)\/[^'"]+['"]/g
|
|
11
22
|
|
|
12
23
|
function shouldAddHeader(fileName) {
|
|
13
24
|
return TEXT_EXTENSIONS.has(path.extname(fileName).toLowerCase())
|
|
14
25
|
}
|
|
15
26
|
|
|
27
|
+
function patchConsumerImports(content) {
|
|
28
|
+
return content.replace(LIB_PACKAGE_IMPORT_RE, "from 'tailjng'")
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
function copyFileWithHeader(srcFile, destFile) {
|
|
17
32
|
fs.mkdirSync(path.dirname(destFile), { recursive: true })
|
|
18
33
|
|
|
@@ -22,6 +37,9 @@ function copyFileWithHeader(srcFile, destFile) {
|
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
let content = fs.readFileSync(srcFile, "utf8")
|
|
40
|
+
if (path.extname(srcFile).toLowerCase() === ".ts") {
|
|
41
|
+
content = patchConsumerImports(content)
|
|
42
|
+
}
|
|
25
43
|
const headerComment = generateHeaderComment(path.basename(srcFile))
|
|
26
44
|
content = headerComment + "\n\n" + content
|
|
27
45
|
fs.writeFileSync(destFile, content)
|
|
@@ -49,7 +67,11 @@ function copyDirectoryRecursive(srcDir, destDir, projectRoot) {
|
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
function resetInstalledSharedFolders() {
|
|
71
|
+
installedSharedFolders.clear()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findTailjngPackageRoot() {
|
|
53
75
|
let currentDir = process.cwd()
|
|
54
76
|
while (!fs.existsSync(path.join(currentDir, "node_modules"))) {
|
|
55
77
|
currentDir = path.dirname(currentDir)
|
|
@@ -59,7 +81,74 @@ async function copyComponentFiles(componentName, componentPath, isDependency = f
|
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
return getPackageRoot(path.join(currentDir, "node_modules", "tailjng", "cli"))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function copySharedFolderIfNeeded(componentPath, isDependency = false, options = {}) {
|
|
88
|
+
const { forceSync = false } = options
|
|
89
|
+
const sharedInfo = getSharedFolderInfo(componentPath)
|
|
90
|
+
if (!sharedInfo) {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (installedSharedFolders.has(sharedInfo.groupKey)) {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const packageRoot = findTailjngPackageRoot()
|
|
99
|
+
const sharedSrc = path.join(packageRoot, ...sharedInfo.sourceRelative.split("/"))
|
|
100
|
+
if (!fs.existsSync(sharedSrc)) {
|
|
101
|
+
console.error(
|
|
102
|
+
`${COLORS.red}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.red}ERROR: Shared utilities ${COLORS.bright}"${sharedInfo.groupKey}/shared"${COLORS.reset} ${COLORS.red}not found in package.${COLORS.reset}`,
|
|
103
|
+
)
|
|
104
|
+
console.error(`${COLORS.dim} Expected: ${sharedSrc}${COLORS.reset}`)
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const projectRoot = process.cwd()
|
|
109
|
+
const sharedDest = buildSharedFolderTargetPath(projectRoot, componentPath)
|
|
110
|
+
if (!sharedDest) {
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const sharedLabel = `${sharedInfo.groupKey}/shared`
|
|
115
|
+
const relativeTargetPath = path.relative(projectRoot, sharedDest)
|
|
116
|
+
const sharedMissing = !fs.existsSync(sharedDest)
|
|
117
|
+
|
|
118
|
+
if (!forceSync && !sharedMissing) {
|
|
119
|
+
const shouldOverwrite = await askOverwrite(sharedLabel, relativeTargetPath, isDependency)
|
|
120
|
+
if (!shouldOverwrite) {
|
|
121
|
+
console.log(
|
|
122
|
+
`${COLORS.dim}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.dim}Skipping ${COLORS.bright}"${sharedLabel}"${COLORS.reset} ${COLORS.dim}- keeping existing version.${COLORS.reset}`,
|
|
123
|
+
)
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(
|
|
128
|
+
`${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Removing existing ${COLORS.bright}"${sharedLabel}"${COLORS.reset} ${COLORS.yellow}to overwrite...${COLORS.reset}`,
|
|
129
|
+
)
|
|
130
|
+
fs.rmSync(sharedDest, { recursive: true, force: true })
|
|
131
|
+
} else if (!sharedMissing) {
|
|
132
|
+
console.log(
|
|
133
|
+
`${COLORS.yellow}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.yellow}Updating shared utilities ${COLORS.bright}"${sharedLabel}"${COLORS.reset} ${COLORS.yellow}with the installed component...${COLORS.reset}`,
|
|
134
|
+
)
|
|
135
|
+
fs.rmSync(sharedDest, { recursive: true, force: true })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(
|
|
139
|
+
`${COLORS.blue}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.blue}Copying shared utilities ${COLORS.bright}"${sharedLabel}"${COLORS.reset} → ${relativeTargetPath}${COLORS.reset}`,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
fs.mkdirSync(sharedDest, { recursive: true })
|
|
143
|
+
copyDirectoryRecursive(sharedSrc, sharedDest, projectRoot)
|
|
144
|
+
installedSharedFolders.add(sharedInfo.groupKey)
|
|
145
|
+
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function copyComponentFiles(componentName, componentPath, isDependency = false) {
|
|
150
|
+
const packageRoot = findTailjngPackageRoot()
|
|
151
|
+
const nodeModulesPath = path.join(packageRoot, ...componentPath.split("/"))
|
|
63
152
|
const projectRoot = process.cwd()
|
|
64
153
|
const targetPath = buildTargetPath(projectRoot, componentName, componentPath)
|
|
65
154
|
const pathInfo = parseComponentPath(componentPath)
|
|
@@ -77,6 +166,7 @@ async function copyComponentFiles(componentName, componentPath, isDependency = f
|
|
|
77
166
|
|
|
78
167
|
if (!shouldOverwrite) {
|
|
79
168
|
console.log(`${COLORS.dim}${COLORS.bright}[tailjng CLI]${COLORS.reset} ${COLORS.dim}Skipping ${COLORS.bright}"${componentName}"${COLORS.reset} ${COLORS.dim}- keeping existing version.${COLORS.reset}`)
|
|
169
|
+
await copySharedFolderIfNeeded(componentPath, isDependency, { forceSync: false })
|
|
80
170
|
return false
|
|
81
171
|
}
|
|
82
172
|
|
|
@@ -99,8 +189,9 @@ async function copyComponentFiles(componentName, componentPath, isDependency = f
|
|
|
99
189
|
|
|
100
190
|
fs.mkdirSync(targetPath, { recursive: true })
|
|
101
191
|
copyDirectoryRecursive(nodeModulesPath, targetPath, projectRoot)
|
|
192
|
+
await copySharedFolderIfNeeded(componentPath, isDependency, { forceSync: true })
|
|
102
193
|
|
|
103
194
|
return true
|
|
104
195
|
}
|
|
105
196
|
|
|
106
|
-
module.exports = { copyComponentFiles }
|
|
197
|
+
module.exports = { copyComponentFiles, resetInstalledSharedFolders }
|
|
@@ -58,9 +58,44 @@ function isComponentInstalled(projectRoot, componentName, componentPath) {
|
|
|
58
58
|
return fs.existsSync(buildTargetPath(projectRoot, componentName, componentPath))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* When a registry path is nested (e.g. toggle-radio/toggle-radio), returns sibling `shared/` info.
|
|
63
|
+
* Used by the CLI to copy group-level utilities alongside the component folder.
|
|
64
|
+
*/
|
|
65
|
+
function getSharedFolderInfo(componentPath) {
|
|
66
|
+
if (!componentPath.startsWith(COMPONENTS_SOURCE_PREFIX)) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const relativePath = componentPath.slice(COMPONENTS_SOURCE_PREFIX.length)
|
|
71
|
+
const pathParts = relativePath.split("/")
|
|
72
|
+
if (pathParts.length < 2) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const groupKey = pathParts.slice(0, -1).join("/")
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
groupKey,
|
|
80
|
+
sourceRelative: `${COMPONENTS_SOURCE_PREFIX}${groupKey}/shared`,
|
|
81
|
+
targetRelative: `${groupKey}/shared`,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildSharedFolderTargetPath(projectRoot, componentPath) {
|
|
86
|
+
const sharedInfo = getSharedFolderInfo(componentPath)
|
|
87
|
+
if (!sharedInfo) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return path.join(getComponentsBasePath(projectRoot), sharedInfo.targetRelative)
|
|
92
|
+
}
|
|
93
|
+
|
|
61
94
|
module.exports = {
|
|
62
95
|
parseComponentPath,
|
|
63
96
|
buildTargetPath,
|
|
97
|
+
buildSharedFolderTargetPath,
|
|
98
|
+
getSharedFolderInfo,
|
|
64
99
|
isComponentInstalled,
|
|
65
100
|
isConfigPath,
|
|
66
101
|
CONFIG_SOURCE_PREFIX,
|
package/package.json
CHANGED
|
@@ -139,68 +139,64 @@
|
|
|
139
139
|
<div class="flex flex-col gap-2">
|
|
140
140
|
@for (filter of filtersSelect; track $index) {
|
|
141
141
|
@if (filter.isVisible ?? true) {
|
|
142
|
-
@
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
[isFilterSelect]="true"
|
|
201
|
-
/>
|
|
202
|
-
</div>
|
|
203
|
-
}
|
|
142
|
+
@if (isDropdownFilter(filter)) {
|
|
143
|
+
<div class="relative">
|
|
144
|
+
<JDropdownSelect
|
|
145
|
+
[type]="'dropdown'"
|
|
146
|
+
[(ngModel)]="filter.selected"
|
|
147
|
+
(selectionChange)="
|
|
148
|
+
filter.onSelected ? filter.onSelected($event) : null
|
|
149
|
+
"
|
|
150
|
+
[optionLabel]="filter.optionLabel ?? ''"
|
|
151
|
+
[optionValue]="filter.optionValue ?? ''"
|
|
152
|
+
[labelSeparator]="filter.labelSeparator ?? ''"
|
|
153
|
+
[optionLabelTemplate]="filter.optionLabelTemplate ?? ''"
|
|
154
|
+
[optionLabelFn]="filter.optionLabelFn"
|
|
155
|
+
[placeholder]="filter.placeholder ?? ''"
|
|
156
|
+
[showClear]="filter.showClear ?? false"
|
|
157
|
+
[options]="filter.options"
|
|
158
|
+
[sort]="filter.sort ?? 'ASC'"
|
|
159
|
+
[showAllOption]="filter.showAllOption ?? false"
|
|
160
|
+
[isFilterSelect]="true"
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
} @else if (isSearchableFilter(filter)) {
|
|
164
|
+
<div class="relative">
|
|
165
|
+
<JDropdownSelect
|
|
166
|
+
[type]="'searchable'"
|
|
167
|
+
[(ngModel)]="filter.selected"
|
|
168
|
+
(selectionChange)="filter.onSelected?.($event)"
|
|
169
|
+
[endpoint]="filter.endpoint"
|
|
170
|
+
[optionLabel]="filter.optionLabel"
|
|
171
|
+
[optionValue]="filter.optionValue"
|
|
172
|
+
[labelSeparator]="filter.labelSeparator ?? ''"
|
|
173
|
+
[optionLabelTemplate]="filter.optionLabelTemplate ?? ''"
|
|
174
|
+
[optionLabelFn]="filter.optionLabelFn"
|
|
175
|
+
[responseKey]="filter.responseKey ?? ''"
|
|
176
|
+
[dataPath]="filter.dataPath ?? ''"
|
|
177
|
+
[placeholder]="filter.placeholder ?? ''"
|
|
178
|
+
[showClear]="filter.showClear ?? false"
|
|
179
|
+
[loadOnInit]="filter.loadOnInit ?? false"
|
|
180
|
+
[isSearch]="filter.isSearch ?? true"
|
|
181
|
+
[searchFields]="filter.searchFields || []"
|
|
182
|
+
[defaultFilters]="filter.defaultFilters || {}"
|
|
183
|
+
[sort]="filter.sort ?? 'ASC'"
|
|
184
|
+
[showAllOption]="filter.showAllOption ?? false"
|
|
185
|
+
[isFilterSelect]="true"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
} @else if (isMultiTableFilter(filter)) {
|
|
189
|
+
<div class="relative">
|
|
190
|
+
<JMultiTableSelect
|
|
191
|
+
[(ngModel)]="filter.selected"
|
|
192
|
+
(selectionChange)="
|
|
193
|
+
filter.onSelected ? filter.onSelected($event) : null
|
|
194
|
+
"
|
|
195
|
+
[columns]="filter.columns"
|
|
196
|
+
[btnText]="filter.btnText ?? ''"
|
|
197
|
+
[isFilterSelect]="true"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
204
200
|
}
|
|
205
201
|
}
|
|
206
202
|
}
|
|
@@ -3,20 +3,27 @@ import { NgClass } from '@angular/common';
|
|
|
3
3
|
import { FormsModule } from '@angular/forms';
|
|
4
4
|
import { Subject, Subscription, debounceTime, filter, forkJoin } from 'rxjs';
|
|
5
5
|
import { TableColumn, FilterButton, JUploadFilterService, JAlertDialogService, JAlertToastService, JGenericCrudService, LoadingState, JExcelService, JExcelFilterService } from 'tailjng';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
CompleteFilterAdditionalButtonLoading,
|
|
8
|
+
CompleteFilterDropdownSelect,
|
|
9
|
+
CompleteFilterMultiTableSelect,
|
|
10
|
+
CompleteFilterSearchableSelect,
|
|
11
|
+
CompleteFilterSelect,
|
|
12
|
+
} from './complete-filter.types';
|
|
7
13
|
import { JDropdownSelectComponent } from '../../select/select-dropdown/dropdown-select.component';
|
|
8
14
|
import { JMultiTableSelectComponent } from '../../select/select-multi-table/multi-table-select.component';
|
|
9
15
|
import { JSwitchCheckboxComponent } from '../../checkbox/checkbox-switch/switch-checkbox.component';
|
|
10
16
|
import { JDialogComponent } from '../../dialog/dialog.component';
|
|
11
17
|
import { JButtonComponent } from '../../button/button.component';
|
|
12
|
-
import type { CompleteFilterAdditionalButtonLoading } from './complete-filter.types';
|
|
13
18
|
import { hasActiveFilters } from './complete-filter.util';
|
|
14
19
|
import { Icons } from '../../.config/icons/icons.lucide';
|
|
15
20
|
import { JIconComponent } from '../../icon/icon.component';
|
|
16
21
|
|
|
17
22
|
export type {
|
|
18
23
|
CompleteFilterAdditionalButtonLoading,
|
|
19
|
-
CompleteFilterClearReason
|
|
24
|
+
CompleteFilterClearReason,
|
|
25
|
+
CompleteFilterSelect,
|
|
26
|
+
} from './complete-filter.types';
|
|
20
27
|
export { hasActiveFilters } from './complete-filter.util';
|
|
21
28
|
|
|
22
29
|
/**
|
|
@@ -111,7 +118,19 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
|
|
|
111
118
|
@Input() filtersButton: FilterButton[] = [];
|
|
112
119
|
|
|
113
120
|
// Filtros de tabla
|
|
114
|
-
@Input() filtersSelect:
|
|
121
|
+
@Input() filtersSelect: CompleteFilterSelect[] = [];
|
|
122
|
+
|
|
123
|
+
isDropdownFilter(filter: CompleteFilterSelect): filter is CompleteFilterDropdownSelect {
|
|
124
|
+
return filter.type === 'dropdown';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
isSearchableFilter(filter: CompleteFilterSelect): filter is CompleteFilterSearchableSelect {
|
|
128
|
+
return filter.type === 'searchable';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
isMultiTableFilter(filter: CompleteFilterSelect): filter is CompleteFilterMultiTableSelect {
|
|
132
|
+
return filter.type === 'multi-table';
|
|
133
|
+
}
|
|
115
134
|
|
|
116
135
|
get visibleColumns(): TableColumn<any>[] {
|
|
117
136
|
return this.columns.filter(col => !col.hidden);
|
|
@@ -1,4 +1,71 @@
|
|
|
1
|
-
import type { LoadingState } from 'tailjng';
|
|
1
|
+
import type { LoadingState, TableColumn } from 'tailjng';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filter-select shapes for JFilter templates.
|
|
5
|
+
* Prefixed with `CompleteFilter*` to avoid clashing with `tailjng` exports during strict template narrowing.
|
|
6
|
+
*/
|
|
7
|
+
export interface CompleteFilterDropdownSelect {
|
|
8
|
+
type: 'dropdown';
|
|
9
|
+
selected: unknown;
|
|
10
|
+
initSelected?: unknown;
|
|
11
|
+
onSelected?: (value: unknown) => void;
|
|
12
|
+
optionLabel?: string | string[];
|
|
13
|
+
optionValue?: string;
|
|
14
|
+
labelSeparator?: string;
|
|
15
|
+
optionLabelTemplate?: string;
|
|
16
|
+
optionLabelFn?: (option: Record<string, unknown>) => string;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
showClear?: boolean;
|
|
19
|
+
options: unknown[];
|
|
20
|
+
deep?: string;
|
|
21
|
+
sort?: 'ASC' | 'DESC';
|
|
22
|
+
isVisible?: ((data?: unknown) => boolean) | boolean;
|
|
23
|
+
showAllOption?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CompleteFilterSearchableSelect {
|
|
27
|
+
type: 'searchable';
|
|
28
|
+
selected: unknown;
|
|
29
|
+
initSelected?: unknown;
|
|
30
|
+
onSelected?: (value: unknown) => void;
|
|
31
|
+
endpoint: string;
|
|
32
|
+
isSearch?: boolean;
|
|
33
|
+
optionLabel: string | string[];
|
|
34
|
+
optionValue: string;
|
|
35
|
+
labelSeparator?: string;
|
|
36
|
+
optionLabelTemplate?: string;
|
|
37
|
+
optionLabelFn?: (option: Record<string, unknown>) => string;
|
|
38
|
+
responseKey?: string;
|
|
39
|
+
dataPath?: string;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
showClear?: boolean;
|
|
42
|
+
loadOnInit?: boolean;
|
|
43
|
+
searchFields?: string[];
|
|
44
|
+
defaultFilters?: Record<string, unknown>;
|
|
45
|
+
deep?: string;
|
|
46
|
+
sort?: 'ASC' | 'DESC';
|
|
47
|
+
isVisible?: ((data?: unknown) => boolean) | boolean;
|
|
48
|
+
showAllOption?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CompleteFilterMultiTableSelect {
|
|
52
|
+
type: 'multi-table';
|
|
53
|
+
selected: unknown;
|
|
54
|
+
initSelected?: unknown;
|
|
55
|
+
onSelected?: (value: unknown) => void;
|
|
56
|
+
columns: TableColumn<unknown>[];
|
|
57
|
+
btnText?: string;
|
|
58
|
+
placeholder?: string;
|
|
59
|
+
deep?: string;
|
|
60
|
+
sort?: 'ASC' | 'DESC';
|
|
61
|
+
isVisible?: ((data?: unknown) => boolean) | boolean;
|
|
62
|
+
showAllOption?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type CompleteFilterSelect =
|
|
66
|
+
| CompleteFilterDropdownSelect
|
|
67
|
+
| CompleteFilterSearchableSelect
|
|
68
|
+
| CompleteFilterMultiTableSelect;
|
|
2
69
|
|
|
3
70
|
/** Event payload from `(clearFilters)` — typically `'clear'`. */
|
|
4
71
|
export type CompleteFilterClearReason = string;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
/** Whether search text or any filter select differs from its initial value. */
|
|
4
|
-
export function hasActiveFilters(
|
|
5
|
-
searchQuery: string,
|
|
6
|
-
filtersSelect:
|
|
7
|
-
): boolean {
|
|
8
|
-
const hasActiveSearch = searchQuery?.trim().length > 0;
|
|
9
|
-
const hasSelectedFilters = filtersSelect.some((filter) => {
|
|
10
|
-
const actual = filter.selected;
|
|
11
|
-
const initial = filter.initSelected ?? null;
|
|
12
|
-
return JSON.stringify(actual) !== JSON.stringify(initial);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
return hasActiveSearch || hasSelectedFilters;
|
|
16
|
-
}
|
|
1
|
+
import type { CompleteFilterSelect } from './complete-filter.types';
|
|
2
|
+
|
|
3
|
+
/** Whether search text or any filter select differs from its initial value. */
|
|
4
|
+
export function hasActiveFilters(
|
|
5
|
+
searchQuery: string,
|
|
6
|
+
filtersSelect: CompleteFilterSelect[],
|
|
7
|
+
): boolean {
|
|
8
|
+
const hasActiveSearch = searchQuery?.trim().length > 0;
|
|
9
|
+
const hasSelectedFilters = filtersSelect.some((filter) => {
|
|
10
|
+
const actual = filter.selected;
|
|
11
|
+
const initial = filter.initSelected ?? null;
|
|
12
|
+
return JSON.stringify(actual) !== JSON.stringify(initial);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return hasActiveSearch || hasSelectedFilters;
|
|
16
|
+
}
|
|
@@ -18,22 +18,105 @@ export function buildToggleLoadParams(
|
|
|
18
18
|
return params;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Reads a nested property using dot notation (`a.b.c`). */
|
|
22
|
+
export function getNestedToggleRawValue(obj: unknown, path: string): unknown {
|
|
23
|
+
if (!path?.trim()) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let value: unknown = obj;
|
|
28
|
+
|
|
29
|
+
for (const part of path.split('.').filter(Boolean)) {
|
|
30
|
+
if (value == null || typeof value !== 'object') {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
value = (value as Record<string, unknown>)[part];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value ?? '';
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
/**
|
|
22
|
-
* Reads a nested property using dot notation.
|
|
41
|
+
* Reads a nested property using dot notation and returns a display string.
|
|
23
42
|
*/
|
|
24
43
|
export function getNestedToggleValue(obj: unknown, path: string): string {
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
const value = getNestedToggleRawValue(obj, path);
|
|
45
|
+
return value == null || value === '' ? '' : String(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the options array from `response.data`.
|
|
50
|
+
*
|
|
51
|
+
* Use `dataPath` with dot notation for nested shapes, e.g.:
|
|
52
|
+
* - `users` → `data.users`
|
|
53
|
+
* - `typeAccess.options` → `data.typeAccess.options`
|
|
54
|
+
*
|
|
55
|
+
* When `dataPath` / `responseKey` are omitted, falls back to legacy behavior:
|
|
56
|
+
* `data[firstEndpointSegment]` if it is an array (e.g. endpoint `role/list` → `data.role`).
|
|
57
|
+
*/
|
|
58
|
+
export function extractToggleOptionsFromResponse(
|
|
59
|
+
responseData: Record<string, unknown> | undefined | null,
|
|
60
|
+
endpoint = '',
|
|
61
|
+
responseKey?: string,
|
|
62
|
+
dataPath?: string,
|
|
63
|
+
): unknown[] {
|
|
64
|
+
if (!responseData || typeof responseData !== 'object') {
|
|
65
|
+
return [];
|
|
27
66
|
}
|
|
28
67
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
const explicitPath = dataPath?.trim() || responseKey?.trim() || '';
|
|
69
|
+
|
|
70
|
+
if (explicitPath) {
|
|
71
|
+
const node = getNestedToggleRawValue(responseData, explicitPath);
|
|
72
|
+
return Array.isArray(node) ? node : [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const legacyKey = endpoint.split('/').filter(Boolean)[0];
|
|
76
|
+
if (legacyKey) {
|
|
77
|
+
const legacyNode = responseData[legacyKey];
|
|
78
|
+
if (Array.isArray(legacyNode)) {
|
|
79
|
+
return legacyNode;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Array.isArray(responseData) ? (responseData as unknown[]) : [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Builds the display label from a template like `Acceso {label} (id {value})`. */
|
|
87
|
+
export function resolveToggleOptionLabelFromTemplate(
|
|
88
|
+
option: Record<string, unknown>,
|
|
89
|
+
template: string,
|
|
90
|
+
): string {
|
|
91
|
+
return template.replace(/\{([^}]+)\}/g, (_, path: string) => {
|
|
92
|
+
const value = getNestedToggleRawValue(option, path.trim());
|
|
93
|
+
if (value == null || value === '') {
|
|
94
|
+
return '';
|
|
32
95
|
}
|
|
33
|
-
return undefined;
|
|
34
|
-
}, obj);
|
|
35
96
|
|
|
36
|
-
|
|
97
|
+
return String(value);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Builds the display label for one option object. */
|
|
102
|
+
export function resolveToggleOptionLabel(
|
|
103
|
+
option: Record<string, unknown>,
|
|
104
|
+
optionLabel: string | string[],
|
|
105
|
+
labelSeparator: string,
|
|
106
|
+
optionLabelTemplate?: string,
|
|
107
|
+
): string {
|
|
108
|
+
if (optionLabelTemplate?.trim()) {
|
|
109
|
+
return resolveToggleOptionLabelFromTemplate(option, optionLabelTemplate);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(optionLabel)) {
|
|
113
|
+
return optionLabel
|
|
114
|
+
.map((key) => getNestedToggleValue(option, key))
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join(labelSeparator);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return getNestedToggleValue(option, optionLabel);
|
|
37
120
|
}
|
|
38
121
|
|
|
39
122
|
/**
|
|
@@ -41,18 +124,34 @@ export function getNestedToggleValue(obj: unknown, path: string): string {
|
|
|
41
124
|
*/
|
|
42
125
|
export function normalizeToggleOptions(
|
|
43
126
|
options: unknown[],
|
|
44
|
-
optionLabel: string,
|
|
127
|
+
optionLabel: string | string[],
|
|
45
128
|
optionValue: string,
|
|
129
|
+
labelSeparator = ' ',
|
|
130
|
+
optionLabelTemplate?: string,
|
|
131
|
+
optionLabelFn?: (option: Record<string, unknown>) => string,
|
|
46
132
|
): ToggleOption[] {
|
|
47
133
|
if (!options.length) {
|
|
48
134
|
return [];
|
|
49
135
|
}
|
|
50
136
|
|
|
51
137
|
if (typeof options[0] === 'object' && options[0] !== null) {
|
|
52
|
-
return options.map((opt) =>
|
|
53
|
-
|
|
54
|
-
label
|
|
55
|
-
|
|
138
|
+
return options.map((opt) => {
|
|
139
|
+
const record = opt as Record<string, unknown>;
|
|
140
|
+
const label = optionLabelFn
|
|
141
|
+
? optionLabelFn(record)
|
|
142
|
+
: resolveToggleOptionLabel(
|
|
143
|
+
record,
|
|
144
|
+
optionLabel,
|
|
145
|
+
labelSeparator,
|
|
146
|
+
optionLabelTemplate,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
value: getNestedToggleRawValue(record, optionValue),
|
|
151
|
+
label,
|
|
152
|
+
original: record,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
56
155
|
}
|
|
57
156
|
|
|
58
157
|
return options.map((opt) => ({
|
|
@@ -14,7 +14,11 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
|
14
14
|
import { JGenericCrudService } from 'tailjng';
|
|
15
15
|
import { Icons } from '../../.config/icons/icons.lucide';
|
|
16
16
|
import { JIconComponent } from '../../icon/icon.component';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildToggleLoadParams,
|
|
19
|
+
extractToggleOptionsFromResponse,
|
|
20
|
+
normalizeToggleOptions,
|
|
21
|
+
} from '../shared/toggle-options.util';
|
|
18
22
|
import { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from '../shared/toggle-options.types';
|
|
19
23
|
|
|
20
24
|
export type { ToggleOption, ToggleRadioLayout, ToggleSortOrder } from './toggle-radio.types';
|
|
@@ -25,6 +29,18 @@ let radioGroupCounter = 0;
|
|
|
25
29
|
* Classic radio group with circular indicators and labels.
|
|
26
30
|
*
|
|
27
31
|
* Install: `npx tailjng add toggle-radio`
|
|
32
|
+
*
|
|
33
|
+
* ```html
|
|
34
|
+
* <JToggleRadio
|
|
35
|
+
* endpoint="enum/typeAccess"
|
|
36
|
+
* dataPath="typeAccess.options"
|
|
37
|
+
* optionLabel="label"
|
|
38
|
+
* optionValue="value"
|
|
39
|
+
* optionLabelTemplate="Acceso {label} (id {value})"
|
|
40
|
+
* [loadOnInit]="true"
|
|
41
|
+
* [(ngModel)]="selected"
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
28
44
|
*/
|
|
29
45
|
@Component({
|
|
30
46
|
selector: 'JToggleRadio',
|
|
@@ -45,8 +61,32 @@ export class JToggleRadioComponent implements OnInit, OnChanges, ControlValueAcc
|
|
|
45
61
|
@Input() name = `j-toggle-radio-${++radioGroupCounter}`;
|
|
46
62
|
@Input() endpoint = '';
|
|
47
63
|
@Input() options: unknown[] = [];
|
|
48
|
-
@Input() optionLabel = 'label';
|
|
64
|
+
@Input() optionLabel: string | string[] = 'label';
|
|
49
65
|
@Input() optionValue = 'value';
|
|
66
|
+
|
|
67
|
+
/** Separator when `optionLabel` is an array of keys. */
|
|
68
|
+
@Input() labelSeparator = ' ';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Custom label template per option. Use `{field}` placeholders with dot paths.
|
|
72
|
+
* Example: `Acceso {label} (id {value})`.
|
|
73
|
+
*/
|
|
74
|
+
@Input() optionLabelTemplate = '';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Optional formatter for full control over the displayed label.
|
|
78
|
+
* Takes precedence over `optionLabelTemplate` when provided.
|
|
79
|
+
*/
|
|
80
|
+
@Input() optionLabelFn?: (option: Record<string, unknown>) => string;
|
|
81
|
+
|
|
82
|
+
/** @deprecated Alias of `dataPath` for a single top-level key in `response.data`. */
|
|
83
|
+
@Input() responseKey = '';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Dot path from `response.data` to the options array.
|
|
87
|
+
* Examples: `users`, `typeAccess.options`, `catalog.items`.
|
|
88
|
+
*/
|
|
89
|
+
@Input() dataPath = '';
|
|
50
90
|
@Input() sort: ToggleSortOrder = 'ASC';
|
|
51
91
|
@Input() defaultFilters: Record<string, unknown> = {};
|
|
52
92
|
@Input() loadOnInit = false;
|
|
@@ -99,7 +139,16 @@ export class JToggleRadioComponent implements OnInit, OnChanges, ControlValueAcc
|
|
|
99
139
|
}
|
|
100
140
|
|
|
101
141
|
ngOnChanges(changes: SimpleChanges): void {
|
|
102
|
-
|
|
142
|
+
const optionInputs = [
|
|
143
|
+
'options',
|
|
144
|
+
'optionLabel',
|
|
145
|
+
'optionValue',
|
|
146
|
+
'labelSeparator',
|
|
147
|
+
'optionLabelTemplate',
|
|
148
|
+
'optionLabelFn',
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
if (optionInputs.some((key) => changes[key]) && !this.endpoint) {
|
|
103
152
|
this.processOptions();
|
|
104
153
|
}
|
|
105
154
|
}
|
|
@@ -119,7 +168,12 @@ export class JToggleRadioComponent implements OnInit, OnChanges, ControlValueAcc
|
|
|
119
168
|
|
|
120
169
|
this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
|
|
121
170
|
next: (res) => {
|
|
122
|
-
const data = (
|
|
171
|
+
const data = extractToggleOptionsFromResponse(
|
|
172
|
+
res.data as Record<string, unknown>,
|
|
173
|
+
this.endpoint,
|
|
174
|
+
this.responseKey || undefined,
|
|
175
|
+
this.dataPath || undefined,
|
|
176
|
+
);
|
|
123
177
|
this.options = data;
|
|
124
178
|
this.processOptions();
|
|
125
179
|
this.isLoading = false;
|
|
@@ -135,7 +189,14 @@ export class JToggleRadioComponent implements OnInit, OnChanges, ControlValueAcc
|
|
|
135
189
|
}
|
|
136
190
|
|
|
137
191
|
processOptions(): void {
|
|
138
|
-
this.internalOptions = normalizeToggleOptions(
|
|
192
|
+
this.internalOptions = normalizeToggleOptions(
|
|
193
|
+
this.options,
|
|
194
|
+
this.optionLabel,
|
|
195
|
+
this.optionValue,
|
|
196
|
+
this.labelSeparator,
|
|
197
|
+
this.optionLabelFn ? undefined : this.optionLabelTemplate || undefined,
|
|
198
|
+
this.optionLabelFn,
|
|
199
|
+
);
|
|
139
200
|
}
|
|
140
201
|
|
|
141
202
|
select(value: unknown): void {
|
|
@@ -14,7 +14,11 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
|
14
14
|
import { JGenericCrudService } from 'tailjng';
|
|
15
15
|
import { Icons } from '../../.config/icons/icons.lucide';
|
|
16
16
|
import { JIconComponent } from '../../icon/icon.component';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildToggleLoadParams,
|
|
19
|
+
extractToggleOptionsFromResponse,
|
|
20
|
+
normalizeToggleOptions,
|
|
21
|
+
} from '../shared/toggle-options.util';
|
|
18
22
|
import { ToggleOption, ToggleSortOrder } from '../shared/toggle-options.types';
|
|
19
23
|
|
|
20
24
|
export type { ToggleOption, ToggleSortOrder } from './segment-toggle.types';
|
|
@@ -23,6 +27,18 @@ export type { ToggleOption, ToggleSortOrder } from './segment-toggle.types';
|
|
|
23
27
|
* Segmented single-select control (pill/tab style).
|
|
24
28
|
*
|
|
25
29
|
* Install: `npx tailjng add toggle-segment`
|
|
30
|
+
*
|
|
31
|
+
* ```html
|
|
32
|
+
* <JToggleSegment
|
|
33
|
+
* endpoint="enum/typeAccess"
|
|
34
|
+
* dataPath="typeAccess.options"
|
|
35
|
+
* optionLabel="label"
|
|
36
|
+
* optionValue="value"
|
|
37
|
+
* optionLabelTemplate="Acceso {label} (id {value})"
|
|
38
|
+
* [loadOnInit]="true"
|
|
39
|
+
* [(ngModel)]="selected"
|
|
40
|
+
* />
|
|
41
|
+
* ```
|
|
26
42
|
*/
|
|
27
43
|
@Component({
|
|
28
44
|
selector: 'JToggleSegment',
|
|
@@ -42,8 +58,32 @@ export class JToggleSegmentComponent implements OnInit, OnChanges, ControlValueA
|
|
|
42
58
|
|
|
43
59
|
@Input() endpoint = '';
|
|
44
60
|
@Input() options: unknown[] = [];
|
|
45
|
-
@Input() optionLabel = 'label';
|
|
61
|
+
@Input() optionLabel: string | string[] = 'label';
|
|
46
62
|
@Input() optionValue = 'value';
|
|
63
|
+
|
|
64
|
+
/** Separator when `optionLabel` is an array of keys. */
|
|
65
|
+
@Input() labelSeparator = ' ';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Custom label template per option. Use `{field}` placeholders with dot paths.
|
|
69
|
+
* Example: `Acceso {label} (id {value})`.
|
|
70
|
+
*/
|
|
71
|
+
@Input() optionLabelTemplate = '';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Optional formatter for full control over the displayed label.
|
|
75
|
+
* Takes precedence over `optionLabelTemplate` when provided.
|
|
76
|
+
*/
|
|
77
|
+
@Input() optionLabelFn?: (option: Record<string, unknown>) => string;
|
|
78
|
+
|
|
79
|
+
/** @deprecated Alias of `dataPath` for a single top-level key in `response.data`. */
|
|
80
|
+
@Input() responseKey = '';
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Dot path from `response.data` to the options array.
|
|
84
|
+
* Examples: `users`, `typeAccess.options`, `catalog.items`.
|
|
85
|
+
*/
|
|
86
|
+
@Input() dataPath = '';
|
|
47
87
|
@Input() sort: ToggleSortOrder = 'ASC';
|
|
48
88
|
@Input() defaultFilters: Record<string, unknown> = {};
|
|
49
89
|
@Input() loadOnInit = false;
|
|
@@ -95,7 +135,16 @@ export class JToggleSegmentComponent implements OnInit, OnChanges, ControlValueA
|
|
|
95
135
|
}
|
|
96
136
|
|
|
97
137
|
ngOnChanges(changes: SimpleChanges): void {
|
|
98
|
-
|
|
138
|
+
const optionInputs = [
|
|
139
|
+
'options',
|
|
140
|
+
'optionLabel',
|
|
141
|
+
'optionValue',
|
|
142
|
+
'labelSeparator',
|
|
143
|
+
'optionLabelTemplate',
|
|
144
|
+
'optionLabelFn',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
if (optionInputs.some((key) => changes[key]) && !this.endpoint) {
|
|
99
148
|
this.processOptions();
|
|
100
149
|
}
|
|
101
150
|
}
|
|
@@ -115,7 +164,12 @@ export class JToggleSegmentComponent implements OnInit, OnChanges, ControlValueA
|
|
|
115
164
|
|
|
116
165
|
this.genericService.findAll<unknown>({ endpoint: this.endpoint, params }).subscribe({
|
|
117
166
|
next: (res) => {
|
|
118
|
-
const data = (
|
|
167
|
+
const data = extractToggleOptionsFromResponse(
|
|
168
|
+
res.data as Record<string, unknown>,
|
|
169
|
+
this.endpoint,
|
|
170
|
+
this.responseKey || undefined,
|
|
171
|
+
this.dataPath || undefined,
|
|
172
|
+
);
|
|
119
173
|
this.options = data;
|
|
120
174
|
this.processOptions();
|
|
121
175
|
this.isLoading = false;
|
|
@@ -131,7 +185,14 @@ export class JToggleSegmentComponent implements OnInit, OnChanges, ControlValueA
|
|
|
131
185
|
}
|
|
132
186
|
|
|
133
187
|
processOptions(): void {
|
|
134
|
-
this.internalOptions = normalizeToggleOptions(
|
|
188
|
+
this.internalOptions = normalizeToggleOptions(
|
|
189
|
+
this.options,
|
|
190
|
+
this.optionLabel,
|
|
191
|
+
this.optionValue,
|
|
192
|
+
this.labelSeparator,
|
|
193
|
+
this.optionLabelFn ? undefined : this.optionLabelTemplate || undefined,
|
|
194
|
+
this.optionLabelFn,
|
|
195
|
+
);
|
|
135
196
|
}
|
|
136
197
|
|
|
137
198
|
select(value: unknown): void {
|