@egose/shadcn-theme-ng 0.6.1 → 0.6.3
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/data-table/README.md +1 -1
- package/data-table/fesm2022/data-table.mjs +16 -6
- package/data-table/types/data-table.d.ts +9 -2
- package/form-select/README.md +8 -7
- package/form-select/fesm2022/form-select.mjs +33 -3
- package/form-select/types/form-select.d.ts +17 -0
- package/package.json +1 -1
- package/select/README.md +97 -45
package/data-table/README.md
CHANGED
|
@@ -156,7 +156,7 @@ Key `EgDataTable` inputs: `columns` (required), `gridColumns`, `data` (array, or
|
|
|
156
156
|
|
|
157
157
|
Outputs: `selectionChange` (also fires once on init, mirroring React's mount effect), `pageChange`, `pageSizeChange`, `rowClick`, `sortingChange`, `columnFiltersChange`, `filterChange` (toolbar input string), `columnVisibilityChange`.
|
|
158
158
|
|
|
159
|
-
The toolbar (`showToolbar`) always renders the filter input: with `filterColumnId` it filters that column client-side (and `filterChange` mirrors the string); without it, keystrokes only emit `filterChange` so the caller can filter server-side. The column-visibility dropdown is opt-in via `showColumnToggle` (default `false`). `toolbarActions` takes an `ng-template` rendered on the right side of the toolbar row (built-ins stay left) — it renders the row even when `showToolbar` is false.
|
|
159
|
+
The toolbar (`showToolbar`) always renders the filter input: with `filterColumnId` it filters that column client-side (and `filterChange` mirrors the string); without it, keystrokes only emit `filterChange` so the caller can filter server-side. The column-visibility dropdown is opt-in via `showColumnToggle` (default `false`, requires `showToolbar`). `showColumnToggle` also enables hiding: the `Hide` item in `EgDataTableColumnHeader` only renders when it is true (TanStack `column.getCanHide()`), so a hidden column always has a way back via View; per-column `enableHiding: false` disables individual columns even then. `toolbarActions` takes an `ng-template` rendered on the right side of the toolbar row (built-ins stay left) — it renders the row even when `showToolbar` is false.
|
|
160
160
|
|
|
161
161
|
`size` scales spatial density one step down/up (`sm`: `h-8` headers, `p-1` cells, `h-7` buttons; `lg`: `h-12` headers, `p-3` cells, `h-9` buttons; filter input, page-size trigger, and pager meta texts follow). `EgDataTableColumnHeader` (consumer-instantiated via `flexRenderComponent`) takes the same `size` — for reactive updates put every input in `bindings` (`inputBinding('size', ...)`, plus `column`/`title`, since `bindings` cannot mix with `inputs`).
|
|
162
162
|
|
|
@@ -154,7 +154,7 @@ class EgDataTableViewOptions {
|
|
|
154
154
|
.filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide()), /* @ts-ignore */
|
|
155
155
|
...(ngDevMode ? [{ debugName: "_hidableColumns" }] : /* istanbul ignore next */ []));
|
|
156
156
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgDataTableViewOptions, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
157
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTableViewOptions, isStandalone: true, selector: "eg-data-table-view-options", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideSettings2 })], ngImport: i0, template: "\n <button\n hlmButton\n variant=\"secondary\"\n appearance=\"outline\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <ng-icon hlm name=\"lucideSettings2\" />\n View\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu class=\"w-[150px]\">\n <div hlmDropdownMenuLabel>Toggle columns</div>\n <div hlmDropdownMenuSeparator></div>\n @for (column of _hidableColumns(); track column.id) {\n <button\n hlmDropdownMenuCheckbox\n class=\"capitalize\"\n [checked]=\"column.getIsVisible()\"\n (triggered)=\"column.toggleVisibility()\"\n >\n <hlm-dropdown-menu-checkbox-indicator />\n {{ column.id }}\n </button>\n }\n </div>\n </ng-template>\n ", isInline: true, dependencies: [{ kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "directive", type: i1$1.HlmDropdownMenu, selector: "[hlmDropdownMenu],hlm-dropdown-menu", inputs: ["sideOffset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuCheckbox, selector: "[hlmDropdownMenuCheckbox],[hlmDropdownMenuCheckboxItem]", inputs: ["inset"] }, { kind: "component", type: i1$1.HlmDropdownMenuCheckboxIndicator, selector: "hlm-dropdown-menu-checkbox-indicator" }, { kind: "directive", type: i1$1.HlmDropdownMenuLabel, selector: "[hlmDropdownMenuLabel],hlm-dropdown-menu-label", inputs: ["inset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuSeparator, selector: "[hlmDropdownMenuSeparator],hlm-dropdown-menu-separator" }, { kind: "directive", type: i1$1.HlmDropdownMenuTrigger, selector: "[hlmDropdownMenuTrigger]", inputs: ["align", "side"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: HlmIcon, selector: "ng-icon[hlm]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
157
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTableViewOptions, isStandalone: true, selector: "eg-data-table-view-options", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideSettings2 })], ngImport: i0, template: "\n <button\n hlmButton\n variant=\"secondary\"\n appearance=\"outline\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <ng-icon hlm size=\"sm\" name=\"lucideSettings2\" />\n View\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu class=\"w-[150px]\">\n <div hlmDropdownMenuLabel>Toggle columns</div>\n <div hlmDropdownMenuSeparator></div>\n @for (column of _hidableColumns(); track column.id) {\n <button\n hlmDropdownMenuCheckbox\n class=\"capitalize\"\n [checked]=\"column.getIsVisible()\"\n (triggered)=\"column.toggleVisibility()\"\n >\n <hlm-dropdown-menu-checkbox-indicator />\n {{ column.id }}\n </button>\n }\n </div>\n </ng-template>\n ", isInline: true, dependencies: [{ kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "directive", type: i1$1.HlmDropdownMenu, selector: "[hlmDropdownMenu],hlm-dropdown-menu", inputs: ["sideOffset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuCheckbox, selector: "[hlmDropdownMenuCheckbox],[hlmDropdownMenuCheckboxItem]", inputs: ["inset"] }, { kind: "component", type: i1$1.HlmDropdownMenuCheckboxIndicator, selector: "hlm-dropdown-menu-checkbox-indicator" }, { kind: "directive", type: i1$1.HlmDropdownMenuLabel, selector: "[hlmDropdownMenuLabel],hlm-dropdown-menu-label", inputs: ["inset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuSeparator, selector: "[hlmDropdownMenuSeparator],hlm-dropdown-menu-separator" }, { kind: "directive", type: i1$1.HlmDropdownMenuTrigger, selector: "[hlmDropdownMenuTrigger]", inputs: ["align", "side"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: HlmIcon, selector: "ng-icon[hlm]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
158
158
|
}
|
|
159
159
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgDataTableViewOptions, decorators: [{
|
|
160
160
|
type: Component,
|
|
@@ -162,7 +162,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
162
162
|
selector: 'eg-data-table-view-options',
|
|
163
163
|
imports: [HlmButton, HlmDropdownMenuImports, NgIcon, HlmIcon],
|
|
164
164
|
providers: [provideIcons({ lucideSettings2 })],
|
|
165
|
-
template: "\n <button\n hlmButton\n variant=\"secondary\"\n appearance=\"outline\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <ng-icon hlm name=\"lucideSettings2\" />\n View\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu class=\"w-[150px]\">\n <div hlmDropdownMenuLabel>Toggle columns</div>\n <div hlmDropdownMenuSeparator></div>\n @for (column of _hidableColumns(); track column.id) {\n <button\n hlmDropdownMenuCheckbox\n class=\"capitalize\"\n [checked]=\"column.getIsVisible()\"\n (triggered)=\"column.toggleVisibility()\"\n >\n <hlm-dropdown-menu-checkbox-indicator />\n {{ column.id }}\n </button>\n }\n </div>\n </ng-template>\n ",
|
|
165
|
+
template: "\n <button\n hlmButton\n variant=\"secondary\"\n appearance=\"outline\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <ng-icon hlm size=\"sm\" name=\"lucideSettings2\" />\n View\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu class=\"w-[150px]\">\n <div hlmDropdownMenuLabel>Toggle columns</div>\n <div hlmDropdownMenuSeparator></div>\n @for (column of _hidableColumns(); track column.id) {\n <button\n hlmDropdownMenuCheckbox\n class=\"capitalize\"\n [checked]=\"column.getIsVisible()\"\n (triggered)=\"column.toggleVisibility()\"\n >\n <hlm-dropdown-menu-checkbox-indicator />\n {{ column.id }}\n </button>\n }\n </div>\n </ng-template>\n ",
|
|
166
166
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
167
167
|
}]
|
|
168
168
|
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
@@ -296,7 +296,7 @@ class EgDataTable {
|
|
|
296
296
|
...(ngDevMode ? [{ debugName: "filterColumnId" }] : /* istanbul ignore next */ []));
|
|
297
297
|
filterPlaceholder = input('Filter...', /* @ts-ignore */
|
|
298
298
|
...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : /* istanbul ignore next */ []));
|
|
299
|
-
/** Show the column-visibility dropdown inside the toolbar. Off by default; opt in per table. */
|
|
299
|
+
/** Show the column-visibility dropdown inside the toolbar (requires `showToolbar`). Off by default; opt in per table. Also enables hiding — the `Hide` item in `EgDataTableColumnHeader` only renders when this is true, so hidden columns always have a way back. Per-column `enableHiding: false` further disables individual columns. */
|
|
300
300
|
showColumnToggle = input(false, /* @ts-ignore */
|
|
301
301
|
...(ngDevMode ? [{ debugName: "showColumnToggle" }] : /* istanbul ignore next */ []));
|
|
302
302
|
/** Custom right-side toolbar content (e.g. action buttons) as a template. Renders the toolbar row even when `showToolbar` is false; built-ins stay on the left. */
|
|
@@ -458,6 +458,10 @@ class EgDataTable {
|
|
|
458
458
|
columns: this._finalColumns(),
|
|
459
459
|
data: this._tableItems(),
|
|
460
460
|
getRowId: this.getRowId(),
|
|
461
|
+
// Hiding is only allowed when the View menu can bring columns back.
|
|
462
|
+
// This drives `column.getCanHide()` for both `EgDataTableColumnHeader`
|
|
463
|
+
// (Hide item) and `EgDataTableViewOptions` (toggle list).
|
|
464
|
+
enableHiding: this.showColumnToggle(),
|
|
461
465
|
manualPagination: server,
|
|
462
466
|
manualSorting: server,
|
|
463
467
|
manualFiltering: server,
|
|
@@ -642,7 +646,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
642
646
|
*
|
|
643
647
|
* Render via `flexRenderComponent(EgDataTableColumnHeader, { inputs: { column, title } })`
|
|
644
648
|
* so the extra `title` input flows alongside the flex-render `column` context.
|
|
645
|
-
* Non-sortable columns render a plain label.
|
|
649
|
+
* Non-sortable columns render a plain label. The `Hide` menu item only renders
|
|
650
|
+
* when `column.getCanHide()` is true — `EgDataTable` ties table-level
|
|
651
|
+
* `enableHiding` to its `showColumnToggle` input so there is always a way back
|
|
652
|
+
* via the View menu, and per-column `enableHiding: false` further disables it.
|
|
646
653
|
*/
|
|
647
654
|
class EgDataTableColumnHeader {
|
|
648
655
|
column = input.required(/* @ts-ignore */
|
|
@@ -654,11 +661,14 @@ class EgDataTableColumnHeader {
|
|
|
654
661
|
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
655
662
|
_sorted = computed(() => this.column().getIsSorted(), /* @ts-ignore */
|
|
656
663
|
...(ngDevMode ? [{ debugName: "_sorted" }] : /* istanbul ignore next */ []));
|
|
664
|
+
/** Hideable only when TanStack allows it (table + column `enableHiding`). Absent `getCanHide` (legacy mocks) means hideable. */
|
|
665
|
+
_canHide = computed(() => this.column().getCanHide?.() ?? true, /* @ts-ignore */
|
|
666
|
+
...(ngDevMode ? [{ debugName: "_canHide" }] : /* istanbul ignore next */ []));
|
|
657
667
|
/** Height per density. Merged by `hlmButton`. */
|
|
658
668
|
_buttonClass = computed(() => hlm("-ml-3", this.size() === 'sm' ? "h-7" : this.size() === 'lg' ? "h-9" : "h-8"), /* @ts-ignore */
|
|
659
669
|
...(ngDevMode ? [{ debugName: "_buttonClass" }] : /* istanbul ignore next */ []));
|
|
660
670
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgDataTableColumnHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
661
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTableColumnHeader, isStandalone: true, selector: "eg-data-table-column-header", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideArrowDown, lucideArrowUp, lucideChevronsUpDown, lucideEyeOff })], ngImport: i0, template: "\n @if (column().getCanSort()) {\n <div class=\"flex items-center gap-2\">\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <span>{{ title() }}</span>\n @switch (_sorted()) {\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n Asc\n </button>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(true)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n Desc\n </button>\n <div hlmDropdownMenuSeparator></div>\n
|
|
671
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTableColumnHeader, isStandalone: true, selector: "eg-data-table-column-header", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideArrowDown, lucideArrowUp, lucideChevronsUpDown, lucideEyeOff })], ngImport: i0, template: "\n @if (column().getCanSort()) {\n <div class=\"flex items-center gap-2\">\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <span>{{ title() }}</span>\n @switch (_sorted()) {\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n Asc\n </button>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(true)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n Desc\n </button>\n @if (_canHide()) {\n <div hlmDropdownMenuSeparator></div>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleVisibility(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideEyeOff\" />\n Hide\n </button>\n }\n </div>\n </ng-template>\n </div>\n } @else {\n <div>{{ title() }}</div>\n }\n ", isInline: true, dependencies: [{ kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "directive", type: i1$1.HlmDropdownMenu, selector: "[hlmDropdownMenu],hlm-dropdown-menu", inputs: ["sideOffset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuItem, selector: "[hlmDropdownMenuItem],hlm-dropdown-menu-item", inputs: ["disabled", "variant", "inset"] }, { kind: "directive", type: i1$1.HlmDropdownMenuSeparator, selector: "[hlmDropdownMenuSeparator],hlm-dropdown-menu-separator" }, { kind: "directive", type: i1$1.HlmDropdownMenuTrigger, selector: "[hlmDropdownMenuTrigger]", inputs: ["align", "side"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: HlmIcon, selector: "ng-icon[hlm]", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
662
672
|
}
|
|
663
673
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgDataTableColumnHeader, decorators: [{
|
|
664
674
|
type: Component,
|
|
@@ -666,7 +676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
666
676
|
selector: 'eg-data-table-column-header',
|
|
667
677
|
imports: [HlmButton, HlmDropdownMenuImports, NgIcon, HlmIcon],
|
|
668
678
|
providers: [provideIcons({ lucideArrowDown, lucideArrowUp, lucideChevronsUpDown, lucideEyeOff })],
|
|
669
|
-
template: "\n @if (column().getCanSort()) {\n <div class=\"flex items-center gap-2\">\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <span>{{ title() }}</span>\n @switch (_sorted()) {\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n Asc\n </button>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(true)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n Desc\n </button>\n <div hlmDropdownMenuSeparator></div>\n
|
|
679
|
+
template: "\n @if (column().getCanSort()) {\n <div class=\"flex items-center gap-2\">\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_buttonClass()\"\n type=\"button\"\n [hlmDropdownMenuTrigger]=\"_menu\"\n >\n <span>{{ title() }}</span>\n @switch (_sorted()) {\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n <ng-template #_menu>\n <div hlmDropdownMenu>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n Asc\n </button>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleSorting(true)\">\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n Desc\n </button>\n @if (_canHide()) {\n <div hlmDropdownMenuSeparator></div>\n <button hlmDropdownMenuItem type=\"button\" (click)=\"column().toggleVisibility(false)\">\n <ng-icon hlm size=\"sm\" name=\"lucideEyeOff\" />\n Hide\n </button>\n }\n </div>\n </ng-template>\n </div>\n } @else {\n <div>{{ title() }}</div>\n }\n ",
|
|
670
680
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
671
681
|
}]
|
|
672
682
|
}], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
@@ -237,7 +237,7 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
|
|
|
237
237
|
/** Column id the toolbar filter input writes to (e.g. `'email'`). Empty means client-side filtering is skipped and keystrokes only emit `filterChange` (server-side pattern). */
|
|
238
238
|
readonly filterColumnId: _angular_core.InputSignal<string>;
|
|
239
239
|
readonly filterPlaceholder: _angular_core.InputSignal<string>;
|
|
240
|
-
/** Show the column-visibility dropdown inside the toolbar. Off by default; opt in per table. */
|
|
240
|
+
/** Show the column-visibility dropdown inside the toolbar (requires `showToolbar`). Off by default; opt in per table. Also enables hiding — the `Hide` item in `EgDataTableColumnHeader` only renders when this is true, so hidden columns always have a way back. Per-column `enableHiding: false` further disables individual columns. */
|
|
241
241
|
readonly showColumnToggle: _angular_core.InputSignal<boolean>;
|
|
242
242
|
/** Custom right-side toolbar content (e.g. action buttons) as a template. Renders the toolbar row even when `showToolbar` is false; built-ins stay on the left. */
|
|
243
243
|
readonly toolbarActions: _angular_core.InputSignal<TemplateRef<unknown> | undefined>;
|
|
@@ -381,13 +381,18 @@ interface EgSortableColumn {
|
|
|
381
381
|
getIsSorted(): false | SortDirection;
|
|
382
382
|
toggleSorting(desc?: boolean): void;
|
|
383
383
|
toggleVisibility(value?: boolean): void;
|
|
384
|
+
/** Whether the column can be hidden (`columnDef.enableHiding` and table `enableHiding`). Optional for back-compat mocks — absent means hideable. */
|
|
385
|
+
getCanHide?(): boolean;
|
|
384
386
|
}
|
|
385
387
|
/**
|
|
386
388
|
* Sortable + hideable column header.
|
|
387
389
|
*
|
|
388
390
|
* Render via `flexRenderComponent(EgDataTableColumnHeader, { inputs: { column, title } })`
|
|
389
391
|
* so the extra `title` input flows alongside the flex-render `column` context.
|
|
390
|
-
* Non-sortable columns render a plain label.
|
|
392
|
+
* Non-sortable columns render a plain label. The `Hide` menu item only renders
|
|
393
|
+
* when `column.getCanHide()` is true — `EgDataTable` ties table-level
|
|
394
|
+
* `enableHiding` to its `showColumnToggle` input so there is always a way back
|
|
395
|
+
* via the View menu, and per-column `enableHiding: false` further disables it.
|
|
391
396
|
*/
|
|
392
397
|
declare class EgDataTableColumnHeader {
|
|
393
398
|
readonly column: _angular_core.InputSignal<EgSortableColumn>;
|
|
@@ -395,6 +400,8 @@ declare class EgDataTableColumnHeader {
|
|
|
395
400
|
/** Density of the trigger button. */
|
|
396
401
|
readonly size: _angular_core.InputSignal<HlmTableSize>;
|
|
397
402
|
protected readonly _sorted: _angular_core.Signal<false | SortDirection>;
|
|
403
|
+
/** Hideable only when TanStack allows it (table + column `enableHiding`). Absent `getCanHide` (legacy mocks) means hideable. */
|
|
404
|
+
protected readonly _canHide: _angular_core.Signal<boolean>;
|
|
398
405
|
/** Height per density. Merged by `hlmButton`. */
|
|
399
406
|
protected readonly _buttonClass: _angular_core.Signal<string>;
|
|
400
407
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgDataTableColumnHeader, never>;
|
package/form-select/README.md
CHANGED
|
@@ -44,8 +44,9 @@ export class MyForm {}
|
|
|
44
44
|
Requirements:
|
|
45
45
|
|
|
46
46
|
- Must sit inside a `<form [formGroup]>` (it injects `FormGroupDirective` and provides `ControlContainer`).
|
|
47
|
-
- `controlName` is required — forwarded as `[formControlName]` to the inner `
|
|
47
|
+
- `controlName` is required — forwarded as `[formControlName]` to the inner `hlm-select` / `hlm-select-multiple`.
|
|
48
48
|
- Single mode binds `string`; multi mode (`multiple`) binds `string[]`.
|
|
49
|
+
- The closed trigger always shows the option `label` (single: via `itemToString`; multi: comma-joined labels). The form value stays the raw `value` string(s).
|
|
49
50
|
|
|
50
51
|
## Anatomy / Structure
|
|
51
52
|
|
|
@@ -54,7 +55,7 @@ Requirements:
|
|
|
54
55
|
<!-- rendered internally (single mode) -->
|
|
55
56
|
<hlm-form-field>
|
|
56
57
|
<label hlmLabel for="<effectiveId>">Country <span>*</span></label>
|
|
57
|
-
<
|
|
58
|
+
<hlm-select formControlName="country" itemToString="…">
|
|
58
59
|
<hlm-select-trigger buttonId="<effectiveId>" ariaDescribedby="…">
|
|
59
60
|
<hlm-select-value placeholder="Select…" />
|
|
60
61
|
</hlm-select-trigger>
|
|
@@ -62,14 +63,14 @@ Requirements:
|
|
|
62
63
|
<hlm-select-label>Fruits</hlm-select-label>
|
|
63
64
|
<hlm-select-item value="apple">Apple</hlm-select-item>
|
|
64
65
|
</hlm-select-content>
|
|
65
|
-
</
|
|
66
|
+
</hlm-select>
|
|
66
67
|
<hlm-error id="<effectiveId>-error">…</hlm-error>
|
|
67
68
|
<hlm-hint id="<effectiveId>-hint">…</hlm-hint>
|
|
68
69
|
</hlm-form-field>
|
|
69
70
|
</eg-form-select>
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
In multi mode the inner `
|
|
73
|
+
In multi mode the inner `hlm-select` becomes `hlm-select-multiple` and the trigger renders `<hlm-select-placeholder>` plus an `hlmSelectValues` template with comma-joined labels inside `<hlm-select-values-content>`; everything else is identical. Real selectors: `eg-form-select`, `hlm-form-field`, `label[hlmLabel]`, `hlm-select` / `hlm-select-multiple`, `hlm-select-trigger`, `hlm-select-value` (single) / `hlm-select-placeholder` + `hlm-select-values-content` (multi), `hlm-select-content`, `hlm-select-label`, `hlm-select-item`, `hlm-error`, `hlm-hint`.
|
|
73
74
|
|
|
74
75
|
## API reference
|
|
75
76
|
|
|
@@ -84,11 +85,11 @@ In multi mode the inner `brn-select` becomes `brn-select-multiple`; everything e
|
|
|
84
85
|
| `error` | `string \| undefined` | `undefined` | Error text rendered in `<hlm-error>`. |
|
|
85
86
|
| `autoError` | `boolean` | `true` | Auto-resolve the message from the control's `ValidationErrors` when `error` is unset. Explicit `error` always wins; `error=""` counts as unset. Global wording via `provideEgFormErrorMessages`. |
|
|
86
87
|
| `hint` | `string \| undefined` | `undefined` | Hint text rendered in `<hlm-hint>`. |
|
|
87
|
-
| `placeholder` | `string` | `''` | Placeholder forwarded to `<hlm-select-value
|
|
88
|
+
| `placeholder` | `string` | `''` | Placeholder forwarded to `<hlm-select-value>` (single) / `<hlm-select-placeholder>` (multi). |
|
|
88
89
|
| `disabled` | `boolean` | `false` | Forwarded as `wrapperDisabled` to `<hlm-select-trigger>`. |
|
|
89
90
|
| `required` | `boolean` | `false` | Renders a red `*` next to the label (pair with `Validators.required`). |
|
|
90
|
-
| `multiple` | `boolean` | `false` | When `true`, renders `
|
|
91
|
-
| `options` | `SelectOption[]` | `[]` | `{ value: string; label: string }[]` rendered as `<hlm-select-item>` rows. Local interface, not exported.
|
|
91
|
+
| `multiple` | `boolean` | `false` | When `true`, renders `hlm-select-multiple` (value is `string[]`, trigger shows comma-joined labels). |
|
|
92
|
+
| `options` | `SelectOption[]` | `[]` | `{ value: string; label: string }[]` rendered as `<hlm-select-item>` rows. `label` is shown in the list and the closed trigger; `value` is the form value. Local interface, not exported. |
|
|
92
93
|
| `optionsLabel` | `string \| undefined` | `undefined` | Optional group heading rendered once as `<hlm-select-label>`. |
|
|
93
94
|
| `class` (`userClass`) | `ClassValue` | `''` | Extra host classes (merged over `tw:flex tw:flex-col`). |
|
|
94
95
|
| `labelClass` | `string` | `''` | Extra label classes (merged over `tw:mb-1 tw:gap-0`). |
|
|
@@ -7,7 +7,7 @@ import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
|
7
7
|
import { hlm } from '@egose/shadcn-theme-ng/utils';
|
|
8
8
|
import { BrnSelectImports } from '@spartan-ng/brain/select';
|
|
9
9
|
import * as i2 from '@egose/shadcn-theme-ng/select';
|
|
10
|
-
import { HlmSelectImports, HlmSelect, HlmSelectTrigger, HlmSelectValue, HlmSelectContent, HlmSelectItem, HlmSelectLabel } from '@egose/shadcn-theme-ng/select';
|
|
10
|
+
import { HlmSelectImports, HlmSelect, HlmSelectMultiple, HlmSelectTrigger, HlmSelectValue, HlmSelectPlaceholder, HlmSelectValues, HlmSelectValuesContent, HlmSelectContent, HlmSelectItem, HlmSelectLabel } from '@egose/shadcn-theme-ng/select';
|
|
11
11
|
|
|
12
12
|
const EgFormSelectConfigToken = new InjectionToken('EgFormSelectConfig');
|
|
13
13
|
function provideEgFormSelectConfig(config) {
|
|
@@ -91,6 +91,32 @@ class EgFormSelect {
|
|
|
91
91
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
92
92
|
optionsLabel = input(undefined, /* @ts-ignore */
|
|
93
93
|
...(ngDevMode ? [{ debugName: "optionsLabel" }] : /* istanbul ignore next */ [])); // optional group label
|
|
94
|
+
/**
|
|
95
|
+
* Maps a stored `value` to its human-readable `label` for the closed trigger
|
|
96
|
+
* (and listbox type-ahead). Spartan's `BrnSelectValue` renders
|
|
97
|
+
* `stringifyAsLabel(value, itemToString)` — without this it would show the raw
|
|
98
|
+
* value (e.g. `jahn` instead of `J. Hahn`). Unknown IDs fall back to the raw
|
|
99
|
+
* value so async/programmatic values stay visible. Plain arrow-function
|
|
100
|
+
* property (stable identity); reads `options()` at call time so Spartan's
|
|
101
|
+
* display computed re-evaluates when options arrive.
|
|
102
|
+
*/
|
|
103
|
+
itemToString = (value) => {
|
|
104
|
+
if (value === null || value === undefined)
|
|
105
|
+
return '';
|
|
106
|
+
return this.options().find((option) => option.value === value)?.label ?? String(value);
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Comma-joined labels for multi-select trigger content. Plain method (not a
|
|
110
|
+
* computed): `values` comes from the `hlmSelectValues` template context, while
|
|
111
|
+
* `options()` is read fresh so late-arriving options refresh labels without
|
|
112
|
+
* touching selection — mirroring the React `FormMultiSelect` fallback.
|
|
113
|
+
*/
|
|
114
|
+
formatMultipleLabels(values) {
|
|
115
|
+
if (!values?.length)
|
|
116
|
+
return '';
|
|
117
|
+
const labels = new Map(this.options().map((option) => [option.value, option.label]));
|
|
118
|
+
return values.map((value) => labels.get(value) ?? String(value)).join(', ');
|
|
119
|
+
}
|
|
94
120
|
// Styling classes
|
|
95
121
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
96
122
|
labelClass = input('', /* @ts-ignore */
|
|
@@ -113,7 +139,7 @@ class EgFormSelect {
|
|
|
113
139
|
$hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
114
140
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
115
141
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
116
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSelect, isStandalone: true, selector: "eg-form-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionsLabel: { classPropertyName: "optionsLabel", publicName: "optionsLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, selectClass: { classPropertyName: "selectClass", publicName: "selectClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (multiple()) {\n <
|
|
142
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSelect, isStandalone: true, selector: "eg-form-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionsLabel: { classPropertyName: "optionsLabel", publicName: "optionsLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, selectClass: { classPropertyName: "selectClass", publicName: "selectClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (multiple()) {\n <hlm-select-multiple [formControlName]=\"cnm\" [itemToString]=\"itemToString\">\n <hlm-select-trigger\n [buttonId]=\"effectiveId()\"\n [ariaDescribedby]=\"describedBy()\"\n [wrapperDisabled]=\"disabled()\"\n [class]=\"$selectClass()\"\n >\n <hlm-select-placeholder>{{ placeholder() }}</hlm-select-placeholder>\n <ng-template hlmSelectValues let-values>\n <hlm-select-values-content>\n {{ formatMultipleLabels(values) }}\n </hlm-select-values-content>\n </ng-template>\n </hlm-select-trigger>\n\n <hlm-select-content *hlmSelectPortal>\n @if (options().length) {\n @if (optionsLabel()) {\n <hlm-select-label>{{ optionsLabel() }}</hlm-select-label>\n }\n @for (option of options(); track option.value) {\n <hlm-select-item [value]=\"option.value\">{{ option.label }}</hlm-select-item>\n }\n }\n </hlm-select-content>\n </hlm-select-multiple>\n } @else {\n <hlm-select [formControlName]=\"cnm\" [itemToString]=\"itemToString\">\n <hlm-select-trigger\n [buttonId]=\"effectiveId()\"\n [ariaDescribedby]=\"describedBy()\"\n [wrapperDisabled]=\"disabled()\"\n [class]=\"$selectClass()\"\n >\n <hlm-select-value [placeholder]=\"placeholder()\" />\n </hlm-select-trigger>\n\n <hlm-select-content *hlmSelectPortal>\n @if (options().length) {\n @if (optionsLabel()) {\n <hlm-select-label>{{ optionsLabel() }}</hlm-select-label>\n }\n @for (option of options(); track option.value) {\n <hlm-select-item [value]=\"option.value\">{{ option.label }}</hlm-select-item>\n }\n }\n </hlm-select-content>\n </hlm-select>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "directive", type: i2.HlmSelect, selector: "[hlmSelect],hlm-select" }, { kind: "component", type: i2.HlmSelectContent, selector: "hlm-select-content", inputs: ["showScroll"] }, { kind: "component", type: i2.HlmSelectItem, selector: "hlm-select-item" }, { kind: "directive", type: i2.HlmSelectLabel, selector: "[hlmSelectLabel],hlm-select-label" }, { kind: "directive", type: i2.HlmSelectMultiple, selector: "[hlmSelectMultiple],hlm-select-multiple" }, { kind: "directive", type: i2.HlmSelectPlaceholder, selector: "[hlmSelectPlaceholder],hlm-select-placeholder" }, { kind: "directive", type: i2.HlmSelectPortal, selector: "[hlmSelectPortal]" }, { kind: "component", type: i2.HlmSelectTrigger, selector: "hlm-select-trigger", inputs: ["class", "buttonId", "ariaDescribedby", "wrapperDisabled", "size", "forceInvalid"] }, { kind: "directive", type: i2.HlmSelectValue, selector: "[hlmSelectValue],hlm-select-value" }, { kind: "directive", type: i2.HlmSelectValues, selector: "[hlmSelectValues]" }, { kind: "directive", type: i2.HlmSelectValuesContent, selector: "[hlmSelectValuesContent],hlm-select-values-content" }] });
|
|
117
143
|
}
|
|
118
144
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSelect, decorators: [{
|
|
119
145
|
type: Component,
|
|
@@ -132,14 +158,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
132
158
|
BrnSelectImports,
|
|
133
159
|
HlmSelectImports,
|
|
134
160
|
HlmSelect,
|
|
161
|
+
HlmSelectMultiple,
|
|
135
162
|
HlmSelectTrigger,
|
|
136
163
|
HlmSelectValue,
|
|
164
|
+
HlmSelectPlaceholder,
|
|
165
|
+
HlmSelectValues,
|
|
166
|
+
HlmSelectValuesContent,
|
|
137
167
|
HlmSelectContent,
|
|
138
168
|
HlmSelectItem,
|
|
139
169
|
HlmSelectLabel,
|
|
140
170
|
],
|
|
141
171
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
142
|
-
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (multiple()) {\n <
|
|
172
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (multiple()) {\n <hlm-select-multiple [formControlName]=\"cnm\" [itemToString]=\"itemToString\">\n <hlm-select-trigger\n [buttonId]=\"effectiveId()\"\n [ariaDescribedby]=\"describedBy()\"\n [wrapperDisabled]=\"disabled()\"\n [class]=\"$selectClass()\"\n >\n <hlm-select-placeholder>{{ placeholder() }}</hlm-select-placeholder>\n <ng-template hlmSelectValues let-values>\n <hlm-select-values-content>\n {{ formatMultipleLabels(values) }}\n </hlm-select-values-content>\n </ng-template>\n </hlm-select-trigger>\n\n <hlm-select-content *hlmSelectPortal>\n @if (options().length) {\n @if (optionsLabel()) {\n <hlm-select-label>{{ optionsLabel() }}</hlm-select-label>\n }\n @for (option of options(); track option.value) {\n <hlm-select-item [value]=\"option.value\">{{ option.label }}</hlm-select-item>\n }\n }\n </hlm-select-content>\n </hlm-select-multiple>\n } @else {\n <hlm-select [formControlName]=\"cnm\" [itemToString]=\"itemToString\">\n <hlm-select-trigger\n [buttonId]=\"effectiveId()\"\n [ariaDescribedby]=\"describedBy()\"\n [wrapperDisabled]=\"disabled()\"\n [class]=\"$selectClass()\"\n >\n <hlm-select-value [placeholder]=\"placeholder()\" />\n </hlm-select-trigger>\n\n <hlm-select-content *hlmSelectPortal>\n @if (options().length) {\n @if (optionsLabel()) {\n <hlm-select-label>{{ optionsLabel() }}</hlm-select-label>\n }\n @for (option of options(); track option.value) {\n <hlm-select-item [value]=\"option.value\">{{ option.label }}</hlm-select-item>\n }\n }\n </hlm-select-content>\n </hlm-select>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
|
|
143
173
|
}]
|
|
144
174
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionsLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], selectClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
145
175
|
|
|
@@ -47,6 +47,23 @@ declare class EgFormSelect {
|
|
|
47
47
|
multiple: _angular_core.InputSignal<boolean>;
|
|
48
48
|
options: _angular_core.InputSignal<SelectOption[]>;
|
|
49
49
|
optionsLabel: _angular_core.InputSignal<string | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Maps a stored `value` to its human-readable `label` for the closed trigger
|
|
52
|
+
* (and listbox type-ahead). Spartan's `BrnSelectValue` renders
|
|
53
|
+
* `stringifyAsLabel(value, itemToString)` — without this it would show the raw
|
|
54
|
+
* value (e.g. `jahn` instead of `J. Hahn`). Unknown IDs fall back to the raw
|
|
55
|
+
* value so async/programmatic values stay visible. Plain arrow-function
|
|
56
|
+
* property (stable identity); reads `options()` at call time so Spartan's
|
|
57
|
+
* display computed re-evaluates when options arrive.
|
|
58
|
+
*/
|
|
59
|
+
readonly itemToString: (value: string | null | undefined) => string;
|
|
60
|
+
/**
|
|
61
|
+
* Comma-joined labels for multi-select trigger content. Plain method (not a
|
|
62
|
+
* computed): `values` comes from the `hlmSelectValues` template context, while
|
|
63
|
+
* `options()` is read fresh so late-arriving options refresh labels without
|
|
64
|
+
* touching selection — mirroring the React `FormMultiSelect` fallback.
|
|
65
|
+
*/
|
|
66
|
+
protected formatMultipleLabels(values: string[] | null | undefined): string;
|
|
50
67
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
51
68
|
labelClass: _angular_core.InputSignal<string>;
|
|
52
69
|
selectClass: _angular_core.InputSignal<string>;
|
package/package.json
CHANGED
package/select/README.md
CHANGED
|
@@ -100,16 +100,15 @@ Single select:
|
|
|
100
100
|
</div>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Multi select (same skeleton, `hlmSelectMultiple` + `hlmSelectValues`):
|
|
103
|
+
Multi select (same skeleton, `hlmSelectMultiple` + `hlmSelectValues` template):
|
|
104
104
|
|
|
105
105
|
```html
|
|
106
106
|
<div hlmSelectMultiple [(value)]="toppings">
|
|
107
107
|
<hlm-select-trigger>
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</span>
|
|
108
|
+
<hlm-select-placeholder>Pick toppings</hlm-select-placeholder>
|
|
109
|
+
<ng-template hlmSelectValues let-values>
|
|
110
|
+
<hlm-select-values-content> {{ formatToppings(values) }} </hlm-select-values-content>
|
|
111
|
+
</ng-template>
|
|
113
112
|
</hlm-select-trigger>
|
|
114
113
|
|
|
115
114
|
<hlm-select-content *hlmSelectPortal>
|
|
@@ -119,6 +118,8 @@ Multi select (same skeleton, `hlmSelectMultiple` + `hlmSelectValues`):
|
|
|
119
118
|
</div>
|
|
120
119
|
```
|
|
121
120
|
|
|
121
|
+
> Display rule: the closed trigger does **not** read the item content — single mode renders `itemToString(value)` (raw `value` when unset), so whenever the label differs from the value (e.g. `jahn` → `J. Hahn`) pass `[itemToString]` that maps values to labels. Multi mode renders whatever the `hlmSelectValues` template outputs, so map each id to its label there (see examples 1 and 4).
|
|
122
|
+
|
|
122
123
|
Selector/slot summary:
|
|
123
124
|
|
|
124
125
|
| Selector | `data-slot` | Notes |
|
|
@@ -145,13 +146,13 @@ Selector/slot summary:
|
|
|
145
146
|
|
|
146
147
|
Thin wrappers. Forwarded `BrnSelect` / `BrnSelectMultiple` bindings:
|
|
147
148
|
|
|
148
|
-
| Binding | Kind | Description
|
|
149
|
-
| -------------------- | ------ |
|
|
150
|
-
| `disabled` | input | Disables the select
|
|
151
|
-
| `value` | input | Selected value (single) / values (multiple); use `[(value)]`
|
|
152
|
-
| `isItemEqualToValue` | input | Custom equality fn for object values
|
|
153
|
-
| `itemToString` | input |
|
|
154
|
-
| `valueChange` | output | Emits on selection change
|
|
149
|
+
| Binding | Kind | Description |
|
|
150
|
+
| -------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| `disabled` | input | Disables the select |
|
|
152
|
+
| `value` | input | Selected value (single) / values (multiple); use `[(value)]` |
|
|
153
|
+
| `isItemEqualToValue` | input | Custom equality fn for object values |
|
|
154
|
+
| `itemToString` | input | Maps a value to its trigger/type-ahead label. Required whenever the label differs from the value — without it the raw value shows. |
|
|
155
|
+
| `valueChange` | output | Emits on selection change |
|
|
155
156
|
|
|
156
157
|
Forwarded `BrnPopover` bindings (positioning of the floating panel):
|
|
157
158
|
|
|
@@ -194,18 +195,18 @@ Forwarded `BrnSelectItem` inputs: `id`, `disabled`, `value`. Shows a `lucideChec
|
|
|
194
195
|
|
|
195
196
|
### Remaining pieces
|
|
196
197
|
|
|
197
|
-
| Class | Forwarded inputs | Notes
|
|
198
|
-
| ------------------------ | -------------------------------------------- |
|
|
199
|
-
| `HlmSelectGroup` | — (plain `BrnSelectGroup`) | Padding wrapper
|
|
200
|
-
| `HlmSelectLabel` | `id` (via `BrnSelectLabel`) | Muted small-caps-ish heading
|
|
201
|
-
| `HlmSelectValue` | `placeholder` (via `BrnSelectValue`) |
|
|
202
|
-
| `HlmSelectValues` | — (plain `BrnSelectValues`) | Multi-value host
|
|
203
|
-
| `HlmSelectValuesContent` | — | Flex row with gap
|
|
204
|
-
| `HlmSelectPlaceholder` | — (plain `BrnSelectPlaceholder`) | Hides when a value shows (`data-hidden`)
|
|
205
|
-
| `HlmSelectSeparator` | `orientation` (via `BrnSelectSeparator`) | 1px divider
|
|
206
|
-
| `HlmSelectScrollUp/Down` | — | Sticky chevron affordances
|
|
207
|
-
| `HlmSelectPortal` | `context`, `class` (via `BrnPopoverContent`) | Structural use: `*hlmSelectPortal="let ctx"`
|
|
208
|
-
| `HlmSelectValueTemplate` | — (plain `BrnSelectValueTemplate`) | Marker for custom value rendering
|
|
198
|
+
| Class | Forwarded inputs | Notes |
|
|
199
|
+
| ------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
200
|
+
| `HlmSelectGroup` | — (plain `BrnSelectGroup`) | Padding wrapper |
|
|
201
|
+
| `HlmSelectLabel` | `id` (via `BrnSelectLabel`) | Muted small-caps-ish heading |
|
|
202
|
+
| `HlmSelectValue` | `placeholder` (via `BrnSelectValue`) | Renders `itemToString(value)` or the placeholder; hidden when empty with no placeholder |
|
|
203
|
+
| `HlmSelectValues` | — (plain `BrnSelectValues`) | Multi-value template host (`<ng-template hlmSelectValues let-values>`) |
|
|
204
|
+
| `HlmSelectValuesContent` | — | Flex row with gap |
|
|
205
|
+
| `HlmSelectPlaceholder` | — (plain `BrnSelectPlaceholder`) | Hides when a value shows (`data-hidden`) |
|
|
206
|
+
| `HlmSelectSeparator` | `orientation` (via `BrnSelectSeparator`) | 1px divider |
|
|
207
|
+
| `HlmSelectScrollUp/Down` | — | Sticky chevron affordances |
|
|
208
|
+
| `HlmSelectPortal` | `context`, `class` (via `BrnPopoverContent`) | Structural use: `*hlmSelectPortal="let ctx"` |
|
|
209
|
+
| `HlmSelectValueTemplate` | — (plain `BrnSelectValueTemplate`) | Marker for custom value rendering |
|
|
209
210
|
|
|
210
211
|
## Examples
|
|
211
212
|
|
|
@@ -220,14 +221,14 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
220
221
|
standalone: true,
|
|
221
222
|
imports: [HlmSelectImports],
|
|
222
223
|
template: `
|
|
223
|
-
<div hlmSelect [(value)]="fruit">
|
|
224
|
+
<div hlmSelect [(value)]="fruit" [itemToString]="fruitToString">
|
|
224
225
|
<hlm-select-trigger>
|
|
225
226
|
<hlm-select-value placeholder="Pick a fruit" />
|
|
226
227
|
</hlm-select-trigger>
|
|
227
228
|
<hlm-select-content *hlmSelectPortal>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
@for (option of fruits; track option.value) {
|
|
230
|
+
<hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
|
|
231
|
+
}
|
|
231
232
|
</hlm-select-content>
|
|
232
233
|
</div>
|
|
233
234
|
<p>Selected: {{ fruit() ?? 'none' }}</p>
|
|
@@ -235,13 +236,22 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
235
236
|
})
|
|
236
237
|
export class BasicSelectComponent {
|
|
237
238
|
readonly fruit = signal<string | null>(null);
|
|
239
|
+
readonly fruits = [
|
|
240
|
+
{ value: 'apple', label: 'Apple' },
|
|
241
|
+
{ value: 'banana', label: 'Banana' },
|
|
242
|
+
{ value: 'orange', label: 'Orange' },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// Without this the trigger would show `apple` instead of `Apple`.
|
|
246
|
+
readonly fruitToString = (value: string | null) =>
|
|
247
|
+
this.fruits.find((option) => option.value === value)?.label ?? value ?? '';
|
|
238
248
|
}
|
|
239
249
|
```
|
|
240
250
|
|
|
241
251
|
### 2. Grouped options with labels, separator, disabled item, scroll buttons
|
|
242
252
|
|
|
243
253
|
```html
|
|
244
|
-
<div hlmSelect [(value)]="city">
|
|
254
|
+
<div hlmSelect [(value)]="city" [itemToString]="cityToString">
|
|
245
255
|
<hlm-select-trigger>
|
|
246
256
|
<hlm-select-value placeholder="Pick a city" />
|
|
247
257
|
</hlm-select-trigger>
|
|
@@ -264,6 +274,19 @@ export class BasicSelectComponent {
|
|
|
264
274
|
</div>
|
|
265
275
|
```
|
|
266
276
|
|
|
277
|
+
```ts
|
|
278
|
+
// Component companion: map every grouped value to its label so the trigger
|
|
279
|
+
// shows `Berlin`, not `berlin`.
|
|
280
|
+
readonly cities = [
|
|
281
|
+
{ value: 'berlin', label: 'Berlin' },
|
|
282
|
+
{ value: 'munich', label: 'Munich' },
|
|
283
|
+
{ value: 'paris', label: 'Paris' },
|
|
284
|
+
{ value: 'lyon', label: 'Lyon (unavailable)' },
|
|
285
|
+
];
|
|
286
|
+
readonly cityToString = (value: string | null) =>
|
|
287
|
+
this.cities.find((option) => option.value === value)?.label ?? value ?? '';
|
|
288
|
+
```
|
|
289
|
+
|
|
267
290
|
### 3. Reactive forms + small trigger + forced invalid demo
|
|
268
291
|
|
|
269
292
|
`BrnSelect` is a `ControlValueAccessor`: bind `formControlName` on the `hlmSelect` host.
|
|
@@ -279,14 +302,14 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
279
302
|
imports: [HlmSelectImports, ReactiveFormsModule],
|
|
280
303
|
template: `
|
|
281
304
|
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
282
|
-
<div hlmSelect formControlName="country">
|
|
305
|
+
<div hlmSelect formControlName="country" [itemToString]="countryToString">
|
|
283
306
|
<hlm-select-trigger size="sm" ariaDescribedby="country-hint">
|
|
284
307
|
<hlm-select-value placeholder="Country" />
|
|
285
308
|
</hlm-select-trigger>
|
|
286
309
|
<hlm-select-content *hlmSelectPortal>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
310
|
+
@for (option of countries; track option.value) {
|
|
311
|
+
<hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
|
|
312
|
+
}
|
|
290
313
|
</hlm-select-content>
|
|
291
314
|
</div>
|
|
292
315
|
<p id="country-hint" class="tw:text-muted-foreground tw:text-xs">Used for shipping.</p>
|
|
@@ -298,6 +321,13 @@ export class FormSelectComponent {
|
|
|
298
321
|
readonly form = new FormGroup({
|
|
299
322
|
country: new FormControl<string | null>(null, Validators.required),
|
|
300
323
|
});
|
|
324
|
+
readonly countries = [
|
|
325
|
+
{ value: 'de', label: 'Germany' },
|
|
326
|
+
{ value: 'fr', label: 'France' },
|
|
327
|
+
{ value: 'es', label: 'Spain' },
|
|
328
|
+
];
|
|
329
|
+
readonly countryToString = (value: string | null) =>
|
|
330
|
+
this.countries.find((option) => option.value === value)?.label ?? value ?? '';
|
|
301
331
|
|
|
302
332
|
submit(): void {
|
|
303
333
|
this.form.markAllAsTouched();
|
|
@@ -318,16 +348,17 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
318
348
|
template: `
|
|
319
349
|
<div hlmSelectMultiple [(value)]="toppings">
|
|
320
350
|
<hlm-select-trigger>
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
351
|
+
<hlm-select-placeholder>Pick toppings</hlm-select-placeholder>
|
|
352
|
+
<ng-template hlmSelectValues let-values>
|
|
353
|
+
<hlm-select-values-content>
|
|
354
|
+
{{ formatToppings(values) }}
|
|
355
|
+
</hlm-select-values-content>
|
|
356
|
+
</ng-template>
|
|
326
357
|
</hlm-select-trigger>
|
|
327
358
|
<hlm-select-content *hlmSelectPortal>
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
359
|
+
@for (option of options; track option.value) {
|
|
360
|
+
<hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
|
|
361
|
+
}
|
|
331
362
|
</hlm-select-content>
|
|
332
363
|
</div>
|
|
333
364
|
<p>{{ toppings().length }} selected</p>
|
|
@@ -335,6 +366,17 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
335
366
|
})
|
|
336
367
|
export class MultiSelectComponent {
|
|
337
368
|
readonly toppings = signal<string[]>([]);
|
|
369
|
+
readonly options = [
|
|
370
|
+
{ value: 'cheese', label: 'Extra cheese' },
|
|
371
|
+
{ value: 'bacon', label: 'Bacon' },
|
|
372
|
+
{ value: 'mushrooms', label: 'Mushrooms' },
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
// The values template renders raw ids — map them to labels explicitly.
|
|
376
|
+
formatToppings(values: string[]): string {
|
|
377
|
+
const labels = new Map(this.options.map((option) => [option.value, option.label]));
|
|
378
|
+
return values.map((value) => labels.get(value) ?? value).join(', ');
|
|
379
|
+
}
|
|
338
380
|
}
|
|
339
381
|
```
|
|
340
382
|
|
|
@@ -400,11 +442,12 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
400
442
|
(closed)="onClosed()"
|
|
401
443
|
>
|
|
402
444
|
<hlm-select-trigger>
|
|
403
|
-
<span hlmSelectValueTemplate>⭐ {{
|
|
445
|
+
<span hlmSelectValueTemplate>⭐ {{ planLabel() }}</span>
|
|
404
446
|
</hlm-select-trigger>
|
|
405
447
|
<hlm-select-content *hlmSelectPortal="let ctx">
|
|
406
|
-
|
|
407
|
-
|
|
448
|
+
@for (option of plans; track option.value) {
|
|
449
|
+
<hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
|
|
450
|
+
}
|
|
408
451
|
</hlm-select-content>
|
|
409
452
|
</div>
|
|
410
453
|
`,
|
|
@@ -412,6 +455,15 @@ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
|
412
455
|
export class AdvancedSelectComponent {
|
|
413
456
|
readonly plan = signal<string | null>(null);
|
|
414
457
|
readonly open = signal(false);
|
|
458
|
+
readonly plans = [
|
|
459
|
+
{ value: 'hobby', label: 'Hobby' },
|
|
460
|
+
{ value: 'pro', label: 'Pro' },
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
// Custom templates bypass itemToString — resolve the label explicitly.
|
|
464
|
+
planLabel(): string {
|
|
465
|
+
return this.plans.find((option) => option.value === this.plan())?.label ?? 'Pick a plan';
|
|
466
|
+
}
|
|
415
467
|
|
|
416
468
|
onClosed(): void {
|
|
417
469
|
console.log('panel closed');
|