@cdevhub/ngx-tw 0.5.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 +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +11 -2
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +28 -4
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs → cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs} +3 -3
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +23 -7
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs → cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs} +107 -19
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +21 -13
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +111 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +332 -0
- package/theme/_semantic.css +60 -26
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +12 -3
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +19 -3
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +20 -6
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +0 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { model, input, output, isDevMode, Directive, inject, viewChild, effect, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { model, input, output, isDevMode, Directive, inject, ElementRef, viewChild, effect, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { TW_SORT_HANDLE } from '@cdevhub/ngx-tw/core';
|
|
4
4
|
import { NgTemplateOutlet } from '@angular/common';
|
|
5
5
|
import { FocusMonitor, AriaDescriber } from '@angular/cdk/a11y';
|
|
@@ -35,13 +35,13 @@ class SortDirective {
|
|
|
35
35
|
active = model(null, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), alias: 'twSortActive' });
|
|
36
36
|
/** Current sort direction. `null` represents the cleared state. Two-way bindable via `[(twSortDirection)]`. Defaults to `null`. */
|
|
37
37
|
direction = model(null, { ...(ngDevMode ? { debugName: "direction" } : /* istanbul ignore next */ {}), alias: 'twSortDirection' });
|
|
38
|
-
/** Starting direction used when a header becomes active for the first time. Per-header `start` overrides this. Defaults to `'asc'`. */
|
|
38
|
+
/** Starting direction used when a header becomes active for the first time. Per-header `start` overrides this. Defaults to `'asc'`. Alias: `twSortStart`. */
|
|
39
39
|
start = input('asc', { ...(ngDevMode ? { debugName: "start" } : /* istanbul ignore next */ {}), alias: 'twSortStart' });
|
|
40
|
-
/** When true, the direction cycle skips the cleared (`null`) state — headers toggle between `'asc'` and `'desc'` only. Per-header `disableClear` overrides this. Defaults to `false`. */
|
|
40
|
+
/** When true, the direction cycle skips the cleared (`null`) state — headers toggle between `'asc'` and `'desc'` only. Per-header `disableClear` overrides this. Defaults to `false`. Alias: `twSortDisableClear`. */
|
|
41
41
|
disableClear = input(false, { ...(ngDevMode ? { debugName: "disableClear" } : /* istanbul ignore next */ {}), alias: 'twSortDisableClear' });
|
|
42
|
-
/** When true, all child sort headers are disabled. Defaults to `false`. */
|
|
42
|
+
/** When true, all child sort headers are disabled. Defaults to `false`. Alias: `twSortDisabled`. */
|
|
43
43
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), alias: 'twSortDisabled' });
|
|
44
|
-
/** Fires whenever the user changes `active` or `direction` by interacting with a header. Programmatic writes to `[(twSortActive)]` / `[(twSortDirection)]` do NOT emit. */
|
|
44
|
+
/** Fires whenever the user changes `active` or `direction` by interacting with a header. Programmatic writes to `[(twSortActive)]` / `[(twSortDirection)]` do NOT emit. Alias: `twSortChange`. */
|
|
45
45
|
sortChange = output({ alias: 'twSortChange' });
|
|
46
46
|
sortableIds = new Set();
|
|
47
47
|
/** Registers a header id so duplicates can be detected. Called by `SortHeaderComponent` on init. Throws in dev mode if another header with the same id is already registered. */
|
|
@@ -125,6 +125,29 @@ function snapshot(self) {
|
|
|
125
125
|
disabled: self.isDisabled(),
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether the host element exposes a `columnheader` / `rowheader` role, which are the only two
|
|
130
|
+
* roles `aria-sort` is valid on. A static `role` attribute wins over the tag name; a `[attr.role]`
|
|
131
|
+
* *binding* is not visible yet at construction time and is deliberately not consulted.
|
|
132
|
+
*/
|
|
133
|
+
function isHeaderCell(el) {
|
|
134
|
+
const role = el.getAttribute('role');
|
|
135
|
+
if (role !== null)
|
|
136
|
+
return role === 'columnheader' || role === 'rowheader';
|
|
137
|
+
return el.tagName === 'TH';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Whether the host element is already an interactive control. When it is, the inner container must
|
|
141
|
+
* NOT also become one — two nested widgets is an axe `nested-interactive` failure and leaves
|
|
142
|
+
* assistive tech with an ambiguous activation target.
|
|
143
|
+
*/
|
|
144
|
+
function isInteractiveHost(el) {
|
|
145
|
+
const role = el.getAttribute('role');
|
|
146
|
+
if (role === 'button' || role === 'link')
|
|
147
|
+
return true;
|
|
148
|
+
const tag = el.tagName;
|
|
149
|
+
return tag === 'BUTTON' || (tag === 'A' && el.hasAttribute('href'));
|
|
150
|
+
}
|
|
128
151
|
// ── Static per-color maps (Tailwind v4 requires literal class strings for its JIT scanner). ──
|
|
129
152
|
const ARROW_ACTIVE_COLOR = {
|
|
130
153
|
primary: 'text-primary-600',
|
|
@@ -140,33 +163,44 @@ const ARROW_ACTIVE_COLOR = {
|
|
|
140
163
|
const sortHeaderVariants = tv({
|
|
141
164
|
slots: {
|
|
142
165
|
host: 'group select-none',
|
|
143
|
-
|
|
166
|
+
// `align-top` is load-bearing, not cosmetic. The container is an
|
|
167
|
+
// inline-level box inside the host block, so without it the host's line
|
|
168
|
+
// box is `strut ∪ baseline-aligned container` — at `xs` the strut's
|
|
169
|
+
// descent hangs 2px below the 24px control, and the row measures 26px.
|
|
170
|
+
// `vertical-align: top` flushes the box to the line-box top, making the
|
|
171
|
+
// host exactly the pinned height at every size. Inert at sm–xl, where the
|
|
172
|
+
// control already exceeds the strut.
|
|
173
|
+
container: 'inline-flex align-top items-center gap-1.5 cursor-pointer rounded-md font-medium text-fg transition-colors duration-normal motion-reduce:transition-none hover:bg-surface-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
144
174
|
label: 'min-w-0',
|
|
145
175
|
arrow: 'inline-flex shrink-0 items-center justify-center transition-opacity duration-normal motion-reduce:transition-none',
|
|
146
176
|
arrowIcon: 'shrink-0 transition-transform duration-normal motion-reduce:transition-none',
|
|
147
177
|
},
|
|
148
178
|
variants: {
|
|
149
179
|
size: {
|
|
180
|
+
// Heights are pinned to the control scale (docs/vertical-rhythm.md):
|
|
181
|
+
// 24 / 32 / 36 / 44 / 48. Vertical padding is deleted deliberately —
|
|
182
|
+
// `items-center` on the container does the centring, and leaving `py-*`
|
|
183
|
+
// alive would fight the pinned height under `box-sizing: border-box`.
|
|
150
184
|
xs: {
|
|
151
|
-
container: 'px-2
|
|
185
|
+
container: 'px-2 text-xs h-6',
|
|
152
186
|
// `size-3.5` half-step: only icon size aligning with text-xs without
|
|
153
187
|
// dominating it; codified per CLAUDE.md.
|
|
154
188
|
arrowIcon: 'size-3.5',
|
|
155
189
|
},
|
|
156
190
|
sm: {
|
|
157
|
-
container: 'px-2.5
|
|
191
|
+
container: 'px-2.5 text-sm h-8',
|
|
158
192
|
arrowIcon: 'size-4',
|
|
159
193
|
},
|
|
160
194
|
md: {
|
|
161
|
-
container: 'px-3
|
|
195
|
+
container: 'px-3 text-sm h-9',
|
|
162
196
|
arrowIcon: 'size-4',
|
|
163
197
|
},
|
|
164
198
|
lg: {
|
|
165
|
-
container: 'px-4
|
|
199
|
+
container: 'px-4 text-base h-11',
|
|
166
200
|
arrowIcon: 'size-5',
|
|
167
201
|
},
|
|
168
202
|
xl: {
|
|
169
|
-
container: 'px-5
|
|
203
|
+
container: 'px-5 text-base h-12',
|
|
170
204
|
arrowIcon: 'size-5',
|
|
171
205
|
},
|
|
172
206
|
},
|
|
@@ -192,18 +226,47 @@ const sortHeaderVariants = tv({
|
|
|
192
226
|
},
|
|
193
227
|
false: {},
|
|
194
228
|
},
|
|
229
|
+
// True when the consumer mounted the header on an element that is
|
|
230
|
+
// already a control (`<button>`, `<a href>`, `[role="button"]`). The
|
|
231
|
+
// inner container then stays inert, so the canonical focus ring has to
|
|
232
|
+
// move onto the host — it is the element that actually receives focus.
|
|
233
|
+
hostControl: {
|
|
234
|
+
true: {
|
|
235
|
+
host: 'rounded-md cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
236
|
+
},
|
|
237
|
+
false: {},
|
|
238
|
+
},
|
|
195
239
|
},
|
|
196
240
|
defaultVariants: {
|
|
197
241
|
size: 'md',
|
|
198
242
|
active: false,
|
|
199
243
|
direction: 'none',
|
|
200
244
|
disabled: false,
|
|
245
|
+
hostControl: false,
|
|
201
246
|
},
|
|
202
247
|
}, { twMerge: true });
|
|
203
248
|
/**
|
|
204
249
|
* Turns any element (e.g., `<th>`, `<div>`, `<button>`) into a sortable header under a parent
|
|
205
250
|
* `SortDirective`. Renders the projected label plus an arrow that reflects the current direction.
|
|
206
251
|
* Triggers a sort cycle on click or Enter/Space.
|
|
252
|
+
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* **Where the control and `aria-sort` live depends on the host element.** Both are decided once,
|
|
255
|
+
* at construction, from the host's tag name and its *static* `role` attribute:
|
|
256
|
+
*
|
|
257
|
+
* | Host | `aria-sort` | Interactive element |
|
|
258
|
+
* |---|---|---|
|
|
259
|
+
* | `<th>` / `[role="columnheader"\|"rowheader"]` | on the host | inner container (`role="button"`) |
|
|
260
|
+
* | `<span>` / `<div>` (no role) | **not emitted** | inner container (`role="button"`) |
|
|
261
|
+
* | `<button>` / `<a href>` / `[role="button"\|"link"]` | **not emitted** | the host itself |
|
|
262
|
+
*
|
|
263
|
+
* `aria-sort` is only valid on a `columnheader` / `rowheader`, so emitting it on a `<span>` or
|
|
264
|
+
* `<button>` is an axe `aria-allowed-attr` failure and is silently ignored by assistive tech. When
|
|
265
|
+
* the header is composed *inside* a header cell — `<th><span tw-sort-header>…</span></th>`, the
|
|
266
|
+
* shape `tw-table` generates — the `<th>` owns `aria-sort` (see `tw-column`'s `sortState` input,
|
|
267
|
+
* which auto-resolves against the same `[twSort]` directive). Outside a table there is no
|
|
268
|
+
* `columnheader` to carry the state; the header remains a labelled button and the sort direction
|
|
269
|
+
* is conveyed by the arrow plus the consumer's own live region.
|
|
207
270
|
*/
|
|
208
271
|
// Selector is deliberately kebab-case to read naturally as an HTML attribute on
|
|
209
272
|
// host elements (e.g. `<th tw-sort-header>`). Camelcasing it to `twSortHeader`
|
|
@@ -235,6 +298,15 @@ class SortHeaderComponent {
|
|
|
235
298
|
parent = inject(SortDirective, { optional: true });
|
|
236
299
|
focusMonitor = inject(FocusMonitor);
|
|
237
300
|
ariaDescriber = inject(AriaDescriber, { optional: true });
|
|
301
|
+
hostEl = inject(ElementRef).nativeElement;
|
|
302
|
+
/**
|
|
303
|
+
* Whether the host element can legally carry `aria-sort`. Resolved once, at construction — the
|
|
304
|
+
* host's tag name and static attributes are already applied by then, and a later `[attr.role]`
|
|
305
|
+
* binding would make this timing-dependent.
|
|
306
|
+
*/
|
|
307
|
+
hostIsHeaderCell = isHeaderCell(this.hostEl);
|
|
308
|
+
/** Whether the host is already a control, in which case the inner container must stay inert. */
|
|
309
|
+
hostIsControl = isInteractiveHost(this.hostEl);
|
|
238
310
|
containerRef = viewChild('container', /* @ts-ignore */
|
|
239
311
|
...(ngDevMode ? [{ debugName: "containerRef" }] : /* istanbul ignore next */ []));
|
|
240
312
|
_describedBy = null;
|
|
@@ -249,7 +321,7 @@ class SortHeaderComponent {
|
|
|
249
321
|
// silently ignore subsequent rebinds.
|
|
250
322
|
effect(() => {
|
|
251
323
|
const description = this.sortActionDescription();
|
|
252
|
-
const el = this.
|
|
324
|
+
const el = this.controlElement();
|
|
253
325
|
if (!el || !this.ariaDescriber)
|
|
254
326
|
return;
|
|
255
327
|
if (this._describedBy) {
|
|
@@ -279,15 +351,37 @@ class SortHeaderComponent {
|
|
|
279
351
|
return 'none';
|
|
280
352
|
}, /* @ts-ignore */
|
|
281
353
|
...(ngDevMode ? [{ debugName: "ariaSort" }] : /* istanbul ignore next */ []));
|
|
354
|
+
/**
|
|
355
|
+
* @internal Value actually written to the host's `aria-sort` attribute. `null` — i.e. no
|
|
356
|
+
* attribute — unless the host is a `columnheader` / `rowheader`; see the class remarks.
|
|
357
|
+
*/
|
|
358
|
+
hostAriaSort = computed(() => this.hostIsHeaderCell ? this.ariaSort() : null, /* @ts-ignore */
|
|
359
|
+
...(ngDevMode ? [{ debugName: "hostAriaSort" }] : /* istanbul ignore next */ []));
|
|
360
|
+
/** @internal `role` for the inner container: `'button'` unless the host is already the control. */
|
|
361
|
+
controlRole = computed(() => this.hostIsControl || this.isDisabled() ? null : 'button', /* @ts-ignore */
|
|
362
|
+
...(ngDevMode ? [{ debugName: "controlRole" }] : /* istanbul ignore next */ []));
|
|
363
|
+
/** @internal `tabindex` for the inner container. Mirrors `controlRole()`. */
|
|
364
|
+
controlTabIndex = computed(() => this.hostIsControl || this.isDisabled() ? null : 0, /* @ts-ignore */
|
|
365
|
+
...(ngDevMode ? [{ debugName: "controlTabIndex" }] : /* istanbul ignore next */ []));
|
|
282
366
|
/** Whether the arrow element should render. Active headers always render; inactive render when not disabled so hover reveals the affordance. */
|
|
283
367
|
renderArrow = computed(() => this.isActive() || !this.isDisabled(), /* @ts-ignore */
|
|
284
368
|
...(ngDevMode ? [{ debugName: "renderArrow" }] : /* istanbul ignore next */ []));
|
|
369
|
+
/**
|
|
370
|
+
* The element that carries focus and the accessible description — the host when the consumer
|
|
371
|
+
* mounted the header on a control, the inner container otherwise.
|
|
372
|
+
*/
|
|
373
|
+
controlElement() {
|
|
374
|
+
return this.hostIsControl
|
|
375
|
+
? this.hostEl
|
|
376
|
+
: this.containerRef()?.nativeElement;
|
|
377
|
+
}
|
|
285
378
|
// ── Variant class computation ──
|
|
286
379
|
variantResult = computed(() => sortHeaderVariants({
|
|
287
380
|
size: this.size(),
|
|
288
381
|
active: this.isActive(),
|
|
289
382
|
direction: this.effectiveDirection() ?? 'none',
|
|
290
383
|
disabled: this.isDisabled(),
|
|
384
|
+
hostControl: this.hostIsControl,
|
|
291
385
|
}), /* @ts-ignore */
|
|
292
386
|
...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
293
387
|
/** @internal */
|
|
@@ -315,13 +409,13 @@ class SortHeaderComponent {
|
|
|
315
409
|
this.parent?.register(this.id());
|
|
316
410
|
}
|
|
317
411
|
ngAfterViewInit() {
|
|
318
|
-
const el = this.
|
|
412
|
+
const el = this.controlElement();
|
|
319
413
|
if (el) {
|
|
320
414
|
this.focusMonitor.monitor(el, true);
|
|
321
415
|
}
|
|
322
416
|
}
|
|
323
417
|
ngOnDestroy() {
|
|
324
|
-
const el = this.
|
|
418
|
+
const el = this.controlElement();
|
|
325
419
|
if (el) {
|
|
326
420
|
this.focusMonitor.stopMonitoring(el);
|
|
327
421
|
if (this._describedBy && this.ariaDescriber) {
|
|
@@ -347,17 +441,28 @@ class SortHeaderComponent {
|
|
|
347
441
|
}
|
|
348
442
|
}
|
|
349
443
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SortHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
350
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SortHeaderComponent, isStandalone: true, selector: "[tw-sort-header]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, disableClear: { classPropertyName: "disableClear", publicName: "disableClear", isSignal: true, isRequired: false, transformFunction: null }, headerDisabled: { classPropertyName: "headerDisabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, arrowPosition: { classPropertyName: "arrowPosition", publicName: "arrowPosition", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, sortActionDescription: { classPropertyName: "sortActionDescription", publicName: "sortActionDescription", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "handleClick()", "keydown": "handleKeydown($event)" }, properties: { "class": "hostClasses()", "attr.aria-sort": "ariaSort()", "attr.aria-disabled": "isDisabled() ? \"true\" : null" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, isSignal: true }], exportAs: ["twSortHeader"], ngImport: i0, template: "<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"
|
|
444
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SortHeaderComponent, isStandalone: true, selector: "[tw-sort-header]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, disableClear: { classPropertyName: "disableClear", publicName: "disableClear", isSignal: true, isRequired: false, transformFunction: null }, headerDisabled: { classPropertyName: "headerDisabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, arrowPosition: { classPropertyName: "arrowPosition", publicName: "arrowPosition", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, sortActionDescription: { classPropertyName: "sortActionDescription", publicName: "sortActionDescription", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "handleClick()", "keydown": "handleKeydown($event)" }, properties: { "class": "hostClasses()", "attr.aria-sort": "hostAriaSort()", "attr.data-sort-direction": "ariaSort()", "attr.aria-disabled": "isDisabled() ? \"true\" : null" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, isSignal: true }], exportAs: ["twSortHeader"], ngImport: i0, template: "<!--\n The container is the interactive control (role=button + tabindex) unless the\n consumer already mounted the header on one - a button, an anchor, or an\n element carrying a static button/link role. Nesting a second widget inside\n such a host is an axe nested-interactive failure and leaves assistive tech\n with two competing activation targets, so controlRole and controlTabIndex\n both resolve to null there and the host keeps the focus ring instead.\n-->\n<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"controlRole()\"\n [attr.tabindex]=\"controlTabIndex()\"\n>\n @if (arrowPosition() === 'before' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n <span [class]=\"labelClasses()\"><ng-content /></span>\n @if (arrowPosition() === 'after' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n</div>\n\n<ng-template #arrow>\n <span [class]=\"arrowClasses()\" data-tw-sort-arrow>\n <ng-content select=\"[twSortHeaderIcon]\">\n <svg\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n [class]=\"arrowIconClasses()\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </ng-content>\n </span>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
351
445
|
}
|
|
352
446
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SortHeaderComponent, decorators: [{
|
|
353
447
|
type: Component,
|
|
354
448
|
args: [{ selector: '[tw-sort-header]', exportAs: 'twSortHeader', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
|
|
355
449
|
'[class]': 'hostClasses()',
|
|
356
|
-
'[attr.aria-sort]': '
|
|
450
|
+
'[attr.aria-sort]': 'hostAriaSort()',
|
|
451
|
+
// Sort state as a data attribute, on EVERY host shape.
|
|
452
|
+
//
|
|
453
|
+
// `aria-sort` is only legal on a header cell, so on a span or button host
|
|
454
|
+
// it is deliberately absent — which left the sort state with no stable,
|
|
455
|
+
// valid hook at all on those hosts. The direction was readable only from
|
|
456
|
+
// the arrow's rotation utility class, which is implementation detail no
|
|
457
|
+
// test or consumer should depend on. A `data-*` attribute is valid on any
|
|
458
|
+
// element and carries no ARIA semantics, so it states the same thing
|
|
459
|
+
// without lying to assistive tech. Values mirror `aria-sort` exactly, so
|
|
460
|
+
// a reader does not have to learn a second vocabulary.
|
|
461
|
+
'[attr.data-sort-direction]': 'ariaSort()',
|
|
357
462
|
'[attr.aria-disabled]': 'isDisabled() ? "true" : null',
|
|
358
463
|
'(click)': 'handleClick()',
|
|
359
464
|
'(keydown)': 'handleKeydown($event)',
|
|
360
|
-
}, template: "<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"
|
|
465
|
+
}, template: "<!--\n The container is the interactive control (role=button + tabindex) unless the\n consumer already mounted the header on one - a button, an anchor, or an\n element carrying a static button/link role. Nesting a second widget inside\n such a host is an axe nested-interactive failure and leaves assistive tech\n with two competing activation targets, so controlRole and controlTabIndex\n both resolve to null there and the host keeps the focus ring instead.\n-->\n<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"controlRole()\"\n [attr.tabindex]=\"controlTabIndex()\"\n>\n @if (arrowPosition() === 'before' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n <span [class]=\"labelClasses()\"><ng-content /></span>\n @if (arrowPosition() === 'after' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n</div>\n\n<ng-template #arrow>\n <span [class]=\"arrowClasses()\" data-tw-sort-arrow>\n <ng-content select=\"[twSortHeaderIcon]\">\n <svg\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n [class]=\"arrowIconClasses()\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </ng-content>\n </span>\n</ng-template>\n" }]
|
|
361
466
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], start: [{ type: i0.Input, args: [{ isSignal: true, alias: "start", required: false }] }], disableClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableClear", required: false }] }], headerDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], arrowPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPosition", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], sortActionDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortActionDescription", required: false }] }], containerRef: [{ type: i0.ViewChild, args: ['container', { isSignal: true }] }] } });
|
|
362
467
|
|
|
363
468
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdevhub-ngx-tw-sort.mjs","sources":["../../../projects/ngx-tw/sort/sort.ts","../../../projects/ngx-tw/sort/sort-header.ts","../../../projects/ngx-tw/sort/sort-header.html","../../../projects/ngx-tw/sort/cdevhub-ngx-tw-sort.ts"],"sourcesContent":["import {\n Directive,\n input,\n isDevMode,\n model,\n output,\n} from '@angular/core';\nimport { TW_SORT_HANDLE } from '@cdevhub/ngx-tw/core';\n\n/** Sort direction. `null` represents the cleared (unsorted) state. */\nexport type SortDirection = 'asc' | 'desc' | null;\n\n/** Shape that `SortHeaderComponent` implements when registering with a parent `SortDirective`. */\nexport interface TwSortable {\n /** Unique id identifying the column or field this header sorts. */\n readonly id: string;\n /** Header-level starting direction. `undefined` falls back to the parent directive's `start`. */\n readonly start: 'asc' | 'desc' | undefined;\n /** Header-level override for disabling the cleared state in the cycle. `undefined` falls back to the parent directive's `disableClear`. */\n readonly disableClear: boolean | undefined;\n /** Whether this header is disabled. */\n readonly disabled: boolean;\n}\n\n/** Payload emitted by `SortDirective.sortChange` whenever a user interaction changes the sort state. */\nexport interface TwSortEvent {\n /** The new active header id, or `null` when the sort was cleared. */\n active: string | null;\n /** The new sort direction. */\n direction: SortDirection;\n /** Snapshot of the previous state, before this change. */\n previous: {\n /** Previous active header id. */\n active: string | null;\n /** Previous sort direction. */\n direction: SortDirection;\n };\n}\n\n/**\n * Returns the direction cycle for a given starting direction and disable-clear flag.\n * @internal Exported for unit tests; not part of the public API.\n */\nexport function getSortDirectionCycle(\n start: 'asc' | 'desc',\n disableClear: boolean,\n): SortDirection[] {\n const cycle: SortDirection[] = start === 'desc' ? ['desc', 'asc'] : ['asc', 'desc'];\n if (!disableClear) cycle.push(null);\n return cycle;\n}\n\n/**\n * Container directive that holds the current sort state (`active` id + `direction`) and\n * coordinates with child `SortHeaderComponent` instances. Compose with any rendering layer —\n * tables, lists, custom grids — by placing child headers inside an element marked `[twSort]`.\n *\n * @remarks\n * **`tw*` input/output aliases.** Every `input()` / `output()` / `model()` on this directive\n * carries a `tw*` alias (e.g. `'twSortActive'`, `'twSortDirection'`, `'twSortChange'`). The\n * aliases namespace bindings under the directive selector so they cannot collide with\n * attributes the host element already owns (a `<table>` consumer binding `[active]` would\n * otherwise be ambiguous). This mirrors Angular Material's `mat*` aliasing precedent on\n * `MatSort`. Compodoc surfaces both the field name and the alias; consumer templates MUST\n * bind via the aliased name (`[twSortActive]`, `(twSortChange)`, …). Removing an alias is a\n * breaking API change requiring a major version bump.\n */\n@Directive({\n selector: '[twSort]',\n exportAs: 'twSort',\n host: {\n 'class': 'tw-sort',\n },\n providers: [{ provide: TW_SORT_HANDLE, useExisting: SortDirective }],\n})\nexport class SortDirective {\n /** The id of the currently sorted header, or `null` when nothing is sorted. Two-way bindable via `[(twSortActive)]`. Defaults to `null`. */\n readonly active = model<string | null>(null, { alias: 'twSortActive' });\n\n /** Current sort direction. `null` represents the cleared state. Two-way bindable via `[(twSortDirection)]`. Defaults to `null`. */\n readonly direction = model<SortDirection>(null, { alias: 'twSortDirection' });\n\n /** Starting direction used when a header becomes active for the first time. Per-header `start` overrides this. Defaults to `'asc'`. */\n readonly start = input<'asc' | 'desc'>('asc', { alias: 'twSortStart' });\n\n /** When true, the direction cycle skips the cleared (`null`) state — headers toggle between `'asc'` and `'desc'` only. Per-header `disableClear` overrides this. Defaults to `false`. */\n readonly disableClear = input<boolean>(false, { alias: 'twSortDisableClear' });\n\n /** When true, all child sort headers are disabled. Defaults to `false`. */\n readonly disabled = input<boolean>(false, { alias: 'twSortDisabled' });\n\n /** Fires whenever the user changes `active` or `direction` by interacting with a header. Programmatic writes to `[(twSortActive)]` / `[(twSortDirection)]` do NOT emit. */\n readonly sortChange = output<TwSortEvent>({ alias: 'twSortChange' });\n\n private readonly sortableIds = new Set<string>();\n\n /** Registers a header id so duplicates can be detected. Called by `SortHeaderComponent` on init. Throws in dev mode if another header with the same id is already registered. */\n register(id: string): void {\n if (isDevMode()) {\n if (!id) {\n throw new Error('tw-sort-header must be provided with a unique id.');\n }\n if (this.sortableIds.has(id)) {\n throw new Error(\n `Cannot have two tw-sort-header elements with the same id (${id}).`,\n );\n }\n }\n this.sortableIds.add(id);\n }\n\n /** Deregisters a header id. Called on destroy. */\n deregister(id: string): void {\n this.sortableIds.delete(id);\n }\n\n /** Cycles the direction for the given header and emits `sortChange`. No-op when the directive or the header is disabled. */\n sort(sortable: TwSortable): void {\n if (this.disabled() || sortable.disabled) return;\n\n const previousActive = this.active();\n const previousDirection = this.direction();\n\n let nextActive: string | null;\n let nextDirection: SortDirection;\n\n if (previousActive !== sortable.id) {\n nextActive = sortable.id;\n nextDirection = sortable.start ?? this.start();\n } else {\n nextDirection = this.getNextSortDirection(sortable);\n nextActive = nextDirection === null ? null : sortable.id;\n }\n\n this.active.set(nextActive);\n this.direction.set(nextDirection);\n\n this.sortChange.emit({\n active: nextActive,\n direction: nextDirection,\n previous: {\n active: previousActive,\n direction: previousDirection,\n },\n });\n }\n\n /** Returns the next direction in the cycle for the given header, based on the current state and header/parent overrides. */\n getNextSortDirection(sortable: TwSortable): SortDirection {\n const start = sortable.start ?? this.start();\n const disableClear = sortable.disableClear ?? this.disableClear();\n const cycle = getSortDirectionCycle(start, disableClear);\n\n // If the header is not currently active, the next direction is the starting one.\n if (this.active() !== sortable.id) {\n return start;\n }\n\n const currentIndex = cycle.indexOf(this.direction());\n if (currentIndex < 0) {\n return start;\n }\n return cycle[(currentIndex + 1) % cycle.length] as SortDirection;\n }\n}\n","import {\n type AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n type ElementRef,\n inject,\n input,\n isDevMode,\n type OnDestroy,\n type OnInit,\n viewChild,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { AriaDescriber, FocusMonitor } from '@angular/cdk/a11y';\nimport { tv } from 'tailwind-variants';\nimport type { TwColor, TwSize } from '@cdevhub/ngx-tw/core';\nimport { SortDirective, type SortDirection, type TwSortable } from './sort';\n\nfunction snapshot(self: SortHeaderComponent): TwSortable {\n return {\n id: self.id(),\n start: self.start(),\n disableClear: self.disableClear(),\n disabled: self.isDisabled(),\n };\n}\n\n/** Position of the sort arrow relative to the header label. */\nexport type TwSortArrowPosition = 'before' | 'after';\n\n// ── Static per-color maps (Tailwind v4 requires literal class strings for its JIT scanner). ──\n\nconst ARROW_ACTIVE_COLOR: Record<TwColor, string> = {\n primary: 'text-primary-600',\n secondary: 'text-secondary-600',\n accent: 'text-accent-600',\n neutral: 'text-fg',\n info: 'text-info-600',\n success: 'text-success-600',\n warning: 'text-warning-600',\n error: 'text-error-600',\n};\n\n// ── tv() config ──\n\nconst sortHeaderVariants = tv(\n {\n slots: {\n host: 'group select-none',\n container:\n 'inline-flex items-center gap-1.5 cursor-pointer rounded-md font-medium text-fg transition-colors duration-normal motion-reduce:transition-none hover:bg-surface-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',\n label: 'min-w-0',\n arrow:\n 'inline-flex shrink-0 items-center justify-center transition-opacity duration-normal motion-reduce:transition-none',\n arrowIcon:\n 'shrink-0 transition-transform duration-normal motion-reduce:transition-none',\n },\n variants: {\n size: {\n xs: {\n container: 'px-2 py-1 text-xs',\n // `size-3.5` half-step: only icon size aligning with text-xs without\n // dominating it; codified per CLAUDE.md.\n arrowIcon: 'size-3.5',\n },\n sm: {\n container: 'px-2.5 py-1.5 text-sm',\n arrowIcon: 'size-4',\n },\n md: {\n container: 'px-3 py-2 text-sm',\n arrowIcon: 'size-4',\n },\n lg: {\n container: 'px-4 py-2.5 text-base',\n arrowIcon: 'size-5',\n },\n xl: {\n container: 'px-5 py-3 text-base',\n arrowIcon: 'size-5',\n },\n },\n active: {\n true: {\n container: 'text-fg',\n arrow: 'opacity-100',\n },\n false: {\n container: 'text-fg-muted',\n arrow:\n 'opacity-0 group-hover:opacity-50 group-focus-within:opacity-50',\n },\n },\n direction: {\n asc: { arrowIcon: 'rotate-180' },\n desc: { arrowIcon: 'rotate-0' },\n none: { arrowIcon: 'rotate-0' },\n },\n disabled: {\n true: {\n host: 'opacity-50 pointer-events-none',\n container: 'cursor-not-allowed',\n },\n false: {},\n },\n },\n defaultVariants: {\n size: 'md',\n active: false,\n direction: 'none',\n disabled: false,\n },\n },\n { twMerge: true },\n);\n\n/**\n * Turns any element (e.g., `<th>`, `<div>`, `<button>`) into a sortable header under a parent\n * `SortDirective`. Renders the projected label plus an arrow that reflects the current direction.\n * Triggers a sort cycle on click or Enter/Space.\n */\n// Selector is deliberately kebab-case to read naturally as an HTML attribute on\n// host elements (e.g. `<th tw-sort-header>`). Camelcasing it to `twSortHeader`\n// would break the public API and ~20 demo usages.\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: '[tw-sort-header]',\n exportAs: 'twSortHeader',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgTemplateOutlet],\n templateUrl: './sort-header.html',\n host: {\n '[class]': 'hostClasses()',\n '[attr.aria-sort]': 'ariaSort()',\n '[attr.aria-disabled]': 'isDisabled() ? \"true\" : null',\n '(click)': 'handleClick()',\n '(keydown)': 'handleKeydown($event)',\n },\n})\nexport class SortHeaderComponent implements OnInit, AfterViewInit, OnDestroy {\n /** Unique id for this header. Required — identifies the field/column this header sorts. */\n readonly id = input.required<string>();\n\n /** Overrides the parent directive's `start` for this header only. `undefined` inherits from the parent. */\n readonly start = input<'asc' | 'desc' | undefined>(undefined);\n\n /** Overrides the parent directive's `disableClear` for this header only. `undefined` inherits from the parent. */\n readonly disableClear = input<boolean | undefined>(undefined);\n\n /** When true, this header is disabled — clicks and keyboard activation are ignored. Defaults to `false`. */\n readonly headerDisabled = input<boolean>(false, { alias: 'disabled' });\n\n /** Whether the sort arrow renders `'before'` or `'after'` the projected label. Defaults to `'after'`. */\n readonly arrowPosition = input<TwSortArrowPosition>('after');\n\n /** Semantic color used to tint the arrow when this header is active. Defaults to `'primary'`. */\n readonly color = input<TwColor>('primary');\n\n /** Controls padding and font size. Uses the shared `TwSize` scale. Defaults to `'md'`. */\n readonly size = input<TwSize>('md');\n\n /** Accessible description applied via `AriaDescriber` so screen readers announce the sort action alongside the header text. Defaults to `'Sort'`. */\n readonly sortActionDescription = input<string>('Sort');\n\n private readonly parent = inject(SortDirective, { optional: true });\n private readonly focusMonitor = inject(FocusMonitor);\n private readonly ariaDescriber = inject(AriaDescriber, { optional: true });\n\n private readonly containerRef =\n viewChild<ElementRef<HTMLElement>>('container');\n\n private _describedBy: string | null = null;\n\n constructor() {\n if (!this.parent && isDevMode()) {\n throw new Error(\n 'tw-sort-header must be placed within a parent element with the twSort directive.',\n );\n }\n\n // Reactive: re-tags the host whenever `sortActionDescription` (a public\n // signal input) changes; consumers may swap the description at runtime for\n // i18n / state-driven copy. Cleanup runs in `ngOnDestroy`. Not a one-shot\n // `afterNextRender`: that would freeze the description at first render and\n // silently ignore subsequent rebinds.\n effect(() => {\n const description = this.sortActionDescription();\n const el = this.containerRef()?.nativeElement;\n if (!el || !this.ariaDescriber) return;\n if (this._describedBy) {\n this.ariaDescriber.removeDescription(el, this._describedBy);\n }\n this.ariaDescriber.describe(el, description);\n this._describedBy = description;\n });\n }\n\n // ── Derived signals ──\n\n /** Whether this header is currently the active (sorted) header. */\n readonly isActive = computed(\n () => this.parent?.active() === this.id(),\n );\n\n /** Whether this header is disabled via its own input or the parent directive's `disabled`. */\n readonly isDisabled = computed(\n () => this.headerDisabled() || (this.parent?.disabled() ?? false),\n );\n\n /** Active direction for *this* header (null when not active). */\n readonly effectiveDirection = computed<SortDirection>(() =>\n this.isActive() ? (this.parent?.direction() ?? null) : null,\n );\n\n /** `aria-sort` value: `'ascending' | 'descending' | 'none'`. */\n readonly ariaSort = computed(() => {\n const d = this.effectiveDirection();\n if (d === 'asc') return 'ascending';\n if (d === 'desc') return 'descending';\n return 'none';\n });\n\n /** Whether the arrow element should render. Active headers always render; inactive render when not disabled so hover reveals the affordance. */\n readonly renderArrow = computed(() => this.isActive() || !this.isDisabled());\n\n // ── Variant class computation ──\n\n private readonly variantResult = computed(() =>\n sortHeaderVariants({\n size: this.size(),\n active: this.isActive(),\n direction: this.effectiveDirection() ?? 'none',\n disabled: this.isDisabled(),\n }),\n );\n\n /** @internal */\n readonly hostClasses = computed(() => this.variantResult().host());\n\n /** @internal */\n readonly containerClasses = computed(() => this.variantResult().container());\n\n /** @internal */\n readonly labelClasses = computed(() => this.variantResult().label());\n\n /** @internal */\n readonly arrowClasses = computed(() => {\n const base = this.variantResult().arrow();\n if (!this.isActive()) return base;\n return `${base} ${ARROW_ACTIVE_COLOR[this.color()]}`;\n });\n\n /** @internal */\n readonly arrowIconClasses = computed(() => this.variantResult().arrowIcon());\n\n // ── Lifecycle ──\n\n ngOnInit(): void {\n this.parent?.register(this.id());\n }\n\n ngAfterViewInit(): void {\n const el = this.containerRef()?.nativeElement;\n if (el) {\n this.focusMonitor.monitor(el, true);\n }\n }\n\n ngOnDestroy(): void {\n const el = this.containerRef()?.nativeElement;\n if (el) {\n this.focusMonitor.stopMonitoring(el);\n if (this._describedBy && this.ariaDescriber) {\n this.ariaDescriber.removeDescription(el, this._describedBy);\n }\n }\n this.parent?.deregister(this.id());\n }\n\n // ── Event handlers ──\n\n /** @internal */\n handleClick(): void {\n if (this.isDisabled()) return;\n this.parent?.sort(snapshot(this));\n }\n\n /** @internal */\n handleKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.parent?.sort(snapshot(this));\n }\n }\n}\n","<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"isDisabled() ? null : 'button'\"\n [attr.tabindex]=\"isDisabled() ? null : 0\"\n>\n @if (arrowPosition() === 'before' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n <span [class]=\"labelClasses()\"><ng-content /></span>\n @if (arrowPosition() === 'after' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n</div>\n\n<ng-template #arrow>\n <span [class]=\"arrowClasses()\" data-tw-sort-arrow>\n <ng-content select=\"[twSortHeaderIcon]\">\n <svg\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n [class]=\"arrowIconClasses()\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </ng-content>\n </span>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-sort.mjs","sources":["../../../projects/ngx-tw/sort/sort.ts","../../../projects/ngx-tw/sort/sort-header.ts","../../../projects/ngx-tw/sort/sort-header.html","../../../projects/ngx-tw/sort/cdevhub-ngx-tw-sort.ts"],"sourcesContent":["import {\n Directive,\n input,\n isDevMode,\n model,\n output,\n} from '@angular/core';\nimport { TW_SORT_HANDLE } from '@cdevhub/ngx-tw/core';\n\n/** Sort direction. `null` represents the cleared (unsorted) state. */\nexport type SortDirection = 'asc' | 'desc' | null;\n\n/** Shape that `SortHeaderComponent` implements when registering with a parent `SortDirective`. */\nexport interface TwSortable {\n /** Unique id identifying the column or field this header sorts. */\n readonly id: string;\n /** Header-level starting direction. `undefined` falls back to the parent directive's `start`. */\n readonly start: 'asc' | 'desc' | undefined;\n /** Header-level override for disabling the cleared state in the cycle. `undefined` falls back to the parent directive's `disableClear`. */\n readonly disableClear: boolean | undefined;\n /** Whether this header is disabled. */\n readonly disabled: boolean;\n}\n\n/** Payload emitted by `SortDirective.sortChange` whenever a user interaction changes the sort state. */\nexport interface TwSortEvent {\n /** The new active header id, or `null` when the sort was cleared. */\n active: string | null;\n /** The new sort direction. */\n direction: SortDirection;\n /** Snapshot of the previous state, before this change. */\n previous: {\n /** Previous active header id. */\n active: string | null;\n /** Previous sort direction. */\n direction: SortDirection;\n };\n}\n\n/**\n * Returns the direction cycle for a given starting direction and disable-clear flag.\n * @internal Exported for unit tests; not part of the public API.\n */\nexport function getSortDirectionCycle(\n start: 'asc' | 'desc',\n disableClear: boolean,\n): SortDirection[] {\n const cycle: SortDirection[] = start === 'desc' ? ['desc', 'asc'] : ['asc', 'desc'];\n if (!disableClear) cycle.push(null);\n return cycle;\n}\n\n/**\n * Container directive that holds the current sort state (`active` id + `direction`) and\n * coordinates with child `SortHeaderComponent` instances. Compose with any rendering layer —\n * tables, lists, custom grids — by placing child headers inside an element marked `[twSort]`.\n *\n * @remarks\n * **`tw*` input/output aliases.** Every `input()` / `output()` / `model()` on this directive\n * carries a `tw*` alias (e.g. `'twSortActive'`, `'twSortDirection'`, `'twSortChange'`). The\n * aliases namespace bindings under the directive selector so they cannot collide with\n * attributes the host element already owns (a `<table>` consumer binding `[active]` would\n * otherwise be ambiguous). This mirrors Angular Material's `mat*` aliasing precedent on\n * `MatSort`. Compodoc surfaces both the field name and the alias; consumer templates MUST\n * bind via the aliased name (`[twSortActive]`, `(twSortChange)`, …). Removing an alias is a\n * breaking API change requiring a major version bump.\n */\n@Directive({\n selector: '[twSort]',\n exportAs: 'twSort',\n host: {\n 'class': 'tw-sort',\n },\n providers: [{ provide: TW_SORT_HANDLE, useExisting: SortDirective }],\n})\nexport class SortDirective {\n /** The id of the currently sorted header, or `null` when nothing is sorted. Two-way bindable via `[(twSortActive)]`. Defaults to `null`. */\n readonly active = model<string | null>(null, { alias: 'twSortActive' });\n\n /** Current sort direction. `null` represents the cleared state. Two-way bindable via `[(twSortDirection)]`. Defaults to `null`. */\n readonly direction = model<SortDirection>(null, { alias: 'twSortDirection' });\n\n /** Starting direction used when a header becomes active for the first time. Per-header `start` overrides this. Defaults to `'asc'`. Alias: `twSortStart`. */\n readonly start = input<'asc' | 'desc'>('asc', { alias: 'twSortStart' });\n\n /** When true, the direction cycle skips the cleared (`null`) state — headers toggle between `'asc'` and `'desc'` only. Per-header `disableClear` overrides this. Defaults to `false`. Alias: `twSortDisableClear`. */\n readonly disableClear = input<boolean>(false, { alias: 'twSortDisableClear' });\n\n /** When true, all child sort headers are disabled. Defaults to `false`. Alias: `twSortDisabled`. */\n readonly disabled = input<boolean>(false, { alias: 'twSortDisabled' });\n\n /** Fires whenever the user changes `active` or `direction` by interacting with a header. Programmatic writes to `[(twSortActive)]` / `[(twSortDirection)]` do NOT emit. Alias: `twSortChange`. */\n readonly sortChange = output<TwSortEvent>({ alias: 'twSortChange' });\n\n private readonly sortableIds = new Set<string>();\n\n /** Registers a header id so duplicates can be detected. Called by `SortHeaderComponent` on init. Throws in dev mode if another header with the same id is already registered. */\n register(id: string): void {\n if (isDevMode()) {\n if (!id) {\n throw new Error('tw-sort-header must be provided with a unique id.');\n }\n if (this.sortableIds.has(id)) {\n throw new Error(\n `Cannot have two tw-sort-header elements with the same id (${id}).`,\n );\n }\n }\n this.sortableIds.add(id);\n }\n\n /** Deregisters a header id. Called on destroy. */\n deregister(id: string): void {\n this.sortableIds.delete(id);\n }\n\n /** Cycles the direction for the given header and emits `sortChange`. No-op when the directive or the header is disabled. */\n sort(sortable: TwSortable): void {\n if (this.disabled() || sortable.disabled) return;\n\n const previousActive = this.active();\n const previousDirection = this.direction();\n\n let nextActive: string | null;\n let nextDirection: SortDirection;\n\n if (previousActive !== sortable.id) {\n nextActive = sortable.id;\n nextDirection = sortable.start ?? this.start();\n } else {\n nextDirection = this.getNextSortDirection(sortable);\n nextActive = nextDirection === null ? null : sortable.id;\n }\n\n this.active.set(nextActive);\n this.direction.set(nextDirection);\n\n this.sortChange.emit({\n active: nextActive,\n direction: nextDirection,\n previous: {\n active: previousActive,\n direction: previousDirection,\n },\n });\n }\n\n /** Returns the next direction in the cycle for the given header, based on the current state and header/parent overrides. */\n getNextSortDirection(sortable: TwSortable): SortDirection {\n const start = sortable.start ?? this.start();\n const disableClear = sortable.disableClear ?? this.disableClear();\n const cycle = getSortDirectionCycle(start, disableClear);\n\n // If the header is not currently active, the next direction is the starting one.\n if (this.active() !== sortable.id) {\n return start;\n }\n\n const currentIndex = cycle.indexOf(this.direction());\n if (currentIndex < 0) {\n return start;\n }\n return cycle[(currentIndex + 1) % cycle.length] as SortDirection;\n }\n}\n","import {\n type AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n isDevMode,\n type OnDestroy,\n type OnInit,\n viewChild,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { AriaDescriber, FocusMonitor } from '@angular/cdk/a11y';\nimport { tv } from 'tailwind-variants';\nimport type { TwColor, TwSize } from '@cdevhub/ngx-tw/core';\nimport { SortDirective, type SortDirection, type TwSortable } from './sort';\n\nfunction snapshot(self: SortHeaderComponent): TwSortable {\n return {\n id: self.id(),\n start: self.start(),\n disableClear: self.disableClear(),\n disabled: self.isDisabled(),\n };\n}\n\n/** Position of the sort arrow relative to the header label. */\nexport type TwSortArrowPosition = 'before' | 'after';\n\n/**\n * Whether the host element exposes a `columnheader` / `rowheader` role, which are the only two\n * roles `aria-sort` is valid on. A static `role` attribute wins over the tag name; a `[attr.role]`\n * *binding* is not visible yet at construction time and is deliberately not consulted.\n */\nfunction isHeaderCell(el: HTMLElement): boolean {\n const role = el.getAttribute('role');\n if (role !== null) return role === 'columnheader' || role === 'rowheader';\n return el.tagName === 'TH';\n}\n\n/**\n * Whether the host element is already an interactive control. When it is, the inner container must\n * NOT also become one — two nested widgets is an axe `nested-interactive` failure and leaves\n * assistive tech with an ambiguous activation target.\n */\nfunction isInteractiveHost(el: HTMLElement): boolean {\n const role = el.getAttribute('role');\n if (role === 'button' || role === 'link') return true;\n const tag = el.tagName;\n return tag === 'BUTTON' || (tag === 'A' && el.hasAttribute('href'));\n}\n\n// ── Static per-color maps (Tailwind v4 requires literal class strings for its JIT scanner). ──\n\nconst ARROW_ACTIVE_COLOR: Record<TwColor, string> = {\n primary: 'text-primary-600',\n secondary: 'text-secondary-600',\n accent: 'text-accent-600',\n neutral: 'text-fg',\n info: 'text-info-600',\n success: 'text-success-600',\n warning: 'text-warning-600',\n error: 'text-error-600',\n};\n\n// ── tv() config ──\n\nconst sortHeaderVariants = tv(\n {\n slots: {\n host: 'group select-none',\n // `align-top` is load-bearing, not cosmetic. The container is an\n // inline-level box inside the host block, so without it the host's line\n // box is `strut ∪ baseline-aligned container` — at `xs` the strut's\n // descent hangs 2px below the 24px control, and the row measures 26px.\n // `vertical-align: top` flushes the box to the line-box top, making the\n // host exactly the pinned height at every size. Inert at sm–xl, where the\n // control already exceeds the strut.\n container:\n 'inline-flex align-top items-center gap-1.5 cursor-pointer rounded-md font-medium text-fg transition-colors duration-normal motion-reduce:transition-none hover:bg-surface-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',\n label: 'min-w-0',\n arrow:\n 'inline-flex shrink-0 items-center justify-center transition-opacity duration-normal motion-reduce:transition-none',\n arrowIcon:\n 'shrink-0 transition-transform duration-normal motion-reduce:transition-none',\n },\n variants: {\n size: {\n // Heights are pinned to the control scale (docs/vertical-rhythm.md):\n // 24 / 32 / 36 / 44 / 48. Vertical padding is deleted deliberately —\n // `items-center` on the container does the centring, and leaving `py-*`\n // alive would fight the pinned height under `box-sizing: border-box`.\n xs: {\n container: 'px-2 text-xs h-6',\n // `size-3.5` half-step: only icon size aligning with text-xs without\n // dominating it; codified per CLAUDE.md.\n arrowIcon: 'size-3.5',\n },\n sm: {\n container: 'px-2.5 text-sm h-8',\n arrowIcon: 'size-4',\n },\n md: {\n container: 'px-3 text-sm h-9',\n arrowIcon: 'size-4',\n },\n lg: {\n container: 'px-4 text-base h-11',\n arrowIcon: 'size-5',\n },\n xl: {\n container: 'px-5 text-base h-12',\n arrowIcon: 'size-5',\n },\n },\n active: {\n true: {\n container: 'text-fg',\n arrow: 'opacity-100',\n },\n false: {\n container: 'text-fg-muted',\n arrow:\n 'opacity-0 group-hover:opacity-50 group-focus-within:opacity-50',\n },\n },\n direction: {\n asc: { arrowIcon: 'rotate-180' },\n desc: { arrowIcon: 'rotate-0' },\n none: { arrowIcon: 'rotate-0' },\n },\n disabled: {\n true: {\n host: 'opacity-50 pointer-events-none',\n container: 'cursor-not-allowed',\n },\n false: {},\n },\n // True when the consumer mounted the header on an element that is\n // already a control (`<button>`, `<a href>`, `[role=\"button\"]`). The\n // inner container then stays inert, so the canonical focus ring has to\n // move onto the host — it is the element that actually receives focus.\n hostControl: {\n true: {\n host: 'rounded-md cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',\n },\n false: {},\n },\n },\n defaultVariants: {\n size: 'md',\n active: false,\n direction: 'none',\n disabled: false,\n hostControl: false,\n },\n },\n { twMerge: true },\n);\n\n/**\n * Turns any element (e.g., `<th>`, `<div>`, `<button>`) into a sortable header under a parent\n * `SortDirective`. Renders the projected label plus an arrow that reflects the current direction.\n * Triggers a sort cycle on click or Enter/Space.\n *\n * @remarks\n * **Where the control and `aria-sort` live depends on the host element.** Both are decided once,\n * at construction, from the host's tag name and its *static* `role` attribute:\n *\n * | Host | `aria-sort` | Interactive element |\n * |---|---|---|\n * | `<th>` / `[role=\"columnheader\"\\|\"rowheader\"]` | on the host | inner container (`role=\"button\"`) |\n * | `<span>` / `<div>` (no role) | **not emitted** | inner container (`role=\"button\"`) |\n * | `<button>` / `<a href>` / `[role=\"button\"\\|\"link\"]` | **not emitted** | the host itself |\n *\n * `aria-sort` is only valid on a `columnheader` / `rowheader`, so emitting it on a `<span>` or\n * `<button>` is an axe `aria-allowed-attr` failure and is silently ignored by assistive tech. When\n * the header is composed *inside* a header cell — `<th><span tw-sort-header>…</span></th>`, the\n * shape `tw-table` generates — the `<th>` owns `aria-sort` (see `tw-column`'s `sortState` input,\n * which auto-resolves against the same `[twSort]` directive). Outside a table there is no\n * `columnheader` to carry the state; the header remains a labelled button and the sort direction\n * is conveyed by the arrow plus the consumer's own live region.\n */\n// Selector is deliberately kebab-case to read naturally as an HTML attribute on\n// host elements (e.g. `<th tw-sort-header>`). Camelcasing it to `twSortHeader`\n// would break the public API and ~20 demo usages.\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: '[tw-sort-header]',\n exportAs: 'twSortHeader',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgTemplateOutlet],\n templateUrl: './sort-header.html',\n host: {\n '[class]': 'hostClasses()',\n '[attr.aria-sort]': 'hostAriaSort()',\n // Sort state as a data attribute, on EVERY host shape.\n //\n // `aria-sort` is only legal on a header cell, so on a span or button host\n // it is deliberately absent — which left the sort state with no stable,\n // valid hook at all on those hosts. The direction was readable only from\n // the arrow's rotation utility class, which is implementation detail no\n // test or consumer should depend on. A `data-*` attribute is valid on any\n // element and carries no ARIA semantics, so it states the same thing\n // without lying to assistive tech. Values mirror `aria-sort` exactly, so\n // a reader does not have to learn a second vocabulary.\n '[attr.data-sort-direction]': 'ariaSort()',\n '[attr.aria-disabled]': 'isDisabled() ? \"true\" : null',\n '(click)': 'handleClick()',\n '(keydown)': 'handleKeydown($event)',\n },\n})\nexport class SortHeaderComponent implements OnInit, AfterViewInit, OnDestroy {\n /** Unique id for this header. Required — identifies the field/column this header sorts. */\n readonly id = input.required<string>();\n\n /** Overrides the parent directive's `start` for this header only. `undefined` inherits from the parent. */\n readonly start = input<'asc' | 'desc' | undefined>(undefined);\n\n /** Overrides the parent directive's `disableClear` for this header only. `undefined` inherits from the parent. */\n readonly disableClear = input<boolean | undefined>(undefined);\n\n /** When true, this header is disabled — clicks and keyboard activation are ignored. Defaults to `false`. */\n readonly headerDisabled = input<boolean>(false, { alias: 'disabled' });\n\n /** Whether the sort arrow renders `'before'` or `'after'` the projected label. Defaults to `'after'`. */\n readonly arrowPosition = input<TwSortArrowPosition>('after');\n\n /** Semantic color used to tint the arrow when this header is active. Defaults to `'primary'`. */\n readonly color = input<TwColor>('primary');\n\n /** Controls padding and font size. Uses the shared `TwSize` scale. Defaults to `'md'`. */\n readonly size = input<TwSize>('md');\n\n /** Accessible description applied via `AriaDescriber` so screen readers announce the sort action alongside the header text. Defaults to `'Sort'`. */\n readonly sortActionDescription = input<string>('Sort');\n\n private readonly parent = inject(SortDirective, { optional: true });\n private readonly focusMonitor = inject(FocusMonitor);\n private readonly ariaDescriber = inject(AriaDescriber, { optional: true });\n\n private readonly hostEl: HTMLElement = inject(ElementRef).nativeElement;\n\n /**\n * Whether the host element can legally carry `aria-sort`. Resolved once, at construction — the\n * host's tag name and static attributes are already applied by then, and a later `[attr.role]`\n * binding would make this timing-dependent.\n */\n private readonly hostIsHeaderCell = isHeaderCell(this.hostEl);\n\n /** Whether the host is already a control, in which case the inner container must stay inert. */\n private readonly hostIsControl = isInteractiveHost(this.hostEl);\n\n private readonly containerRef =\n viewChild<ElementRef<HTMLElement>>('container');\n\n private _describedBy: string | null = null;\n\n constructor() {\n if (!this.parent && isDevMode()) {\n throw new Error(\n 'tw-sort-header must be placed within a parent element with the twSort directive.',\n );\n }\n\n // Reactive: re-tags the host whenever `sortActionDescription` (a public\n // signal input) changes; consumers may swap the description at runtime for\n // i18n / state-driven copy. Cleanup runs in `ngOnDestroy`. Not a one-shot\n // `afterNextRender`: that would freeze the description at first render and\n // silently ignore subsequent rebinds.\n effect(() => {\n const description = this.sortActionDescription();\n const el = this.controlElement();\n if (!el || !this.ariaDescriber) return;\n if (this._describedBy) {\n this.ariaDescriber.removeDescription(el, this._describedBy);\n }\n this.ariaDescriber.describe(el, description);\n this._describedBy = description;\n });\n }\n\n // ── Derived signals ──\n\n /** Whether this header is currently the active (sorted) header. */\n readonly isActive = computed(\n () => this.parent?.active() === this.id(),\n );\n\n /** Whether this header is disabled via its own input or the parent directive's `disabled`. */\n readonly isDisabled = computed(\n () => this.headerDisabled() || (this.parent?.disabled() ?? false),\n );\n\n /** Active direction for *this* header (null when not active). */\n readonly effectiveDirection = computed<SortDirection>(() =>\n this.isActive() ? (this.parent?.direction() ?? null) : null,\n );\n\n /** `aria-sort` value: `'ascending' | 'descending' | 'none'`. */\n readonly ariaSort = computed(() => {\n const d = this.effectiveDirection();\n if (d === 'asc') return 'ascending';\n if (d === 'desc') return 'descending';\n return 'none';\n });\n\n /**\n * @internal Value actually written to the host's `aria-sort` attribute. `null` — i.e. no\n * attribute — unless the host is a `columnheader` / `rowheader`; see the class remarks.\n */\n readonly hostAriaSort = computed(() =>\n this.hostIsHeaderCell ? this.ariaSort() : null,\n );\n\n /** @internal `role` for the inner container: `'button'` unless the host is already the control. */\n readonly controlRole = computed(() =>\n this.hostIsControl || this.isDisabled() ? null : 'button',\n );\n\n /** @internal `tabindex` for the inner container. Mirrors `controlRole()`. */\n readonly controlTabIndex = computed(() =>\n this.hostIsControl || this.isDisabled() ? null : 0,\n );\n\n /** Whether the arrow element should render. Active headers always render; inactive render when not disabled so hover reveals the affordance. */\n readonly renderArrow = computed(() => this.isActive() || !this.isDisabled());\n\n /**\n * The element that carries focus and the accessible description — the host when the consumer\n * mounted the header on a control, the inner container otherwise.\n */\n private controlElement(): HTMLElement | undefined {\n return this.hostIsControl\n ? this.hostEl\n : this.containerRef()?.nativeElement;\n }\n\n // ── Variant class computation ──\n\n private readonly variantResult = computed(() =>\n sortHeaderVariants({\n size: this.size(),\n active: this.isActive(),\n direction: this.effectiveDirection() ?? 'none',\n disabled: this.isDisabled(),\n hostControl: this.hostIsControl,\n }),\n );\n\n /** @internal */\n readonly hostClasses = computed(() => this.variantResult().host());\n\n /** @internal */\n readonly containerClasses = computed(() => this.variantResult().container());\n\n /** @internal */\n readonly labelClasses = computed(() => this.variantResult().label());\n\n /** @internal */\n readonly arrowClasses = computed(() => {\n const base = this.variantResult().arrow();\n if (!this.isActive()) return base;\n return `${base} ${ARROW_ACTIVE_COLOR[this.color()]}`;\n });\n\n /** @internal */\n readonly arrowIconClasses = computed(() => this.variantResult().arrowIcon());\n\n // ── Lifecycle ──\n\n ngOnInit(): void {\n this.parent?.register(this.id());\n }\n\n ngAfterViewInit(): void {\n const el = this.controlElement();\n if (el) {\n this.focusMonitor.monitor(el, true);\n }\n }\n\n ngOnDestroy(): void {\n const el = this.controlElement();\n if (el) {\n this.focusMonitor.stopMonitoring(el);\n if (this._describedBy && this.ariaDescriber) {\n this.ariaDescriber.removeDescription(el, this._describedBy);\n }\n }\n this.parent?.deregister(this.id());\n }\n\n // ── Event handlers ──\n\n /** @internal */\n handleClick(): void {\n if (this.isDisabled()) return;\n this.parent?.sort(snapshot(this));\n }\n\n /** @internal */\n handleKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.parent?.sort(snapshot(this));\n }\n }\n}\n","<!--\n The container is the interactive control (role=button + tabindex) unless the\n consumer already mounted the header on one - a button, an anchor, or an\n element carrying a static button/link role. Nesting a second widget inside\n such a host is an axe nested-interactive failure and leaves assistive tech\n with two competing activation targets, so controlRole and controlTabIndex\n both resolve to null there and the host keeps the focus ring instead.\n-->\n<div\n #container\n [class]=\"containerClasses()\"\n [attr.role]=\"controlRole()\"\n [attr.tabindex]=\"controlTabIndex()\"\n>\n @if (arrowPosition() === 'before' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n <span [class]=\"labelClasses()\"><ng-content /></span>\n @if (arrowPosition() === 'after' && renderArrow()) {\n <ng-container *ngTemplateOutlet=\"arrow\" />\n }\n</div>\n\n<ng-template #arrow>\n <span [class]=\"arrowClasses()\" data-tw-sort-arrow>\n <ng-content select=\"[twSortHeaderIcon]\">\n <svg\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n [class]=\"arrowIconClasses()\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </ng-content>\n </span>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -293,10 +293,22 @@ function hasMinSizeOverflow(panes, unit, availablePx) {
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
let nextSplitId = 0;
|
|
296
|
+
/**
|
|
297
|
+
* WCAG 2.2 SC 2.5.8 (Target Size Minimum) floor, in CSS px. A gutter thinner
|
|
298
|
+
* than this pads itself out to the floor with transparent padding — see
|
|
299
|
+
* `_gutterHitPad`.
|
|
300
|
+
*/
|
|
301
|
+
const TARGET_SIZE_MIN_PX = 24;
|
|
296
302
|
const splitVariants = tv({
|
|
297
303
|
slots: {
|
|
298
304
|
root: 'flex h-full w-full',
|
|
299
|
-
|
|
305
|
+
// `relative` and `bg-clip-content` are load-bearing for the SC 2.5.8 hit
|
|
306
|
+
// area (see `_gutterHitPad`): the element's border box is padded out to
|
|
307
|
+
// 24px so the pointer target meets the floor, `bg-clip-content` keeps the
|
|
308
|
+
// paint inside the `gutterSize`-thick content box, and `relative` puts
|
|
309
|
+
// the gutter in the positioned-descendant paint phase so its overhang
|
|
310
|
+
// wins hit-testing over the adjacent panes' in-flow content.
|
|
311
|
+
gutter: 'relative shrink-0 bg-border bg-clip-content transition-colors duration-normal motion-reduce:transition-none hover:bg-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
300
312
|
},
|
|
301
313
|
variants: {
|
|
302
314
|
direction: {
|
|
@@ -332,7 +344,7 @@ class SplitComponent {
|
|
|
332
344
|
/** How sizes are expressed and reported. A single unit governs the whole container. Defaults to `'percent'`. */
|
|
333
345
|
unit = input('percent', /* @ts-ignore */
|
|
334
346
|
...(ngDevMode ? [{ debugName: "unit" }] : /* istanbul ignore next */ []));
|
|
335
|
-
/**
|
|
347
|
+
/** Painted thickness of each gutter in pixels, perpendicular to the split axis, and the space it takes from the panes. The pointer target is padded out to a 24px minimum independently of this value (WCAG 2.2 SC 2.5.8), so a thin gutter stays thin without becoming hard to grab. Defaults to `6`. */
|
|
336
348
|
gutterSize = input(6, /* @ts-ignore */
|
|
337
349
|
...(ngDevMode ? [{ debugName: "gutterSize" }] : /* istanbul ignore next */ []));
|
|
338
350
|
/** When true, all resize interactions are disabled. Gutters are not focusable and do not respond to input. Defaults to `false`. */
|
|
@@ -391,8 +403,13 @@ class SplitComponent {
|
|
|
391
403
|
*/
|
|
392
404
|
_sizes = signal([], /* @ts-ignore */
|
|
393
405
|
...(ngDevMode ? [{ debugName: "_sizes" }] : /* istanbul ignore next */ []));
|
|
394
|
-
/**
|
|
395
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Live `dir` resolved through CDK `Directionality`. Read straight from
|
|
408
|
+
* `valueSignal` (CDK 22+) so `_isRtl` re-evaluates on a runtime `dir` flip
|
|
409
|
+
* with no manual subscription mirror. Falls back to 'ltr' when the consumer's
|
|
410
|
+
* injector cannot supply the token (`{ optional: true }`).
|
|
411
|
+
*/
|
|
412
|
+
_cdkDir = computed(() => this._directionality?.valueSignal() ?? 'ltr', /* @ts-ignore */
|
|
396
413
|
...(ngDevMode ? [{ debugName: "_cdkDir" }] : /* istanbul ignore next */ []));
|
|
397
414
|
/** Resolves the effective RTL state: explicit `rtl` input overrides CDK `dir`. */
|
|
398
415
|
_isRtl = computed(() => this.rtl() ?? this._cdkDir() === 'rtl', /* @ts-ignore */
|
|
@@ -412,6 +429,35 @@ class SplitComponent {
|
|
|
412
429
|
...(ngDevMode ? [{ debugName: "_rootClass" }] : /* istanbul ignore next */ []));
|
|
413
430
|
_gutterClass = computed(() => this._classes().gutter(), /* @ts-ignore */
|
|
414
431
|
...(ngDevMode ? [{ debugName: "_gutterClass" }] : /* istanbul ignore next */ []));
|
|
432
|
+
/**
|
|
433
|
+
* @internal
|
|
434
|
+
* Transparent padding added to each side of the gutter so its pointer target
|
|
435
|
+
* reaches the WCAG 2.2 SC 2.5.8 24x24 floor while the painted line stays
|
|
436
|
+
* `gutterSize` thick (the default gutter is 6px — a quarter of the floor).
|
|
437
|
+
*
|
|
438
|
+
* The padding is cancelled by an equal negative margin on the same side, so
|
|
439
|
+
* the gutter's *layout* footprint is still exactly `gutterSize` and the
|
|
440
|
+
* `availableSpace()` math is untouched; only its border box — which is what
|
|
441
|
+
* receives the pointer and what a target-size audit measures — grows. The
|
|
442
|
+
* overhang reaches `_gutterHitPad` px into each neighbouring pane and takes
|
|
443
|
+
* pointer events there (including over a pane's own scrollbar, if it has
|
|
444
|
+
* one); that is the accepted cost of the technique and mirrors how VS Code
|
|
445
|
+
* and every other thin-sash implementation reaches the floor.
|
|
446
|
+
*
|
|
447
|
+
* Zero once `gutterSize` is 24 or more — a gutter that already clears the
|
|
448
|
+
* floor needs no padding and no overhang.
|
|
449
|
+
*/
|
|
450
|
+
_gutterHitPad = computed(() => Math.max(0, (TARGET_SIZE_MIN_PX - this.gutterSize()) / 2), /* @ts-ignore */
|
|
451
|
+
...(ngDevMode ? [{ debugName: "_gutterHitPad" }] : /* istanbul ignore next */ []));
|
|
452
|
+
/** @internal Horizontal half of `_gutterHitPad` — non-zero only for a horizontal split, where the gutter is thin across the X axis. Physical (not logical) properties: the padding is symmetric, so RTL needs no separate handling. */
|
|
453
|
+
_gutterHitPadX = computed(() => this.direction() === 'horizontal' ? this._gutterHitPad() : 0, /* @ts-ignore */
|
|
454
|
+
...(ngDevMode ? [{ debugName: "_gutterHitPadX" }] : /* istanbul ignore next */ []));
|
|
455
|
+
/** @internal Vertical half of `_gutterHitPad` — non-zero only for a vertical split. */
|
|
456
|
+
_gutterHitPadY = computed(() => this.direction() === 'vertical' ? this._gutterHitPad() : 0, /* @ts-ignore */
|
|
457
|
+
...(ngDevMode ? [{ debugName: "_gutterHitPadY" }] : /* istanbul ignore next */ []));
|
|
458
|
+
/** @internal Gutter border-box thickness: `gutterSize` plus the SC 2.5.8 hit padding on both sides, i.e. `max(gutterSize, 24)`. The negative margins give back the padding, so the *layout* footprint stays `gutterSize`. */
|
|
459
|
+
_gutterBoxPx = computed(() => this.gutterSize() + this._gutterHitPad() * 2, /* @ts-ignore */
|
|
460
|
+
...(ngDevMode ? [{ debugName: "_gutterBoxPx" }] : /* istanbul ignore next */ []));
|
|
415
461
|
/** Indices `0..panes.length - 2` representing the gutter between pane[i] and pane[i+1]. */
|
|
416
462
|
_gutterIndices = computed(() => {
|
|
417
463
|
const n = this._panes().length;
|
|
@@ -446,15 +492,6 @@ class SplitComponent {
|
|
|
446
492
|
const containerPx = untracked(() => this._containerSizePx());
|
|
447
493
|
this._onPanesChange(panes, containerPx);
|
|
448
494
|
});
|
|
449
|
-
// Reflect CDK Directionality changes into the local signal so the computed
|
|
450
|
-
// _isRtl re-evaluates and keyboard / pointer handlers honour live `dir`
|
|
451
|
-
// attribute changes (e.g. when an ancestor flips the page direction).
|
|
452
|
-
if (this._directionality) {
|
|
453
|
-
const sub = this._directionality.change.subscribe(value => {
|
|
454
|
-
this._cdkDir.set(value);
|
|
455
|
-
});
|
|
456
|
-
this._destroyRef.onDestroy(() => sub.unsubscribe());
|
|
457
|
-
}
|
|
458
495
|
}
|
|
459
496
|
// ── Internal resize handler ─────────────────────────────────────────────────
|
|
460
497
|
/**
|
|
@@ -1021,9 +1058,17 @@ class SplitComponent {
|
|
|
1021
1058
|
[attr.tabindex]="disabled() ? -1 : 0"
|
|
1022
1059
|
[class]="_gutterClass()"
|
|
1023
1060
|
[style.order]="i * 2 + 1"
|
|
1024
|
-
[style.flex-basis.px]="
|
|
1025
|
-
[style.min-width.px]="direction() === 'horizontal' ?
|
|
1026
|
-
[style.min-height.px]="direction() === 'vertical' ?
|
|
1061
|
+
[style.flex-basis.px]="_gutterBoxPx()"
|
|
1062
|
+
[style.min-width.px]="direction() === 'horizontal' ? _gutterBoxPx() : 0"
|
|
1063
|
+
[style.min-height.px]="direction() === 'vertical' ? _gutterBoxPx() : 0"
|
|
1064
|
+
[style.padding-left.px]="_gutterHitPadX()"
|
|
1065
|
+
[style.padding-right.px]="_gutterHitPadX()"
|
|
1066
|
+
[style.padding-top.px]="_gutterHitPadY()"
|
|
1067
|
+
[style.padding-bottom.px]="_gutterHitPadY()"
|
|
1068
|
+
[style.margin-left.px]="-_gutterHitPadX()"
|
|
1069
|
+
[style.margin-right.px]="-_gutterHitPadX()"
|
|
1070
|
+
[style.margin-top.px]="-_gutterHitPadY()"
|
|
1071
|
+
[style.margin-bottom.px]="-_gutterHitPadY()"
|
|
1027
1072
|
(pointerdown)="_onGutterPointerDown($event, i)"
|
|
1028
1073
|
(keydown)="_onGutterKeydown($event, i)"
|
|
1029
1074
|
></div>
|
|
@@ -1054,9 +1099,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1054
1099
|
[attr.tabindex]="disabled() ? -1 : 0"
|
|
1055
1100
|
[class]="_gutterClass()"
|
|
1056
1101
|
[style.order]="i * 2 + 1"
|
|
1057
|
-
[style.flex-basis.px]="
|
|
1058
|
-
[style.min-width.px]="direction() === 'horizontal' ?
|
|
1059
|
-
[style.min-height.px]="direction() === 'vertical' ?
|
|
1102
|
+
[style.flex-basis.px]="_gutterBoxPx()"
|
|
1103
|
+
[style.min-width.px]="direction() === 'horizontal' ? _gutterBoxPx() : 0"
|
|
1104
|
+
[style.min-height.px]="direction() === 'vertical' ? _gutterBoxPx() : 0"
|
|
1105
|
+
[style.padding-left.px]="_gutterHitPadX()"
|
|
1106
|
+
[style.padding-right.px]="_gutterHitPadX()"
|
|
1107
|
+
[style.padding-top.px]="_gutterHitPadY()"
|
|
1108
|
+
[style.padding-bottom.px]="_gutterHitPadY()"
|
|
1109
|
+
[style.margin-left.px]="-_gutterHitPadX()"
|
|
1110
|
+
[style.margin-right.px]="-_gutterHitPadX()"
|
|
1111
|
+
[style.margin-top.px]="-_gutterHitPadY()"
|
|
1112
|
+
[style.margin-bottom.px]="-_gutterHitPadY()"
|
|
1060
1113
|
(pointerdown)="_onGutterPointerDown($event, i)"
|
|
1061
1114
|
(keydown)="_onGutterKeydown($event, i)"
|
|
1062
1115
|
></div>
|