@egose/shadcn-theme-ng-tw 0.6.0 → 0.6.1
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/README.md +119 -1
- package/autocomplete/fesm2022/autocomplete.mjs +14 -4
- package/autocomplete/types/autocomplete.d.ts +4 -1
- package/checkbox/README.md +6 -2
- package/checkbox/fesm2022/checkbox.mjs +15 -9
- package/checkbox/types/checkbox.d.ts +6 -4
- package/combobox/fesm2022/combobox.mjs +22 -6
- package/combobox/types/combobox.d.ts +8 -2
- package/data-table/README.md +10 -0
- package/data-table/fesm2022/data-table.mjs +48 -12
- package/data-table/types/data-table.d.ts +16 -4
- package/date-picker/README.md +41 -9
- package/date-picker/fesm2022/date-picker.mjs +265 -59
- package/date-picker/types/date-picker.d.ts +58 -27
- package/form-autocomplete/README.md +4 -2
- package/form-autocomplete/fesm2022/form-autocomplete.mjs +33 -8
- package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
- package/form-combobox/README.md +4 -2
- package/form-combobox/fesm2022/form-combobox.mjs +65 -14
- package/form-combobox/types/form-combobox.d.ts +10 -3
- package/form-input-otp/README.md +7 -5
- package/form-input-otp/fesm2022/form-input-otp.mjs +28 -14
- package/form-input-otp/types/form-input-otp.d.ts +9 -3
- package/form-searchable-multiselect/README.md +15 -1
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +23 -3
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
- package/form-slider/README.md +3 -1
- package/form-slider/fesm2022/form-slider.mjs +21 -6
- package/form-slider/types/form-slider.d.ts +9 -3
- package/form-toggle/README.md +22 -18
- package/form-toggle/fesm2022/form-toggle.mjs +25 -8
- package/form-toggle/types/form-toggle.d.ts +6 -2
- package/input-otp/README.md +33 -9
- package/input-otp/fesm2022/input-otp.mjs +127 -11
- package/input-otp/types/input-otp.d.ts +24 -4
- package/package.json +1 -1
- package/pagination/README.md +21 -12
- package/pagination/fesm2022/pagination.mjs +83 -75
- package/pagination/types/pagination.d.ts +21 -8
- package/phone-input/README.md +16 -0
- package/phone-input/fesm2022/phone-input.mjs +41 -3
- package/phone-input/types/phone-input.d.ts +5 -2
- package/searchable-multiselect/README.md +62 -40
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +97 -36
- package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
- package/slider/fesm2022/slider.mjs +15 -5
- package/slider/types/slider.d.ts +6 -2
- package/stepper/README.md +39 -19
- package/stepper/fesm2022/stepper.mjs +71 -17
- package/stepper/types/stepper.d.ts +12 -1
package/README.md
CHANGED
|
@@ -167,9 +167,46 @@ export class DemoComponent {
|
|
|
167
167
|
}
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Searchable multiselect
|
|
171
|
+
|
|
172
|
+
`EgSearchableMultiselect` provides local, case-insensitive label search, named removable chips, and a `string[]` CVA. Import it directly from its public subpath:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { Component, signal } from '@angular/core';
|
|
176
|
+
import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
177
|
+
|
|
178
|
+
@Component({
|
|
179
|
+
selector: 'app-assignees',
|
|
180
|
+
imports: [EgSearchableMultiselect],
|
|
181
|
+
template: `
|
|
182
|
+
<eg-searchable-multiselect
|
|
183
|
+
[options]="people"
|
|
184
|
+
[(value)]="selected"
|
|
185
|
+
ariaLabel="Assignees"
|
|
186
|
+
searchLabel="Find people"
|
|
187
|
+
searchPlaceholder="Type a name…"
|
|
188
|
+
emptyMessage="No people match"
|
|
189
|
+
/>
|
|
190
|
+
`,
|
|
191
|
+
})
|
|
192
|
+
export class AssigneesComponent {
|
|
193
|
+
readonly people = [
|
|
194
|
+
{ value: 'ada', label: 'Ada Lovelace' },
|
|
195
|
+
{ value: 'grace', label: 'Grace Hopper' },
|
|
196
|
+
];
|
|
197
|
+
readonly selected = signal<string[]>(['ada']);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Use `@egose/shadcn-theme-ng-tw/searchable-multiselect` for the prefixed variant. Pass the complete options list: the trimmed query filters labels in source order without changing selected IDs or chip labels. Clearing the query restores choices. Async option replacements update labels and results; unresolved selections retain their IDs. Search is local to supplied options, with no remote requests or virtualization. The query persists across closing/reopening and external value resets.
|
|
202
|
+
|
|
203
|
+
The native search is visibly labeled, empty results use a polite status region, and chip buttons are named `Remove <label>` (raw ID when unresolved). Customize names with `[removeLabel]="formatter"`, where `formatter: (option: SelectOption) => string` is pure and `SelectOption` comes from the same subpath. Search edits neither emit selection changes nor touch forms. Input/wrapper/form disabled flags block search and selection edits. With default popover focus settings, opening focuses search; Tab reaches checkbox buttons, Space/Enter toggles them, and Escape closes and restores trigger focus. Enter in search does not submit a form.
|
|
204
|
+
|
|
205
|
+
For reactive or template-driven forms, import `ReactiveFormsModule` or `FormsModule` and bind `formControl`/`formControlName` or `ngModel` instead of `value`. CVA writes own selection after the first write; null/empty arrays clear it without user emissions. Standalone new `[value]` arrays replace local edits; `[(value)]` synchronizes the parent. Supply unique IDs and immutable array/option updates. `EgFormSearchableMultiselect` from `@egose/shadcn-theme-ng/form-searchable-multiselect` adds the form label/hint/error layout and forwards `searchLabel`, `searchPlaceholder`, `emptyMessage`, and `removeLabel`.
|
|
206
|
+
|
|
170
207
|
## Date picker values
|
|
171
208
|
|
|
172
|
-
`hlm-date-picker` emits a native JS `Date` (or `null` when cleared) via the `dateChange` output. Read it in the
|
|
209
|
+
By default, `hlm-date-picker` emits a native JS `Date` (or `null` when cleared) via the `dateChange` output. Read it in the
|
|
173
210
|
controller with `(dateChange)`, a template ref (`picker.value()`), or a form binding (`ngModel`/`formControlName`,
|
|
174
211
|
the picker is a `ControlValueAccessor`):
|
|
175
212
|
|
|
@@ -216,6 +253,87 @@ export class MyComponent {}
|
|
|
216
253
|
then always sees local-midnight dates. Related: the picker stays open after selection by default; add the
|
|
217
254
|
`autoCloseOnSelect` attribute (or set it in the same config) to close it on select.
|
|
218
255
|
|
|
256
|
+
### Typed commits and constraints (single, range, multi)
|
|
257
|
+
|
|
258
|
+
Text commits on blur or Enter. Both the parsed value and the configured transform result must satisfy
|
|
259
|
+
`min`/`max`, inclusive of the adapter's entire boundary days. Comparisons and validity (`getTime` must be
|
|
260
|
+
finite) use Spartan's configured date adapter; custom date types also need matching parse/format/transform
|
|
261
|
+
config callbacks. A range's transformed endpoints must be ordered; a custom transform may sort them.
|
|
262
|
+
The default range parser accepts a single date as a same-day range, but rejects an explicitly unparseable
|
|
263
|
+
second endpoint.
|
|
264
|
+
|
|
265
|
+
Multi-date commits cannot exceed `maxSelection`. Consistent with calendar selection, `minSelection` is a
|
|
266
|
+
**deselection floor**: reducing the current count below it is rejected, but growing a selection from empty
|
|
267
|
+
is allowed. Counts apply to the supplied arrays before and after transformation. Supply unique dates.
|
|
268
|
+
At the calendar's maximum, its proposed reset to one new date is accepted only if the floor permits it.
|
|
269
|
+
|
|
270
|
+
Rejected text remains editable across blur, Enter and refocus, with native `aria-invalid="true"` and invalid
|
|
271
|
+
styling. It does **not** change the committed value or emit `dateChange`/CVA `onChange`; the interaction
|
|
272
|
+
marks the control touched. `inputInvalid()` on the text-input component exposes this local state. It does
|
|
273
|
+
not add Angular validation errors: validators still inspect the last committed form value. Use
|
|
274
|
+
`ariaDescribedby` for application-specific guidance, and include `inputInvalid()` in submission eligibility
|
|
275
|
+
if an unresolved draft should block submission. Existing `forceInvalid` is combined with this state.
|
|
276
|
+
|
|
277
|
+
A successful text/calendar commit clears the local invalid state. Empty text or the clear button explicitly
|
|
278
|
+
clears (`null` for single/range, `[]` for multi), bypassing transforms and the selection floor. Whitespace is
|
|
279
|
+
passed to the configured parser. Enter retains the edit format so later blur can parse it; blur uses the
|
|
280
|
+
display format. Rejected calendar selections restore the prior selection without emitting a replacement.
|
|
281
|
+
|
|
282
|
+
`updateDate(value)` is the user-commit boundary and returns `false` on rejection/disabled, `true` on success.
|
|
283
|
+
Programmatic CVA `writeValue` (including form `setValue`/reset) instead applies the transform **without**
|
|
284
|
+
user-constraint enforcement or user emissions, and replaces rejected text even for repeated values/null.
|
|
285
|
+
Standalone `[date]` updates retain their direct, untransformed input contract; they are not user commits.
|
|
286
|
+
`reset()` remains an explicit programmatic clear that emits. Parse/transform callbacks should be pure and
|
|
287
|
+
return values compatible with the configured adapter; parsing failure is represented by `null`.
|
|
288
|
+
|
|
289
|
+
## Validation descriptions on composed fields
|
|
290
|
+
|
|
291
|
+
`EgFormAutocomplete`, `EgFormInputOtp`, `EgFormSlider`, and `EgFormCombobox` connect the
|
|
292
|
+
displayed hint or validation error to their actual native input, slider thumbs, or combobox
|
|
293
|
+
trigger/search input. Errors replace hints when invalid and touched, dirty, or submitted;
|
|
294
|
+
correction and form reset restore the appropriate hint. `HlmError` is styled text, not a live region.
|
|
295
|
+
|
|
296
|
+
Use `[aria-describedby]="'external-help-id'"` on these wrappers to add external descriptions.
|
|
297
|
+
The wrapper normalizes/deduplicates IDs and appends its current message ID. Keep consumer-owned
|
|
298
|
+
description elements mounted, and reserve `<effectiveId>-error` / `-hint` for wrapper messages.
|
|
299
|
+
`[attr.aria-describedby]` only targets the custom host and is not this forwarding contract.
|
|
300
|
+
|
|
301
|
+
For primitive compositions, `HlmAutocompleteInput`, `HlmComboboxInput`, `HlmComboboxTrigger`,
|
|
302
|
+
`HlmSlider`, and `HlmInputOtpControl` accept `[aria-describedby]` and merge it with descriptions
|
|
303
|
+
registered by the enclosing Spartan field. Import `HlmInputOtpControl` (selector `hlm-input-otp`)
|
|
304
|
+
or `HlmInputOtpImports` from `@egose/shadcn-theme-ng/input-otp`; it retains the brain OTP CVA and
|
|
305
|
+
editing behavior while adding native-input descriptions, required and invalid state. Existing
|
|
306
|
+
`brn-input-otp hlmInputOtp` compositions remain available.
|
|
307
|
+
|
|
308
|
+
Single combobox mode labels its trigger with `<effectiveId>` and uses `<effectiveId>-search`
|
|
309
|
+
for its separately labeled popup search. Required/invalid selection state stays on the trigger;
|
|
310
|
+
the popup search filters options. Slider thumbs retain their labels and invalid state; the slider
|
|
311
|
+
role has no `aria-required`. Configure Angular validators independently of wrapper `required`.
|
|
312
|
+
Verification is rendered browser DOM/focus coverage, not screen-reader testing.
|
|
313
|
+
|
|
314
|
+
## Numbered pagination
|
|
315
|
+
|
|
316
|
+
Import `HlmNumberedPagination` or `HlmNumberedPaginationQueryParams` from
|
|
317
|
+
`@egose/shadcn-theme-ng/pagination` (or the `-tw` package), then bind
|
|
318
|
+
`[(currentPage)]`, `[(itemsPerPage)]` and `[totalItems]`.
|
|
319
|
+
|
|
320
|
+
Both pagers floor/clamp the current page when totals, size or page inputs change.
|
|
321
|
+
Empty/non-finite/non-positive totals or sizes produce one page with no previous/next;
|
|
322
|
+
invalid sizes are not rewritten. Non-finite pages become 1. A correction emits
|
|
323
|
+
`currentPageChange` once. Positive fractional totals/sizes use ceil division, with
|
|
324
|
+
page counts capped at `Number.MAX_SAFE_INTEGER`. `maxSize` budgets window entries,
|
|
325
|
+
including ellipses: finite positive values are floored/clamped to 1–100, otherwise 7.
|
|
326
|
+
Ranges below 5 show a contiguous active-page window. Helpers `createPageArray` and
|
|
327
|
+
`outOfBoundCorrection` use the same policy without side effects or allocation based
|
|
328
|
+
on an unbounded page count.
|
|
329
|
+
|
|
330
|
+
The query-params pager builds bounded `?page=` links using `queryParamsHandling="merge"`,
|
|
331
|
+
preserving unrelated query parameters. The parent owns reading route changes and
|
|
332
|
+
any URL synchronization: model corrections and size changes do not navigate. Handle
|
|
333
|
+
`currentPageChange` in the parent if corrected URLs should be replaced, merging other
|
|
334
|
+
parameters. Keep the previous total during loading if temporary zero results should
|
|
335
|
+
not reset the page.
|
|
336
|
+
|
|
219
337
|
## Working example
|
|
220
338
|
|
|
221
339
|
A complete Angular consumer app lives at [`@examples/standard`](https://github.com/egose/shadcn-theme/tree/main/packages/angular/@examples/standard) — see its `src/app/...` for real usage of `@egose/shadcn-theme-ng/button`, `.../layout-simple`, `.../form-text-input`, `.../autocomplete`, `.../select`, `.../form-checkbox`, `.../sheet`, etc.
|
|
@@ -9,6 +9,7 @@ import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
|
9
9
|
import { lucideX, lucideSearch, lucideCheck } from '@ng-icons/lucide';
|
|
10
10
|
import * as i2$1 from '@egose/shadcn-theme-ng-tw/input-group';
|
|
11
11
|
import { HlmInputGroup, HlmInputGroupImports } from '@egose/shadcn-theme-ng-tw/input-group';
|
|
12
|
+
import { BrnFieldControlDescribedBy } from '@spartan-ng/brain/field';
|
|
12
13
|
|
|
13
14
|
class HlmAutocomplete {
|
|
14
15
|
constructor() {
|
|
@@ -103,6 +104,9 @@ class HlmAutocompleteInput {
|
|
|
103
104
|
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
104
105
|
placeholder = input('', /* @ts-ignore */
|
|
105
106
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
107
|
+
/** Native input description IDs, merged with enclosing Spartan field descriptions. */
|
|
108
|
+
ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
109
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
106
110
|
showSearch = input(true, { ...(ngDevMode ? { debugName: "showSearch" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
107
111
|
showClear = input(false, { ...(ngDevMode ? { debugName: "showClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
108
112
|
/** Forces the invalid state visually, regardless of form control state. */
|
|
@@ -114,13 +118,16 @@ class HlmAutocompleteInput {
|
|
|
114
118
|
classes(() => 'tw:w-auto');
|
|
115
119
|
}
|
|
116
120
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmAutocompleteInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
117
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmAutocompleteInput, isStandalone: true, selector: "hlm-autocomplete-input", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null }, ariaInvalidOverride: { classPropertyName: "ariaInvalidOverride", publicName: "aria-invalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideSearch, lucideX })], hostDirectives: [{ directive: i1.BrnAutocompleteAnchor }, { directive: i2$1.HlmInputGroup }], ngImport: i0, template: `
|
|
121
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmAutocompleteInput, isStandalone: true, selector: "hlm-autocomplete-input", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null }, ariaInvalidOverride: { classPropertyName: "ariaInvalidOverride", publicName: "aria-invalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideSearch, lucideX })], hostDirectives: [{ directive: i1.BrnAutocompleteAnchor }, { directive: i2$1.HlmInputGroup }], ngImport: i0, template: `
|
|
118
122
|
<input
|
|
119
123
|
brnAutocompleteInput
|
|
120
124
|
#autocompleteInput="brnAutocompleteInput"
|
|
121
125
|
hlmInputGroupInput
|
|
122
126
|
[id]="inputId()"
|
|
123
127
|
[placeholder]="placeholder()"
|
|
128
|
+
brnFieldControlDescribedBy
|
|
129
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
130
|
+
[attr.aria-required]="required() || null"
|
|
124
131
|
[aria-invalid]="ariaInvalidOverride()"
|
|
125
132
|
[forceInvalid]="forceInvalid()"
|
|
126
133
|
/>
|
|
@@ -146,13 +153,13 @@ class HlmAutocompleteInput {
|
|
|
146
153
|
</hlm-input-group-addon>
|
|
147
154
|
}
|
|
148
155
|
<ng-content />
|
|
149
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: i2$1.HlmInputGroupAddon, selector: "[hlmInputGroupAddon],hlm-input-group-addon", inputs: ["align"] }, { kind: "directive", type: i2$1.HlmInputGroupButton, selector: "button[hlmInputGroupButton]", inputs: ["size", "type", "class"] }, { kind: "directive", type: i2$1.HlmInputGroupInput, selector: "input[hlmInputGroupInput]" }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: BrnAutocompleteClear, selector: "[brnAutocompleteClear]" }, { kind: "directive", type: BrnAutocompleteInput, selector: "input[brnAutocompleteInput]", inputs: ["id", "aria-invalid", "forceInvalid"], exportAs: ["brnAutocompleteInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
156
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: i2$1.HlmInputGroupAddon, selector: "[hlmInputGroupAddon],hlm-input-group-addon", inputs: ["align"] }, { kind: "directive", type: i2$1.HlmInputGroupButton, selector: "button[hlmInputGroupButton]", inputs: ["size", "type", "class"] }, { kind: "directive", type: i2$1.HlmInputGroupInput, selector: "input[hlmInputGroupInput]" }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: BrnAutocompleteClear, selector: "[brnAutocompleteClear]" }, { kind: "directive", type: BrnAutocompleteInput, selector: "input[brnAutocompleteInput]", inputs: ["id", "aria-invalid", "forceInvalid"], exportAs: ["brnAutocompleteInput"] }, { kind: "directive", type: BrnFieldControlDescribedBy, selector: "[brnFieldControlDescribedBy]", inputs: ["aria-describedby"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
150
157
|
}
|
|
151
158
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmAutocompleteInput, decorators: [{
|
|
152
159
|
type: Component,
|
|
153
160
|
args: [{
|
|
154
161
|
selector: 'hlm-autocomplete-input',
|
|
155
|
-
imports: [HlmInputGroupImports, NgIcon, BrnAutocompleteClear, BrnAutocompleteInput],
|
|
162
|
+
imports: [HlmInputGroupImports, NgIcon, BrnAutocompleteClear, BrnAutocompleteInput, BrnFieldControlDescribedBy],
|
|
156
163
|
providers: [provideIcons({ lucideSearch, lucideX })],
|
|
157
164
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
158
165
|
hostDirectives: [BrnAutocompleteAnchor, HlmInputGroup],
|
|
@@ -163,6 +170,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
163
170
|
hlmInputGroupInput
|
|
164
171
|
[id]="inputId()"
|
|
165
172
|
[placeholder]="placeholder()"
|
|
173
|
+
brnFieldControlDescribedBy
|
|
174
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
175
|
+
[attr.aria-required]="required() || null"
|
|
166
176
|
[aria-invalid]="ariaInvalidOverride()"
|
|
167
177
|
[forceInvalid]="forceInvalid()"
|
|
168
178
|
/>
|
|
@@ -190,7 +200,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
190
200
|
<ng-content />
|
|
191
201
|
`,
|
|
192
202
|
}]
|
|
193
|
-
}], ctorParameters: () => [], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }], ariaInvalidOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-invalid", required: false }] }] } });
|
|
203
|
+
}], ctorParameters: () => [], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }], ariaInvalidOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-invalid", required: false }] }] } });
|
|
194
204
|
|
|
195
205
|
class HlmAutocompleteItem {
|
|
196
206
|
_brnAutocompleteItem = inject(BrnAutocompleteItem);
|
|
@@ -32,6 +32,9 @@ declare class HlmAutocompleteInput {
|
|
|
32
32
|
private static _id;
|
|
33
33
|
readonly inputId: i0.InputSignal<string>;
|
|
34
34
|
readonly placeholder: i0.InputSignal<string>;
|
|
35
|
+
/** Native input description IDs, merged with enclosing Spartan field descriptions. */
|
|
36
|
+
readonly ariaDescribedBy: i0.InputSignal<string | null>;
|
|
37
|
+
readonly required: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
35
38
|
readonly showSearch: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
36
39
|
readonly showClear: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
37
40
|
/** Forces the invalid state visually, regardless of form control state. */
|
|
@@ -40,7 +43,7 @@ declare class HlmAutocompleteInput {
|
|
|
40
43
|
readonly ariaInvalidOverride: i0.InputSignalWithTransform<boolean | undefined, BooleanInput>;
|
|
41
44
|
constructor();
|
|
42
45
|
static ɵfac: i0.ɵɵFactoryDeclaration<HlmAutocompleteInput, never>;
|
|
43
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<HlmAutocompleteInput, "hlm-autocomplete-input", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; "ariaInvalidOverride": { "alias": "aria-invalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, [{ directive: typeof i1.BrnAutocompleteAnchor; inputs: {}; outputs: {}; }, { directive: typeof i2$1.HlmInputGroup; inputs: {}; outputs: {}; }]>;
|
|
46
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<HlmAutocompleteInput, "hlm-autocomplete-input", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; "ariaInvalidOverride": { "alias": "aria-invalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, [{ directive: typeof i1.BrnAutocompleteAnchor; inputs: {}; outputs: {}; }, { directive: typeof i2$1.HlmInputGroup; inputs: {}; outputs: {}; }]>;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
declare class HlmAutocompleteItem {
|
package/checkbox/README.md
CHANGED
|
@@ -75,6 +75,8 @@ export class DemoModule {}
|
|
|
75
75
|
|
|
76
76
|
Real selector: `hlm-checkbox` (element only). The host renders `class="contents peer"` so sibling label/description selectors (`peer-*`) keep working; `id` / `aria-*` attributes are nulled on the host and forwarded to the inner `brn-checkbox` instead. Set a visible label with a native `<label for>` — the component does not render label text itself.
|
|
77
77
|
|
|
78
|
+
Omitted/null IDs use `HlmFormIdGenerator` from `@egose/shadcn-theme-ng/form-field`: `hlm-checkbox-${APP_ID}-${sequence}`. The fallback is allocated once per instance from the application-scoped service, so separate applications restart their sequence and matching server/client `APP_ID` plus component creation order reproduce IDs. Explicit IDs are preserved on the focusable button; reverting to null restores that instance's fallback. Supply distinct `APP_ID` values for multiple applications on one page and unique explicit IDs. Checkboxes nested inside separate native labels each receive their own generated label association through Spartan; existing label IDs are retained.
|
|
79
|
+
|
|
78
80
|
## API reference
|
|
79
81
|
|
|
80
82
|
### `HlmCheckbox` (`hlm-checkbox`)
|
|
@@ -83,7 +85,7 @@ Real selector: `hlm-checkbox` (element only). The host renders `class="contents
|
|
|
83
85
|
| -------------------------------------- | ----------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
86
|
| `checked` | `model<boolean \| 'indeterminate'>` | `false` | Checked state. Two-way bindable (`[(checked)]`). `'indeterminate'` renders the checked icon; clicking it resolves to `true`. |
|
|
85
87
|
| `class` (`userClass`) | `ClassValue` | `''` | Extra classes merged into the box. |
|
|
86
|
-
| `id` | `string \| null` | `null` |
|
|
88
|
+
| `id` | `string \| null` | `null` | Explicit native button ID, or application-scoped fallback when null. Pair explicit IDs with `<label for>`. |
|
|
87
89
|
| `aria-label` (`ariaLabel`) | `string \| null` | `null` | Forwarded aria-label. |
|
|
88
90
|
| `aria-labelledby` (`ariaLabelledby`) | `string \| null` | `null` | Forwarded aria-labelledby. |
|
|
89
91
|
| `aria-describedby` (`ariaDescribedby`) | `string \| null` | `null` | Forwarded aria-describedby. |
|
|
@@ -102,6 +104,8 @@ Real selector: `hlm-checkbox` (element only). The host renders `class="contents
|
|
|
102
104
|
|
|
103
105
|
ControlValueAccessor: `writeValue(value)`, `registerOnChange`, `registerOnTouched`, `setDisabledState(isDisabled)` (writes `formDisabled`, hence `disabledState`). `HLM_CHECKBOX_VALUE_ACCESSOR` (`NG_VALUE_ACCESSOR`, `forwardRef(HlmCheckbox)`, `multi: true`) is provided on the component.
|
|
104
106
|
|
|
107
|
+
Both `[checked]` and CVA writes preserve `'indeterminate'`. The primitive forwards boolean checked and indeterminate separately to Spartan: the actual `button[role="checkbox"]` exposes `aria-checked="mixed"` and `data-state="indeterminate"`. An enabled click resolves mixed to `true`, then subsequent clicks toggle true/false; `changed` and CVA `onChange` emit the resulting boolean once. Thus clicking a partially selected table header selects its displayed page. Programmatic CVA writes do not call `changed`/`onChange` or touch the control; null resets to false. Any active input, wrapper, or form disabled lock blocks user toggles without discarding mixed state.
|
|
108
|
+
|
|
105
109
|
## Examples
|
|
106
110
|
|
|
107
111
|
### 1. Basic with label
|
|
@@ -307,7 +311,7 @@ export class ChangedCheckboxComponent {
|
|
|
307
311
|
- The component renders a native-checkbox-equivalent `brn-checkbox` with `aria-label`/`labelledby`/`describedby` forwarding — always provide one: visible `<label for>` preferred, `aria-label` for icon-only rows.
|
|
308
312
|
- `disabledState` adds `data-disabled`, `cursor-not-allowed`, and `opacity-50`; disabled boxes are skipped correctly because clicks early-return.
|
|
309
313
|
- The check glyph is decorative (`lucideCheck` icon, no text) — state is exposed via `data-[state]` / native semantics, not the icon.
|
|
310
|
-
- Tri-state: `'indeterminate'`
|
|
314
|
+
- Tri-state: `'indeterminate'` exposes `aria-checked="mixed"` on the focusable checkbox button. State and nested-label associations are covered by rendered browser DOM tests; screen-reader announcement behavior is not asserted by those tests.
|
|
311
315
|
|
|
312
316
|
## Theming / CSS variables
|
|
313
317
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { forwardRef, input, computed, model, booleanAttribute, signal, output, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
2
|
+
import { forwardRef, input, computed, inject, model, booleanAttribute, signal, output, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
3
3
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
4
|
import { provideIcons, NgIcon } from '@ng-icons/core';
|
|
5
5
|
import { lucideCheck } from '@ng-icons/lucide';
|
|
6
6
|
import { BrnCheckbox } from '@spartan-ng/brain/checkbox';
|
|
7
|
+
import { HlmFormIdGenerator } from '@egose/shadcn-theme-ng-tw/form-field';
|
|
7
8
|
import { HlmIcon } from '@egose/shadcn-theme-ng-tw/icon';
|
|
8
9
|
import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
|
|
9
10
|
|
|
@@ -16,16 +17,19 @@ class HlmCheckbox {
|
|
|
16
17
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
17
18
|
_computedClass = computed(() => hlm('tw:border-input tw:dark:bg-input/30 tw:data-[state=checked]:bg-primary tw:data-[state=checked]:text-primary-foreground tw:dark:data-[state=checked]:bg-primary tw:data-[state=checked]:border-primary tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:ring-destructive/40 tw:aria-invalid:border-destructive tw:shadow-xs tw:peer tw:size-4 tw:shrink-0 tw:cursor-default tw:rounded-[4px] tw:border tw:outline-none tw:transition-shadow tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50', this.userClass(), this.disabledState() ? 'tw:cursor-not-allowed tw:opacity-50' : ''), /* @ts-ignore */
|
|
18
19
|
...(ngDevMode ? [{ debugName: "_computedClass" }] : /* istanbul ignore next */ []));
|
|
19
|
-
/**
|
|
20
|
+
/** Native checkbox ID. Null uses a stable application-scoped fallback; explicit IDs are preserved. */
|
|
20
21
|
id = input(null, /* @ts-ignore */
|
|
21
22
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
23
|
+
_generatedId = inject(HlmFormIdGenerator).generate('hlm-checkbox');
|
|
24
|
+
_id = computed(() => this.id() ?? this._generatedId, /* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [{ debugName: "_id" }] : /* istanbul ignore next */ []));
|
|
22
26
|
/** Used to set the aria-label attribute on the underlying brn element. */
|
|
23
27
|
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
24
28
|
/** Used to set the aria-labelledby attribute on the underlying brn element. */
|
|
25
29
|
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
26
30
|
/** Used to set the aria-describedby attribute on the underlying brn element. */
|
|
27
31
|
ariaDescribedby = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedby" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
28
|
-
/**
|
|
32
|
+
/** Tri-state model. Indeterminate exposes native aria-checked="mixed"; clicking it selects (true). */
|
|
29
33
|
checked = model(false, /* @ts-ignore */
|
|
30
34
|
...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
31
35
|
/** The name attribute of the checkbox. */
|
|
@@ -51,9 +55,9 @@ class HlmCheckbox {
|
|
|
51
55
|
this._onChange?.(checked);
|
|
52
56
|
this.changed.emit(checked);
|
|
53
57
|
}
|
|
54
|
-
/**
|
|
58
|
+
/** Programmatic writes preserve indeterminate; null resets to false without changed/onChange notifications. */
|
|
55
59
|
writeValue(value) {
|
|
56
|
-
this.checked.set(!!value);
|
|
60
|
+
this.checked.set(value === 'indeterminate' ? value : !!value);
|
|
57
61
|
}
|
|
58
62
|
registerOnChange(fn) {
|
|
59
63
|
this._onChange = fn;
|
|
@@ -67,10 +71,11 @@ class HlmCheckbox {
|
|
|
67
71
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
68
72
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmCheckbox, isStandalone: true, selector: "hlm-checkbox", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, wrapperDisabled: { classPropertyName: "wrapperDisabled", publicName: "wrapperDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", changed: "changed" }, host: { properties: { "attr.id": "null", "attr.aria-label": "null", "attr.aria-labelledby": "null", "attr.aria-describedby": "null", "attr.data-disabled": "disabledState() ? \"\" : null" }, classAttribute: "contents peer" }, providers: [HLM_CHECKBOX_VALUE_ACCESSOR], ngImport: i0, template: `
|
|
69
73
|
<brn-checkbox
|
|
70
|
-
[id]="
|
|
74
|
+
[id]="_id()"
|
|
71
75
|
[name]="name()"
|
|
72
76
|
[class]="_computedClass()"
|
|
73
|
-
[checked]="checked()"
|
|
77
|
+
[checked]="checked() === true"
|
|
78
|
+
[indeterminate]="checked() === 'indeterminate'"
|
|
74
79
|
[disabled]="disabledState()"
|
|
75
80
|
[required]="required()"
|
|
76
81
|
[aria-label]="ariaLabel()"
|
|
@@ -94,10 +99,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
94
99
|
imports: [BrnCheckbox, NgIcon, HlmIcon],
|
|
95
100
|
template: `
|
|
96
101
|
<brn-checkbox
|
|
97
|
-
[id]="
|
|
102
|
+
[id]="_id()"
|
|
98
103
|
[name]="name()"
|
|
99
104
|
[class]="_computedClass()"
|
|
100
|
-
[checked]="checked()"
|
|
105
|
+
[checked]="checked() === true"
|
|
106
|
+
[indeterminate]="checked() === 'indeterminate'"
|
|
101
107
|
[disabled]="disabledState()"
|
|
102
108
|
[required]="required()"
|
|
103
109
|
[aria-label]="ariaLabel()"
|
|
@@ -12,15 +12,17 @@ declare const HLM_CHECKBOX_VALUE_ACCESSOR: {
|
|
|
12
12
|
declare class HlmCheckbox implements ControlValueAccessor {
|
|
13
13
|
readonly userClass: _angular_core.InputSignal<ClassValue>;
|
|
14
14
|
protected readonly _computedClass: _angular_core.Signal<string>;
|
|
15
|
-
/**
|
|
15
|
+
/** Native checkbox ID. Null uses a stable application-scoped fallback; explicit IDs are preserved. */
|
|
16
16
|
readonly id: _angular_core.InputSignal<string | null>;
|
|
17
|
+
private readonly _generatedId;
|
|
18
|
+
protected readonly _id: _angular_core.Signal<string>;
|
|
17
19
|
/** Used to set the aria-label attribute on the underlying brn element. */
|
|
18
20
|
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
19
21
|
/** Used to set the aria-labelledby attribute on the underlying brn element. */
|
|
20
22
|
readonly ariaLabelledby: _angular_core.InputSignal<string | null>;
|
|
21
23
|
/** Used to set the aria-describedby attribute on the underlying brn element. */
|
|
22
24
|
readonly ariaDescribedby: _angular_core.InputSignal<string | null>;
|
|
23
|
-
/**
|
|
25
|
+
/** Tri-state model. Indeterminate exposes native aria-checked="mixed"; clicking it selects (true). */
|
|
24
26
|
readonly checked: _angular_core.ModelSignal<CheckboxValue>;
|
|
25
27
|
/** The name attribute of the checkbox. */
|
|
26
28
|
readonly name: _angular_core.InputSignal<string | null>;
|
|
@@ -36,8 +38,8 @@ declare class HlmCheckbox implements ControlValueAccessor {
|
|
|
36
38
|
protected _onChange?: ChangeFn<CheckboxValue>;
|
|
37
39
|
protected _onTouched?: TouchFn;
|
|
38
40
|
protected _handleCheckedChange(checked: boolean): void;
|
|
39
|
-
/**
|
|
40
|
-
writeValue(value: CheckboxValue): void;
|
|
41
|
+
/** Programmatic writes preserve indeterminate; null resets to false without changed/onChange notifications. */
|
|
42
|
+
writeValue(value: CheckboxValue | null): void;
|
|
41
43
|
registerOnChange(fn: ChangeFn<CheckboxValue>): void;
|
|
42
44
|
registerOnTouched(fn: TouchFn): void;
|
|
43
45
|
setDisabledState(isDisabled: boolean): void;
|
|
@@ -211,6 +211,9 @@ class HlmComboboxInput {
|
|
|
211
211
|
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
212
212
|
placeholder = input('', /* @ts-ignore */
|
|
213
213
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
214
|
+
/** Native input description IDs, merged with enclosing Spartan field descriptions. */
|
|
215
|
+
ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
216
|
+
ariaLabelledBy = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledBy" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
214
217
|
showTrigger = input(true, { ...(ngDevMode ? { debugName: "showTrigger" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
215
218
|
showClear = input(false, { ...(ngDevMode ? { debugName: "showClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
216
219
|
forceInvalid = input(false, { ...(ngDevMode ? { debugName: "forceInvalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
@@ -221,7 +224,7 @@ class HlmComboboxInput {
|
|
|
221
224
|
classes(() => 'tw:w-auto');
|
|
222
225
|
}
|
|
223
226
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmComboboxInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
224
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmComboboxInput, isStandalone: true, selector: "hlm-combobox-input", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showTrigger: { classPropertyName: "showTrigger", publicName: "showTrigger", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null }, ariaInvalidOverride: { classPropertyName: "ariaInvalidOverride", publicName: "aria-invalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideChevronDown, lucideX })], hostDirectives: [{ directive: i1.BrnComboboxAnchor }, { directive: i2$1.HlmInputGroup }], ngImport: i0, template: `
|
|
227
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmComboboxInput, isStandalone: true, selector: "hlm-combobox-input", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, showTrigger: { classPropertyName: "showTrigger", publicName: "showTrigger", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null }, ariaInvalidOverride: { classPropertyName: "ariaInvalidOverride", publicName: "aria-invalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideChevronDown, lucideX })], hostDirectives: [{ directive: i1.BrnComboboxAnchor }, { directive: i2$1.HlmInputGroup }], ngImport: i0, template: `
|
|
225
228
|
<input
|
|
226
229
|
brnComboboxInput
|
|
227
230
|
#comboboxInput="brnComboboxInput"
|
|
@@ -230,6 +233,9 @@ class HlmComboboxInput {
|
|
|
230
233
|
hlmInputGroupInput
|
|
231
234
|
[id]="inputId()"
|
|
232
235
|
[placeholder]="placeholder()"
|
|
236
|
+
brnFieldControlDescribedBy
|
|
237
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
238
|
+
[attr.aria-labelledby]="ariaLabelledBy()"
|
|
233
239
|
[forceInvalid]="forceInvalid()"
|
|
234
240
|
[aria-invalid]="ariaInvalidOverride()"
|
|
235
241
|
/>
|
|
@@ -264,13 +270,13 @@ class HlmComboboxInput {
|
|
|
264
270
|
</hlm-input-group-addon>
|
|
265
271
|
|
|
266
272
|
<ng-content />
|
|
267
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: i2$1.HlmInputGroupAddon, selector: "[hlmInputGroupAddon],hlm-input-group-addon", inputs: ["align"] }, { kind: "directive", type: i2$1.HlmInputGroupButton, selector: "button[hlmInputGroupButton]", inputs: ["size", "type", "class"] }, { kind: "directive", type: i2$1.HlmInputGroupInput, selector: "input[hlmInputGroupInput]" }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: i1.BrnComboboxClear, selector: "[brnComboboxClear]" }, { kind: "directive", type: i1.BrnComboboxInput, selector: "input[brnComboboxInput]", inputs: ["id", "aria-invalid", "forceInvalid"], exportAs: ["brnComboboxInput"] }, { kind: "directive", type: i1.BrnComboboxPopoverTrigger, selector: "[brnComboboxPopoverTrigger]", inputs: ["closeOnTriggerClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
273
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: i2$1.HlmInputGroupAddon, selector: "[hlmInputGroupAddon],hlm-input-group-addon", inputs: ["align"] }, { kind: "directive", type: i2$1.HlmInputGroupButton, selector: "button[hlmInputGroupButton]", inputs: ["size", "type", "class"] }, { kind: "directive", type: i2$1.HlmInputGroupInput, selector: "input[hlmInputGroupInput]" }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: i1.BrnComboboxClear, selector: "[brnComboboxClear]" }, { kind: "directive", type: i1.BrnComboboxInput, selector: "input[brnComboboxInput]", inputs: ["id", "aria-invalid", "forceInvalid"], exportAs: ["brnComboboxInput"] }, { kind: "directive", type: i1.BrnComboboxPopoverTrigger, selector: "[brnComboboxPopoverTrigger]", inputs: ["closeOnTriggerClick"] }, { kind: "directive", type: BrnFieldControlDescribedBy, selector: "[brnFieldControlDescribedBy]", inputs: ["aria-describedby"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
268
274
|
}
|
|
269
275
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmComboboxInput, decorators: [{
|
|
270
276
|
type: Component,
|
|
271
277
|
args: [{
|
|
272
278
|
selector: 'hlm-combobox-input',
|
|
273
|
-
imports: [HlmInputGroupImports, NgIcon, BrnComboboxImports, BrnComboboxPopoverTrigger],
|
|
279
|
+
imports: [HlmInputGroupImports, NgIcon, BrnComboboxImports, BrnComboboxPopoverTrigger, BrnFieldControlDescribedBy],
|
|
274
280
|
providers: [provideIcons({ lucideChevronDown, lucideX })],
|
|
275
281
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
276
282
|
hostDirectives: [BrnComboboxAnchor, HlmInputGroup],
|
|
@@ -283,6 +289,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
283
289
|
hlmInputGroupInput
|
|
284
290
|
[id]="inputId()"
|
|
285
291
|
[placeholder]="placeholder()"
|
|
292
|
+
brnFieldControlDescribedBy
|
|
293
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
294
|
+
[attr.aria-labelledby]="ariaLabelledBy()"
|
|
286
295
|
[forceInvalid]="forceInvalid()"
|
|
287
296
|
[aria-invalid]="ariaInvalidOverride()"
|
|
288
297
|
/>
|
|
@@ -319,7 +328,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
319
328
|
<ng-content />
|
|
320
329
|
`,
|
|
321
330
|
}]
|
|
322
|
-
}], ctorParameters: () => [], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTrigger", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }], ariaInvalidOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-invalid", required: false }] }] } });
|
|
331
|
+
}], ctorParameters: () => [], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], showTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTrigger", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }], ariaInvalidOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-invalid", required: false }] }] } });
|
|
323
332
|
|
|
324
333
|
class HlmComboboxItem {
|
|
325
334
|
_brnComboboxItem = inject(BrnComboboxItem);
|
|
@@ -509,11 +518,14 @@ class HlmComboboxTrigger {
|
|
|
509
518
|
...(ngDevMode ? [{ debugName: "_computedClass" }] : /* istanbul ignore next */ []));
|
|
510
519
|
buttonId = input(`hlm-combobox-trigger-${HlmComboboxTrigger._id++}`, /* @ts-ignore */
|
|
511
520
|
...(ngDevMode ? [{ debugName: "buttonId" }] : /* istanbul ignore next */ []));
|
|
521
|
+
/** Native button description IDs, merged with enclosing Spartan field descriptions. */
|
|
522
|
+
ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
523
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
512
524
|
variant = input('outline', /* @ts-ignore */
|
|
513
525
|
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
514
526
|
forceInvalid = input(false, { ...(ngDevMode ? { debugName: "forceInvalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
515
527
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmComboboxTrigger, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
516
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.3", type: HlmComboboxTrigger, isStandalone: true, selector: "hlm-combobox-trigger", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, buttonId: { classPropertyName: "buttonId", publicName: "buttonId", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideChevronDown })], ngImport: i0, template: `
|
|
528
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.3", type: HlmComboboxTrigger, isStandalone: true, selector: "hlm-combobox-trigger", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, buttonId: { classPropertyName: "buttonId", publicName: "buttonId", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, forceInvalid: { classPropertyName: "forceInvalid", publicName: "forceInvalid", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideIcons({ lucideChevronDown })], ngImport: i0, template: `
|
|
517
529
|
<button
|
|
518
530
|
brnComboboxTrigger
|
|
519
531
|
brnComboboxAnchor
|
|
@@ -522,6 +534,8 @@ class HlmComboboxTrigger {
|
|
|
522
534
|
hlmBtn
|
|
523
535
|
data-slot="combobox-trigger"
|
|
524
536
|
[id]="buttonId()"
|
|
537
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
538
|
+
[attr.aria-required]="required() || null"
|
|
525
539
|
[class]="_computedClass()"
|
|
526
540
|
[variant]="variant()"
|
|
527
541
|
[forceInvalid]="forceInvalid()"
|
|
@@ -554,6 +568,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
554
568
|
hlmBtn
|
|
555
569
|
data-slot="combobox-trigger"
|
|
556
570
|
[id]="buttonId()"
|
|
571
|
+
[aria-describedby]="ariaDescribedBy()"
|
|
572
|
+
[attr.aria-required]="required() || null"
|
|
557
573
|
[class]="_computedClass()"
|
|
558
574
|
[variant]="variant()"
|
|
559
575
|
[forceInvalid]="forceInvalid()"
|
|
@@ -563,7 +579,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
563
579
|
</button>
|
|
564
580
|
`,
|
|
565
581
|
}]
|
|
566
|
-
}], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], buttonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonId", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }] } });
|
|
582
|
+
}], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], buttonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonId", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], forceInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceInvalid", required: false }] }] } });
|
|
567
583
|
|
|
568
584
|
class HlmComboboxValue {
|
|
569
585
|
constructor() {
|
|
@@ -55,6 +55,9 @@ declare class HlmComboboxInput {
|
|
|
55
55
|
private static _id;
|
|
56
56
|
readonly inputId: i0.InputSignal<string>;
|
|
57
57
|
readonly placeholder: i0.InputSignal<string>;
|
|
58
|
+
/** Native input description IDs, merged with enclosing Spartan field descriptions. */
|
|
59
|
+
readonly ariaDescribedBy: i0.InputSignal<string | null>;
|
|
60
|
+
readonly ariaLabelledBy: i0.InputSignal<string | null>;
|
|
58
61
|
readonly showTrigger: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
59
62
|
readonly showClear: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
60
63
|
readonly forceInvalid: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
@@ -62,7 +65,7 @@ declare class HlmComboboxInput {
|
|
|
62
65
|
readonly ariaInvalidOverride: i0.InputSignalWithTransform<boolean | undefined, BooleanInput>;
|
|
63
66
|
constructor();
|
|
64
67
|
static ɵfac: i0.ɵɵFactoryDeclaration<HlmComboboxInput, never>;
|
|
65
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<HlmComboboxInput, "hlm-combobox-input", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "showTrigger": { "alias": "showTrigger"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; "ariaInvalidOverride": { "alias": "aria-invalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, [{ directive: typeof i1.BrnComboboxAnchor; inputs: {}; outputs: {}; }, { directive: typeof i2$1.HlmInputGroup; inputs: {}; outputs: {}; }]>;
|
|
68
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<HlmComboboxInput, "hlm-combobox-input", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "ariaLabelledBy": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; "showTrigger": { "alias": "showTrigger"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; "ariaInvalidOverride": { "alias": "aria-invalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, [{ directive: typeof i1.BrnComboboxAnchor; inputs: {}; outputs: {}; }, { directive: typeof i2$1.HlmInputGroup; inputs: {}; outputs: {}; }]>;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
declare class HlmComboboxItem {
|
|
@@ -119,10 +122,13 @@ declare class HlmComboboxTrigger {
|
|
|
119
122
|
readonly userClass: i0.InputSignal<ClassValue>;
|
|
120
123
|
protected readonly _computedClass: i0.Signal<string>;
|
|
121
124
|
readonly buttonId: i0.InputSignal<string>;
|
|
125
|
+
/** Native button description IDs, merged with enclosing Spartan field descriptions. */
|
|
126
|
+
readonly ariaDescribedBy: i0.InputSignal<string | null>;
|
|
127
|
+
readonly required: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
122
128
|
readonly variant: i0.InputSignal<"default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "light" | "dark" | "accent" | "destructive" | "muted" | "outline" | "link" | "ghost" | null | undefined>;
|
|
123
129
|
readonly forceInvalid: i0.InputSignalWithTransform<boolean, BooleanInput>;
|
|
124
130
|
static ɵfac: i0.ɵɵFactoryDeclaration<HlmComboboxTrigger, never>;
|
|
125
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<HlmComboboxTrigger, "hlm-combobox-trigger", never, { "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "buttonId": { "alias": "buttonId"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
131
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<HlmComboboxTrigger, "hlm-combobox-trigger", never, { "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "buttonId": { "alias": "buttonId"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
declare class HlmComboboxValue {
|
package/data-table/README.md
CHANGED
|
@@ -108,12 +108,14 @@ export class ServerPaymentsComponent {
|
|
|
108
108
|
[gridColumns]="gridColumns"
|
|
109
109
|
[data]="payments()"
|
|
110
110
|
[enableSelection]="true"
|
|
111
|
+
[getRowId]="paymentId"
|
|
111
112
|
[(layout)]="layout"
|
|
112
113
|
(selectionChange)="onSelect($event)"
|
|
113
114
|
/>
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
- `enableSelection` auto-prepends a checkbox column; `selectionChange` emits the selected rows.
|
|
118
|
+
- Define `readonly paymentId = (payment: Payment): string => payment.id` on the host. `getRowId` is a pure callback returning a unique, stable string per entity; keep the callback reference stable.
|
|
117
119
|
- `[(layout)]` toggles `'table' | 'grid'` (grid requires `gridColumns`). Column `meta: { hideInTable: true }` hides a column in table layout; `meta: { thumbnail: true }` renders it as the card banner.
|
|
118
120
|
- Sortable plain-string headers get an inline sort button; richer headers should use `EgDataTableColumnHeader` via `flexRenderComponent` (no type arguments needed — its `column` input accepts any TanStack column through the `EgSortableColumn` structural type):
|
|
119
121
|
|
|
@@ -126,6 +128,14 @@ helper.accessor('email', {
|
|
|
126
128
|
});
|
|
127
129
|
```
|
|
128
130
|
|
|
131
|
+
### Selection identity and scope
|
|
132
|
+
|
|
133
|
+
- **Stable IDs:** supply `getRowId` whenever rows can be refreshed, reordered, or fetched from a server. Selection follows IDs still present in the supplied rows, and refreshes emit the current objects, not stale snapshots. Removed IDs are discarded and are not selected if they later return. Changing the callback clears selection.
|
|
134
|
+
- **Server pages:** selection is scoped to the loaded slice, not an accumulated cross-page bulk-action set. Replacing a page keeps only selected IDs also in the new slice. Refreshing the same slice preserves selection when IDs match. Passing `null` or an empty slice clears it; keep the current slice and set `isLoading` if selection should survive loading a refresh.
|
|
135
|
+
- **Client filtering/paging:** `filterRows` defines the available selection scope; removing a row there discards its selection. Built-in column filters and client pagination only hide rows: hidden selections remain in `selectionChange`. Header selection toggles only the displayed page; table and grid share selection. The footer selection count describes the filtered subset.
|
|
136
|
+
- **Without `getRowId`:** existing positional `initialRowSelection` keys (`'0'`, `'1'`, etc.) remain supported for the initial rows. Replacing the source array (including a same-record refresh) or changing the `filterRows` result array clears selection rather than transferring an index to a different entity. Built-in sorting/filtering/paging with the same source array preserves it. This intentionally tightens the former unsafe positional behavior.
|
|
137
|
+
- **Updates and outputs:** update row arrays/objects immutably; in-place mutations are not tracked. `initialRowSelection` seeds only IDs present on initialization; later seed changes are ignored. `selectionChange` fires once on initialization, then when the selected objects or their source order change, including removal and refreshed object references. Unchanged selections do not re-emit for sorting, layout, or other unrelated state changes. IDs must be unique and must not be reused for different entities.
|
|
138
|
+
|
|
129
139
|
## API reference
|
|
130
140
|
|
|
131
141
|
| Symbol | Kind |
|