@lifecorner/angular-ui-kit 3.36.0 → 3.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,6 +65,29 @@ var StateStripComponent = class StateStripComponent {
65
65
  onPointerLeave() {
66
66
  this.hovered.set(null);
67
67
  }
68
+ scrubbed = null;
69
+ onScrubMove(event) {
70
+ const index = this.cellAt(event.clientX);
71
+ if (index === this.scrubbed) return;
72
+ this.scrubbed = index;
73
+ this.hovered.set(index);
74
+ }
75
+ onScrubUp(event) {
76
+ const index = this.cellAt(event.clientX);
77
+ if (index !== null) this.cellEls()[index]?.nativeElement.focus();
78
+ }
79
+ onScrubLeave() {
80
+ this.scrubbed = null;
81
+ this.hovered.set(null);
82
+ }
83
+ cellAt(x) {
84
+ const cells = this.cellEls();
85
+ for (let i = 0; i < cells.length; i++) {
86
+ const box = cells[i].nativeElement.getBoundingClientRect();
87
+ if (x >= box.left && x < box.right) return i;
88
+ }
89
+ return null;
90
+ }
68
91
  onKeydown(event) {
69
92
  const last = this.cells().length - 1;
70
93
  const from = this.current();
@@ -143,8 +166,8 @@ var StateStripComponent = class StateStripComponent {
143
166
  isSignal: true
144
167
  }],
145
168
  ngImport: i0,
146
- template: "@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n",
147
- styles: [":host{display:block;position:relative}:host(.lc-state-strip--empty){display:none}.lc-state-strip__grid{display:block}.lc-state-strip__row{display:flex;block-size:var(--lc-state-strip-height, 2rem)}.lc-state-strip__cell{position:relative;flex:1 1 0;min-inline-size:0;max-inline-size:var(--lc-state-strip-segment-max-width, 1.5rem)}.lc-state-strip__cell:hover{background-color:var(--color-surface-hover)}.lc-state-strip__cell:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-state-strip__mark{position:absolute;inset-inline:min(3px,25%)}.lc-state-strip__mark--success{top:12.5%;height:34.375%;background-color:var(--color-text-success)}.lc-state-strip__mark--error{top:53.125%;height:34.375%;background-color:var(--color-text-error)}.lc-state-strip__mark--warning{top:25%;height:50%;background-color:var(--color-text-warning)}.lc-state-strip__mark--neutral{top:calc(50% - 1px);height:2px;background-color:var(--color-field-border)}.lc-state-strip__name{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.lc-state-strip__caption{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:1;inline-size:max-content;max-inline-size:100%;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-md);background-color:var(--color-text-primary);color:var(--color-surface);box-shadow:var(--elevation-2);font-size:var(--font-size-xs);line-height:1.5;overflow-wrap:break-word;hyphens:auto;pointer-events:none}@media(forced-colors:active){.lc-state-strip__mark{forced-color-adjust:none;background-color:CanvasText}.lc-state-strip__mark--neutral{background-color:GrayText}.lc-state-strip__cell:focus-visible{outline-color:Highlight}.lc-state-strip__caption{border:1px solid CanvasText}}\n"],
169
+ template: "@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [class.lc-state-strip__cell--hovered]=\"i === hovered()\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n <!-- 3.37: the pointer path. One layer over the row picks the run under the pointer, so narrow cells need no 24 px target. -->\n <div\n class=\"lc-state-strip__scrub\"\n aria-hidden=\"true\"\n (pointerdown)=\"onScrubMove($event)\"\n (pointermove)=\"onScrubMove($event)\"\n (pointerup)=\"onScrubUp($event)\"\n (pointerleave)=\"onScrubLeave()\"\n (pointercancel)=\"onScrubLeave()\"\n ></div>\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n",
170
+ styles: [":host{display:block;position:relative}:host(.lc-state-strip--empty){display:none}.lc-state-strip__grid{display:block}.lc-state-strip__row{display:flex;block-size:var(--lc-state-strip-height, 2rem)}.lc-state-strip__cell{position:relative;flex:1 1 0;min-inline-size:0;max-inline-size:var(--lc-state-strip-segment-max-width, 1.5rem)}.lc-state-strip__cell:hover{background-color:var(--color-surface-hover)}.lc-state-strip__cell:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-state-strip__mark{position:absolute;inset-inline:min(3px,25%)}.lc-state-strip__mark--success{top:12.5%;height:34.375%;background-color:var(--color-text-success)}.lc-state-strip__mark--error{top:53.125%;height:34.375%;background-color:var(--color-text-error)}.lc-state-strip__mark--warning{top:25%;height:50%;background-color:var(--color-text-warning)}.lc-state-strip__mark--neutral{top:calc(50% - 1px);height:2px;background-color:var(--color-field-border)}.lc-state-strip__name{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.lc-state-strip__caption{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:1;inline-size:max-content;max-inline-size:100%;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-md);background-color:var(--color-text-primary);color:var(--color-surface);box-shadow:var(--elevation-2);font-size:var(--font-size-xs);line-height:1.5;overflow-wrap:break-word;hyphens:auto;pointer-events:none}.lc-state-strip__row{position:relative}.lc-state-strip__cell--hovered{background-color:var(--color-surface-hover)}.lc-state-strip__scrub{position:absolute;inset:0;touch-action:pan-y pinch-zoom}@media(forced-colors:active){.lc-state-strip__mark{forced-color-adjust:none;background-color:CanvasText}.lc-state-strip__mark--neutral{background-color:GrayText}.lc-state-strip__cell:focus-visible{outline-color:Highlight}.lc-state-strip__caption{border:1px solid CanvasText}}\n"],
148
171
  changeDetection: i0.ChangeDetectionStrategy.OnPush
149
172
  });
150
173
  };
@@ -163,8 +186,8 @@ i0.ɵɵngDeclareClassMetadata({
163
186
  class: "lc-state-strip",
164
187
  "[class.lc-state-strip--empty]": "!cells().length"
165
188
  },
166
- template: "@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n",
167
- styles: [":host{display:block;position:relative}:host(.lc-state-strip--empty){display:none}.lc-state-strip__grid{display:block}.lc-state-strip__row{display:flex;block-size:var(--lc-state-strip-height, 2rem)}.lc-state-strip__cell{position:relative;flex:1 1 0;min-inline-size:0;max-inline-size:var(--lc-state-strip-segment-max-width, 1.5rem)}.lc-state-strip__cell:hover{background-color:var(--color-surface-hover)}.lc-state-strip__cell:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-state-strip__mark{position:absolute;inset-inline:min(3px,25%)}.lc-state-strip__mark--success{top:12.5%;height:34.375%;background-color:var(--color-text-success)}.lc-state-strip__mark--error{top:53.125%;height:34.375%;background-color:var(--color-text-error)}.lc-state-strip__mark--warning{top:25%;height:50%;background-color:var(--color-text-warning)}.lc-state-strip__mark--neutral{top:calc(50% - 1px);height:2px;background-color:var(--color-field-border)}.lc-state-strip__name{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.lc-state-strip__caption{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:1;inline-size:max-content;max-inline-size:100%;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-md);background-color:var(--color-text-primary);color:var(--color-surface);box-shadow:var(--elevation-2);font-size:var(--font-size-xs);line-height:1.5;overflow-wrap:break-word;hyphens:auto;pointer-events:none}@media(forced-colors:active){.lc-state-strip__mark{forced-color-adjust:none;background-color:CanvasText}.lc-state-strip__mark--neutral{background-color:GrayText}.lc-state-strip__cell:focus-visible{outline-color:Highlight}.lc-state-strip__caption{border:1px solid CanvasText}}\n"]
189
+ template: "@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [class.lc-state-strip__cell--hovered]=\"i === hovered()\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n <!-- 3.37: the pointer path. One layer over the row picks the run under the pointer, so narrow cells need no 24 px target. -->\n <div\n class=\"lc-state-strip__scrub\"\n aria-hidden=\"true\"\n (pointerdown)=\"onScrubMove($event)\"\n (pointermove)=\"onScrubMove($event)\"\n (pointerup)=\"onScrubUp($event)\"\n (pointerleave)=\"onScrubLeave()\"\n (pointercancel)=\"onScrubLeave()\"\n ></div>\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n",
190
+ styles: [":host{display:block;position:relative}:host(.lc-state-strip--empty){display:none}.lc-state-strip__grid{display:block}.lc-state-strip__row{display:flex;block-size:var(--lc-state-strip-height, 2rem)}.lc-state-strip__cell{position:relative;flex:1 1 0;min-inline-size:0;max-inline-size:var(--lc-state-strip-segment-max-width, 1.5rem)}.lc-state-strip__cell:hover{background-color:var(--color-surface-hover)}.lc-state-strip__cell:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-state-strip__mark{position:absolute;inset-inline:min(3px,25%)}.lc-state-strip__mark--success{top:12.5%;height:34.375%;background-color:var(--color-text-success)}.lc-state-strip__mark--error{top:53.125%;height:34.375%;background-color:var(--color-text-error)}.lc-state-strip__mark--warning{top:25%;height:50%;background-color:var(--color-text-warning)}.lc-state-strip__mark--neutral{top:calc(50% - 1px);height:2px;background-color:var(--color-field-border)}.lc-state-strip__name{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.lc-state-strip__caption{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:1;inline-size:max-content;max-inline-size:100%;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-md);background-color:var(--color-text-primary);color:var(--color-surface);box-shadow:var(--elevation-2);font-size:var(--font-size-xs);line-height:1.5;overflow-wrap:break-word;hyphens:auto;pointer-events:none}.lc-state-strip__row{position:relative}.lc-state-strip__cell--hovered{background-color:var(--color-surface-hover)}.lc-state-strip__scrub{position:absolute;inset:0;touch-action:pan-y pinch-zoom}@media(forced-colors:active){.lc-state-strip__mark{forced-color-adjust:none;background-color:CanvasText}.lc-state-strip__mark--neutral{background-color:GrayText}.lc-state-strip__cell:focus-visible{outline-color:Highlight}.lc-state-strip__caption{border:1px solid CanvasText}}\n"]
168
191
  }]
169
192
  }],
170
193
  propDecorators: {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-state-strip.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/state-strip/state-strip.component.ts","../../../../libs/angular-ui-kit/src/lib/state-strip/state-strip.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n input,\n linkedSignal,\n signal,\n viewChildren,\n} from '@angular/core';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\n/** State of one run in `lc-state-strip` (3.36). */\nexport type StateStripState = 'success' | 'warning' | 'error' | 'neutral';\n\n/** One run of `lc-state-strip`: its state, and a `label` that names it („Mo 06.10., 06:00“). */\nexport interface StateStripSegment {\n state: StateStripState;\n label: string;\n}\n\n/** The words of the four states, for the counts and the segment names. */\nexport type StateStripLabels = Record<StateStripState, string>;\n\n/** English state words of `lc-state-strip`; pass `stateLabels` for another language. */\nexport const DEFAULT_STATE_STRIP_LABELS: Readonly<StateStripLabels> = {\n success: 'succeeded',\n warning: 'with warnings',\n error: 'failed',\n neutral: 'pending',\n};\n\n/** The order of the counts in the accessible name. */\nconst STATES: readonly StateStripState[] = ['success', 'warning', 'error', 'neutral'];\n\n/**\n * The states of the last runs at a glance (3.36, quellen row 1): one segment\n * per run, oldest first. The state is a shape, not only a colour: success a\n * block in the upper half, error one in the lower half, warning a centred\n * block, neutral a flat dash, so the strip reads without hue and in forced\n * colours.\n *\n * Accessibility: an APG single-row grid. The strip is named by `ariaLabel`\n * and the count per state („Letzte 10 Ladeläufe, 9 geladen, 1\n * fehlgeschlagen“); each segment is a grid cell named by its `label` and\n * state word. One Tab stop (the newest run first), Arrow Left/Right, Home and\n * End move between the runs, Escape hides the caption. Hovering or focusing a\n * segment shows its name below the strip (hidden from screen readers, which\n * read the cell).\n *\n * Without segments it renders nothing. The segments are in the prerendered\n * HTML. CSS hooks: `--lc-state-strip-height` (default `2rem`) and\n * `--lc-state-strip-segment-max-width` (default `1.5rem`; more segments than\n * fit share the width, never overflowing).\n *\n * @example\n * ```html\n * <lc-state-strip\n * ariaLabel=\"Letzte 10 Ladeläufe\"\n * [stateLabels]=\"{ success: 'geladen', warning: 'teilweise geladen', error: 'fehlgeschlagen', neutral: 'ausstehend' }\"\n * [segments]=\"runs\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-state-strip',\n standalone: true,\n templateUrl: './state-strip.component.html',\n styleUrl: './state-strip.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-state-strip',\n '[class.lc-state-strip--empty]': '!cells().length',\n },\n})\nexport class StateStripComponent {\n /**\n * The runs, in order, oldest first: one segment each. Empty renders nothing\n * (no box, no accessibility node).\n * @default []\n */\n readonly segments = input<readonly StateStripSegment[]>([]);\n\n /**\n * Start of the strip's accessible name; the count per state follows it\n * („Letzte 10 Ladeläufe, 9 geladen, 1 fehlgeschlagen“).\n * @default ''\n */\n readonly ariaLabel = input<string>('');\n\n /**\n * The state words for the counts and the segment names, over the English\n * defaults (`DEFAULT_STATE_STRIP_LABELS`: succeeded, with warnings, failed,\n * pending). German: `{ success: 'geladen', warning: 'teilweise geladen',\n * error: 'fehlgeschlagen', neutral: 'ausstehend' }`.\n * @default {}\n */\n readonly stateLabels = input<Partial<StateStripLabels>>({});\n\n private readonly cellEls = viewChildren<ElementRef<HTMLElement>>('cell');\n\n /** The state words in use. */\n private readonly words = computed<StateStripLabels>(() => ({ ...DEFAULT_STATE_STRIP_LABELS, ...this.stateLabels() }));\n\n /** One entry per segment: its state (unknown states draw as neutral) and its name. */\n protected readonly cells = computed(() => {\n const words = this.words();\n return (this.segments() ?? []).map((segment) => {\n const state: StateStripState = STATES.includes(segment.state) ? segment.state : 'neutral';\n return { state, name: nameWithText(segment.label ?? '', words[state]) };\n });\n });\n\n /** The strip's name: `ariaLabel`, then the count per state (0 left out). */\n protected readonly gridLabel = computed(() => {\n const words = this.words();\n const counts = STATES.map((state) => [state, this.cells().filter((c) => c.state === state).length] as const)\n .filter(([, count]) => count > 0)\n .map(([state, count]) => `${count} ${words[state]}`)\n .join(', ');\n const label = this.ariaLabel().trim();\n return label ? `${label}, ${counts}` : counts;\n });\n\n /** The roving Tab stop: the newest run until the user moves. */\n protected readonly current = linkedSignal(() => Math.max(0, this.cells().length - 1));\n\n /** The segment under the pointer, the one with focus, and whether Escape hid the caption. */\n private readonly hovered = signal<number | null>(null);\n private readonly focused = signal<number | null>(null);\n private readonly dismissed = signal(false);\n\n /** The segment whose name the caption shows, if any. */\n private readonly captionIndex = computed(() => {\n const hovered = this.hovered();\n if (hovered !== null) return hovered;\n return this.dismissed() ? null : this.focused();\n });\n\n /** The caption's text: the name of the hovered or focused segment ('' hides it). */\n protected readonly caption = computed(() => {\n const index = this.captionIndex();\n return index === null ? '' : (this.cells()[index]?.name ?? '');\n });\n\n protected onFocus(index: number): void {\n this.current.set(index);\n this.focused.set(index);\n this.dismissed.set(false);\n }\n\n protected onBlur(): void {\n this.focused.set(null);\n }\n\n protected onPointerEnter(index: number): void {\n this.hovered.set(index);\n }\n\n protected onPointerLeave(): void {\n this.hovered.set(null);\n }\n\n /** Arrow Left/Right, Home, End move between the runs (no wrap); Escape hides the caption. */\n protected onKeydown(event: KeyboardEvent): void {\n const last = this.cells().length - 1;\n const from = this.current();\n let to: number | null = null;\n switch (event.key) {\n case 'ArrowRight':\n to = Math.min(from + 1, last);\n break;\n case 'ArrowLeft':\n to = Math.max(from - 1, 0);\n break;\n case 'Home':\n to = 0;\n break;\n case 'End':\n to = last;\n break;\n case 'Escape':\n if (this.captionIndex() !== null) {\n this.hovered.set(null);\n this.dismissed.set(true);\n event.preventDefault();\n }\n return;\n default:\n return;\n }\n event.preventDefault();\n this.current.set(to);\n this.cellEls()[to]?.nativeElement.focus();\n }\n}\n","@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-state-strip.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/state-strip/state-strip.component.ts","../../../../libs/angular-ui-kit/src/lib/state-strip/state-strip.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n input,\n linkedSignal,\n signal,\n viewChildren,\n} from '@angular/core';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\n/** State of one run in `lc-state-strip` (3.36). */\nexport type StateStripState = 'success' | 'warning' | 'error' | 'neutral';\n\n/** One run of `lc-state-strip`: its state, and a `label` that names it („Mo 06.10., 06:00“). */\nexport interface StateStripSegment {\n state: StateStripState;\n label: string;\n}\n\n/** The words of the four states, for the counts and the segment names. */\nexport type StateStripLabels = Record<StateStripState, string>;\n\n/** English state words of `lc-state-strip`; pass `stateLabels` for another language. */\nexport const DEFAULT_STATE_STRIP_LABELS: Readonly<StateStripLabels> = {\n success: 'succeeded',\n warning: 'with warnings',\n error: 'failed',\n neutral: 'pending',\n};\n\n/** The order of the counts in the accessible name. */\nconst STATES: readonly StateStripState[] = ['success', 'warning', 'error', 'neutral'];\n\n/**\n * The states of the last runs at a glance (3.36, quellen row 1): one segment\n * per run, oldest first. The state is a shape, not only a colour: success a\n * block in the upper half, error one in the lower half, warning a centred\n * block, neutral a flat dash, so the strip reads without hue and in forced\n * colours.\n *\n * Accessibility: an APG single-row grid. The strip is named by `ariaLabel`\n * and the count per state („Letzte 10 Ladeläufe, 9 geladen, 1\n * fehlgeschlagen“); each segment is a grid cell named by its `label` and\n * state word. One Tab stop (the newest run first), Arrow Left/Right, Home and\n * End move between the runs, Escape hides the caption. Hovering or focusing a\n * segment shows its name below the strip (hidden from screen readers, which\n * read the cell). 3.37: the pointer uses the whole strip (one layer over the\n * row picks the run under it), so dense strips need no 24 px cells.\n *\n * Without segments it renders nothing. The segments are in the prerendered\n * HTML. CSS hooks: `--lc-state-strip-height` (default `2rem`) and\n * `--lc-state-strip-segment-max-width` (default `1.5rem`; more segments than\n * fit share the width, never overflowing).\n *\n * @example\n * ```html\n * <lc-state-strip\n * ariaLabel=\"Letzte 10 Ladeläufe\"\n * [stateLabels]=\"{ success: 'geladen', warning: 'teilweise geladen', error: 'fehlgeschlagen', neutral: 'ausstehend' }\"\n * [segments]=\"runs\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-state-strip',\n standalone: true,\n templateUrl: './state-strip.component.html',\n styleUrl: './state-strip.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-state-strip',\n '[class.lc-state-strip--empty]': '!cells().length',\n },\n})\nexport class StateStripComponent {\n /**\n * The runs, in order, oldest first: one segment each. Empty renders nothing\n * (no box, no accessibility node).\n *\n * More runs than fit at 24 px (13 and more in 328 px) narrow the cells below\n * 24 px; the pointer then uses the whole strip: pointing at it, or sliding a\n * pressed finger along it, shows that run; the keyboard moves with the\n * arrows (3.37).\n * @default []\n */\n readonly segments = input<readonly StateStripSegment[]>([]);\n\n /**\n * Start of the strip's accessible name; the count per state follows it\n * („Letzte 10 Ladeläufe, 9 geladen, 1 fehlgeschlagen“).\n * @default ''\n */\n readonly ariaLabel = input<string>('');\n\n /**\n * The state words for the counts and the segment names, over the English\n * defaults (`DEFAULT_STATE_STRIP_LABELS`: succeeded, with warnings, failed,\n * pending). German: `{ success: 'geladen', warning: 'teilweise geladen',\n * error: 'fehlgeschlagen', neutral: 'ausstehend' }`.\n * @default {}\n */\n readonly stateLabels = input<Partial<StateStripLabels>>({});\n\n private readonly cellEls = viewChildren<ElementRef<HTMLElement>>('cell');\n\n /** The state words in use. */\n private readonly words = computed<StateStripLabels>(() => ({ ...DEFAULT_STATE_STRIP_LABELS, ...this.stateLabels() }));\n\n /** One entry per segment: its state (unknown states draw as neutral) and its name. */\n protected readonly cells = computed(() => {\n const words = this.words();\n return (this.segments() ?? []).map((segment) => {\n const state: StateStripState = STATES.includes(segment.state) ? segment.state : 'neutral';\n return { state, name: nameWithText(segment.label ?? '', words[state]) };\n });\n });\n\n /** The strip's name: `ariaLabel`, then the count per state (0 left out). */\n protected readonly gridLabel = computed(() => {\n const words = this.words();\n const counts = STATES.map((state) => [state, this.cells().filter((c) => c.state === state).length] as const)\n .filter(([, count]) => count > 0)\n .map(([state, count]) => `${count} ${words[state]}`)\n .join(', ');\n const label = this.ariaLabel().trim();\n return label ? `${label}, ${counts}` : counts;\n });\n\n /** The roving Tab stop: the newest run until the user moves. */\n protected readonly current = linkedSignal(() => Math.max(0, this.cells().length - 1));\n\n /** The segment under the pointer, the one with focus, and whether Escape hid the caption. */\n protected readonly hovered = signal<number | null>(null);\n private readonly focused = signal<number | null>(null);\n private readonly dismissed = signal(false);\n\n /** The segment whose name the caption shows, if any. */\n private readonly captionIndex = computed(() => {\n const hovered = this.hovered();\n if (hovered !== null) return hovered;\n return this.dismissed() ? null : this.focused();\n });\n\n /** The caption's text: the name of the hovered or focused segment ('' hides it). */\n protected readonly caption = computed(() => {\n const index = this.captionIndex();\n return index === null ? '' : (this.cells()[index]?.name ?? '');\n });\n\n protected onFocus(index: number): void {\n this.current.set(index);\n this.focused.set(index);\n this.dismissed.set(false);\n }\n\n protected onBlur(): void {\n this.focused.set(null);\n }\n\n protected onPointerEnter(index: number): void {\n this.hovered.set(index);\n }\n\n protected onPointerLeave(): void {\n this.hovered.set(null);\n }\n\n /** The run the scrub layer last picked; a move within it changes nothing (as entering a cell). */\n private scrubbed: number | null = null;\n\n /**\n * The pointer over the strip (3.37, request 057, 056 S-1): the run whose\n * cell spans the pointer's x shows in the caption; outside every cell none.\n */\n protected onScrubMove(event: PointerEvent): void {\n const index = this.cellAt(event.clientX);\n if (index === this.scrubbed) return;\n this.scrubbed = index;\n this.hovered.set(index);\n }\n\n /** Releasing the pointer (a click, a lifted finger) focuses the run under it. */\n protected onScrubUp(event: PointerEvent): void {\n const index = this.cellAt(event.clientX);\n if (index !== null) this.cellEls()[index]?.nativeElement.focus();\n }\n\n protected onScrubLeave(): void {\n this.scrubbed = null;\n this.hovered.set(null);\n }\n\n /** The index of the cell whose box spans `x`, any writing direction. */\n private cellAt(x: number): number | null {\n const cells = this.cellEls();\n for (let i = 0; i < cells.length; i++) {\n const box = cells[i].nativeElement.getBoundingClientRect();\n if (x >= box.left && x < box.right) return i;\n }\n return null;\n }\n\n /** Arrow Left/Right, Home, End move between the runs (no wrap); Escape hides the caption. */\n protected onKeydown(event: KeyboardEvent): void {\n const last = this.cells().length - 1;\n const from = this.current();\n let to: number | null = null;\n switch (event.key) {\n case 'ArrowRight':\n to = Math.min(from + 1, last);\n break;\n case 'ArrowLeft':\n to = Math.max(from - 1, 0);\n break;\n case 'Home':\n to = 0;\n break;\n case 'End':\n to = last;\n break;\n case 'Escape':\n if (this.captionIndex() !== null) {\n this.hovered.set(null);\n this.dismissed.set(true);\n event.preventDefault();\n }\n return;\n default:\n return;\n }\n event.preventDefault();\n this.current.set(to);\n this.cellEls()[to]?.nativeElement.focus();\n }\n}\n","@if (cells().length) {\n <!-- An APG single-row grid: one Tab stop, arrows move between the runs. -->\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the cells are the focusable controls (roving tabindex); their keys bubble here -->\n <div class=\"lc-state-strip__grid\" role=\"grid\" [attr.aria-label]=\"gridLabel()\" (keydown)=\"onKeydown($event)\">\n <div class=\"lc-state-strip__row\" role=\"row\">\n @for (cell of cells(); track $index; let i = $index) {\n <div\n #cell\n class=\"lc-state-strip__cell\"\n role=\"gridcell\"\n [class.lc-state-strip__cell--hovered]=\"i === hovered()\"\n [attr.tabindex]=\"i === current() ? 0 : -1\"\n (focus)=\"onFocus(i)\"\n (blur)=\"onBlur()\"\n (pointerenter)=\"onPointerEnter(i)\"\n (pointerleave)=\"onPointerLeave()\"\n >\n <span class=\"lc-state-strip__mark lc-state-strip__mark--{{ cell.state }}\"></span>\n <span class=\"lc-state-strip__name\">{{ cell.name }}</span>\n </div>\n }\n <!-- 3.37: the pointer path. One layer over the row picks the run under the pointer, so narrow cells need no 24 px target. -->\n <div\n class=\"lc-state-strip__scrub\"\n aria-hidden=\"true\"\n (pointerdown)=\"onScrubMove($event)\"\n (pointermove)=\"onScrubMove($event)\"\n (pointerup)=\"onScrubUp($event)\"\n (pointerleave)=\"onScrubLeave()\"\n (pointercancel)=\"onScrubLeave()\"\n ></div>\n </div>\n </div>\n @if (caption()) {\n <!-- The name of the hovered or focused run, in the tooltip look; the cell is what screen readers read. -->\n <div class=\"lc-state-strip__caption\" aria-hidden=\"true\">{{ caption() }}</div>\n }\n}\n"],"mappings":"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"}
@@ -1003,7 +1003,7 @@ var TableComponent = class TableComponent {
1003
1003
  isSignal: true
1004
1004
  }],
1005
1005
  ngImport: i0,
1006
- template: "<div [class]=\"wrapperClasses()\">\n <table [class]=\"tableClasses()\" [attr.role]=\"treeMode() ? 'treegrid' : 'table'\">\n @if (caption()) {\n <!-- First child of the table, so it names the table (and the treegrid). -->\n <caption class=\"lc-table__caption\" [class.lc-table__caption--hidden]=\"!showCaption()\">{{ caption() }}</caption>\n }\n <thead>\n <!-- Filter row -->\n @if (filterable()) {\n <tr class=\"lc-table__filter-row\">\n @if (selectable()) {\n <th class=\"lc-table__select-cell\"></th>\n }\n @for (column of columns(); track column.key) {\n <th>\n @if (column.filterable !== false) {\n <lc-input\n class=\"lc-table__filter-input\"\n size=\"xs\"\n [placeholder]=\"filterPlaceholderLabel() + ' ' + column.label\"\n [ariaLabel]=\"filterAriaLabel() + ' ' + column.label\"\n [ngModel]=\"getFilterValue(column.key)\"\n (ngModelChange)=\"onFilterChange(column.key, $event)\"\n />\n }\n </th>\n }\n @if (hasActions()) {\n <th class=\"lc-table__actions-cell\"></th>\n }\n </tr>\n }\n <!-- Header row -->\n <tr>\n @if (selectable()) {\n <th class=\"lc-table__select-cell\" scope=\"col\">\n <!-- The label's ::before covers the cell: a 44 px hit area (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (change)=\"toggleSelectAll()\"\n [attr.aria-label]=\"selectAllAriaLabel()\"\n />\n </label>\n </th>\n }\n @for (column of columns(); track column.key) {\n <!-- A sortable header is a keyboard stop too: Enter/Space sort like a click. -->\n <th\n scope=\"col\"\n [class]=\"getHeaderClasses(column) + (column.cssClass ? ' ' + column.cssClass : '')\"\n [attr.aria-sort]=\"getAriaSort(column.key)\"\n [attr.tabindex]=\"column.sortable ? 0 : null\"\n [style.width]=\"column.width\"\n [title]=\"column.tooltip || ''\"\n (click)=\"handleSort(column.key)\"\n (keydown)=\"column.sortable ? onHeaderKeydown($event, column.key) : null\">\n @if (column.align === 'end') {\n <!-- 3.17: the arrow is placed before the label (CSS), so the label stays\n flush with the figures; in the DOM it still follows the label. No\n whitespace between the label and the arrow. -->\n <span class=\"lc-table__sort-label\">{{ column.label }}@if (column.sortable && getSortState(column.key)) {<span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>}</span>\n } @else {\n {{ column.label }}\n @if (column.sortable && getSortState(column.key)) {\n <span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>\n }\n }\n </th>\n }\n @if (hasActions()) {\n <th\n scope=\"col\"\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [style.width]=\"actionsWidth()\"\n >\n {{ actionsLabel() }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (displayRows().length === 0) {\n <tr>\n <td [attr.colspan]=\"columns().length + (selectable() ? 1 : 0) + (hasActions() ? 1 : 0)\" class=\"lc-table__empty\">\n {{ emptyText() }}\n </td>\n </tr>\n } @else {\n @for (dr of displayRows(); track dr.key) {\n <!-- In tree mode the rows form the treegrid's keyboard model: a roving\n tab stop plus Arrow/Home/End/Enter/Space handling (see the\n component). Flat mode keeps plain table rows. -->\n <tr\n #dataRow\n [class.lc-table__row--selected]=\"selectable() && isRowSelected(dr.row)\"\n [class.lc-table__row--group]=\"dr.hasChildren\"\n [attr.role]=\"treeMode() ? 'row' : null\"\n [attr.tabindex]=\"rowTabindex(dr)\"\n [attr.aria-level]=\"treeMode() ? dr.level : null\"\n [attr.aria-expanded]=\"treeMode() && dr.hasChildren ? dr.expanded : null\"\n [attr.aria-posinset]=\"treeMode() ? dr.posInSet : null\"\n [attr.aria-setsize]=\"treeMode() ? dr.setSize : null\"\n [attr.aria-selected]=\"treeMode() && selectable() ? isRowSelected(dr.row) : null\"\n (click)=\"onRowClick(dr.row, $event)\"\n (focus)=\"onRowFocus(dr)\"\n (keydown)=\"onRowKeydown($event, dr)\"\n >\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\">\n <!-- A click in the cell toggles the box, never the row (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"isRowSelected(dr.row)\"\n (change)=\"toggleRowSelect(dr.row)\"\n (click)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (dr.absIndex + 1)\"\n />\n </label>\n </td>\n }\n @for (column of columns(); track column.key) {\n <td\n [class]=\"getCellClasses(dr.row, column, dr.absIndex)\"\n [ngStyle]=\"getCellStyles(dr.row, column, dr.absIndex)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (dblclick)=\"editable() ? startEdit(dr.row, column.key, getCellValue(dr.row, column.key)) : null\"\n >\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n @for (lvl of indentLevels(dr.depth); track lvl) {\n <span class=\"lc-table__tree-indent\" [style.width.px]=\"indentSize()\"></span>\n }\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\">\n @if (dr.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-table__chevron-btn\"\n [class.lc-table__chevron-btn--expanded]=\"dr.expanded\"\n [attr.aria-label]=\"(dr.expanded ? collapseRowAriaLabel() : expandRowAriaLabel()) + ' ' + treeCellLabel(dr.row)\"\n [attr.aria-expanded]=\"dr.expanded\"\n (click)=\"onChevronClick(dr, $event)\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n <span class=\"lc-table__tree-content\">\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n </span>\n </span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n }\n </td>\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"lc-table__actions\">\n @for (action of actions(); track action.key) {\n @if (!isActionHidden(action, dr.row, dr.absIndex)) {\n <lc-button\n [variant]=\"action.variant ?? 'ghost'\"\n size=\"xs\"\n [iconOnly]=\"!action.label\"\n [disabled]=\"isActionDisabled(action, dr.row, dr.absIndex)\"\n [ariaLabel]=\"actionAriaLabel(action, dr.row, dr.absIndex)\"\n (clicked)=\"onActionClick(action, dr.row, dr.absIndex)\"\n >\n @if (action.icon) {\n <lc-icon [name]=\"action.icon\" size=\"sm\" [decorative]=\"true\" />\n }\n @if (action.label) {\n {{ action.label }}\n }\n </lc-button>\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n @if (footerRow(); as footer) {\n <!-- 3.36: one footer row below every data row; never sorted, filtered,\n paged, selected or counted. Its first cell is the row header. -->\n <tfoot class=\"lc-table__footer\">\n <tr [attr.role]=\"treeMode() ? 'row' : null\">\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\"></td>\n }\n @for (column of columns(); track column.key; let first = $first) {\n @if (first) {\n <th\n scope=\"row\"\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'rowheader' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </th>\n } @else {\n <td\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </td>\n }\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n ></td>\n }\n </tr>\n </tfoot>\n }\n </table>\n</div>\n\n<!-- 3.36: a footer cell. In the tree column the label lines up with the level-0\n labels (an empty disclosure slot before it). Templates get `footer: true`. -->\n<ng-template #footerCell let-row=\"row\" let-column=\"column\">\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\"></span>\n <span class=\"lc-table__tree-content\">\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n </span>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n }\n</ng-template>\n\n<ng-template #footerContent let-row=\"row\" let-column=\"column\">\n @if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row, footer: true }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, -1) }}\n }\n</ng-template>\n\n<!-- Shared cell content (edit input / custom template / formatted value) -->\n<ng-template #cellContent let-row=\"row\" let-column=\"column\" let-absIndex=\"absIndex\">\n @if (isEditing(row, column.key)) {\n <input\n #editInput\n type=\"text\"\n class=\"lc-table__edit-input\"\n [value]=\"editValue()\"\n (input)=\"editValue.set(getInputValue($event))\"\n (blur)=\"commitEdit(row, column.key)\"\n (keydown)=\"onEditKeydown($event, row, column.key)\"\n />\n } @else if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, absIndex) }}\n }\n</ng-template>\n\n<!-- Pagination -->\n@if (paginate()) {\n <div [class]=\"'lc-table-pagination lc-table-pagination--' + paginationSize()\">\n <div class=\"lc-table-pagination__info\">\n {{ paginationStart }}–{{ paginationEnd }} {{ rangeOfLabel() }} {{ totalRows() }}\n </div>\n <div class=\"lc-table-pagination__controls\">\n <lc-select\n class=\"lc-table-pagination__size\"\n [size]=\"paginationSize()\"\n [options]=\"pageSizeSelectOptions()\"\n [ngModel]=\"internalPageSize()\"\n [ariaLabel]=\"rowsPerPageAriaLabel()\"\n (ngModelChange)=\"onPageSizeModelChange($event)\"\n />\n <div class=\"lc-table-pagination__pager\">\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() === 0\"\n (click)=\"goToPage(currentPage() - 1)\"\n [attr.aria-label]=\"previousPageAriaLabel()\"\n >‹</button>\n <span class=\"lc-table-pagination__page\">\n {{ currentPage() + 1 }} / {{ totalPages() }}\n </span>\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() >= totalPages() - 1\"\n (click)=\"goToPage(currentPage() + 1)\"\n [attr.aria-label]=\"nextPageAriaLabel()\"\n >›</button>\n </div>\n </div>\n </div>\n}\n",
1006
+ template: "<div [class]=\"wrapperClasses()\">\n <table [class]=\"tableClasses()\" [attr.role]=\"treeMode() ? 'treegrid' : 'table'\">\n @if (caption()) {\n <!-- First child of the table, so it names the table (and the treegrid). -->\n <caption class=\"lc-table__caption\" [class.lc-table__caption--hidden]=\"!showCaption()\">{{ caption() }}</caption>\n }\n <thead>\n <!-- Filter row -->\n @if (filterable()) {\n <tr class=\"lc-table__filter-row\">\n @if (selectable()) {\n <th class=\"lc-table__select-cell\"></th>\n }\n @for (column of columns(); track column.key) {\n <th>\n @if (column.filterable !== false) {\n <lc-input\n class=\"lc-table__filter-input\"\n size=\"xs\"\n [placeholder]=\"filterPlaceholderLabel() + ' ' + column.label\"\n [ariaLabel]=\"filterAriaLabel() + ' ' + column.label\"\n [ngModel]=\"getFilterValue(column.key)\"\n (ngModelChange)=\"onFilterChange(column.key, $event)\"\n />\n }\n </th>\n }\n @if (hasActions()) {\n <th class=\"lc-table__actions-cell\"></th>\n }\n </tr>\n }\n <!-- Header row -->\n <tr>\n @if (selectable()) {\n <th class=\"lc-table__select-cell\" scope=\"col\">\n <!-- The label's ::before covers the cell: a 44 px hit area (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (change)=\"toggleSelectAll()\"\n [attr.aria-label]=\"selectAllAriaLabel()\"\n />\n </label>\n </th>\n }\n @for (column of columns(); track column.key) {\n <!-- A sortable header is a keyboard stop too: Enter/Space sort like a click. -->\n <th\n scope=\"col\"\n [class]=\"getHeaderClasses(column) + (column.cssClass ? ' ' + column.cssClass : '')\"\n [attr.aria-sort]=\"getAriaSort(column.key)\"\n [attr.tabindex]=\"column.sortable ? 0 : null\"\n [style.width]=\"column.width\"\n [title]=\"column.tooltip || ''\"\n (click)=\"handleSort(column.key)\"\n (keydown)=\"column.sortable ? onHeaderKeydown($event, column.key) : null\">\n @if (column.align === 'end') {\n <!-- 3.17: the arrow is placed before the label (CSS), so the label stays\n flush with the figures; in the DOM it still follows the label. No\n whitespace between the label and the arrow. -->\n <span class=\"lc-table__sort-label\">{{ column.label }}@if (column.sortable && getSortState(column.key)) {<span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>}</span>\n } @else {\n {{ column.label }}\n @if (column.sortable && getSortState(column.key)) {\n <span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>\n }\n }\n </th>\n }\n @if (hasActions()) {\n <th\n scope=\"col\"\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [style.width]=\"actionsWidth()\"\n >\n {{ actionsLabel() }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (displayRows().length === 0) {\n <tr>\n <td [attr.colspan]=\"columns().length + (selectable() ? 1 : 0) + (hasActions() ? 1 : 0)\" class=\"lc-table__empty\">\n {{ emptyText() }}\n </td>\n </tr>\n } @else {\n @for (dr of displayRows(); track dr.key) {\n <!-- In tree mode the rows form the treegrid's keyboard model: a roving\n tab stop plus Arrow/Home/End/Enter/Space handling (see the\n component). Flat mode keeps plain table rows. -->\n <tr\n #dataRow\n [class.lc-table__row--selected]=\"selectable() && isRowSelected(dr.row)\"\n [class.lc-table__row--group]=\"dr.hasChildren\"\n [attr.role]=\"treeMode() ? 'row' : null\"\n [attr.tabindex]=\"rowTabindex(dr)\"\n [attr.aria-level]=\"treeMode() ? dr.level : null\"\n [attr.aria-expanded]=\"treeMode() && dr.hasChildren ? dr.expanded : null\"\n [attr.aria-posinset]=\"treeMode() ? dr.posInSet : null\"\n [attr.aria-setsize]=\"treeMode() ? dr.setSize : null\"\n [attr.aria-selected]=\"treeMode() && selectable() ? isRowSelected(dr.row) : null\"\n (click)=\"onRowClick(dr.row, $event)\"\n (focus)=\"onRowFocus(dr)\"\n (keydown)=\"onRowKeydown($event, dr)\"\n >\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\">\n <!-- A click in the cell toggles the box, never the row (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"isRowSelected(dr.row)\"\n (change)=\"toggleRowSelect(dr.row)\"\n (click)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (dr.absIndex + 1)\"\n />\n </label>\n </td>\n }\n @for (column of columns(); track column.key) {\n <td\n [class]=\"getCellClasses(dr.row, column, dr.absIndex)\"\n [ngStyle]=\"getCellStyles(dr.row, column, dr.absIndex)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (dblclick)=\"editable() ? startEdit(dr.row, column.key, getCellValue(dr.row, column.key)) : null\"\n >\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n @for (lvl of indentLevels(dr.depth); track lvl) {\n <span class=\"lc-table__tree-indent\" [style.width.px]=\"indentSize()\"></span>\n }\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\">\n @if (dr.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-table__chevron-btn\"\n [class.lc-table__chevron-btn--expanded]=\"dr.expanded\"\n [attr.aria-label]=\"(dr.expanded ? collapseRowAriaLabel() : expandRowAriaLabel()) + ' ' + treeCellLabel(dr.row)\"\n [attr.aria-expanded]=\"dr.expanded\"\n (click)=\"onChevronClick(dr, $event)\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n <span class=\"lc-table__tree-content\">\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n </span>\n </span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n }\n </td>\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"lc-table__actions\">\n @for (action of actions(); track action.key) {\n @if (!isActionHidden(action, dr.row, dr.absIndex)) {\n <lc-button\n [variant]=\"action.variant ?? 'ghost'\"\n [size]=\"action.size ?? 'xs'\"\n [iconOnly]=\"!action.label\"\n [disabled]=\"isActionDisabled(action, dr.row, dr.absIndex)\"\n [ariaLabel]=\"actionAriaLabel(action, dr.row, dr.absIndex)\"\n (clicked)=\"onActionClick(action, dr.row, dr.absIndex)\"\n >\n @if (action.icon) {\n <lc-icon [name]=\"action.icon\" size=\"sm\" [decorative]=\"true\" />\n }\n @if (action.label) {\n {{ action.label }}\n }\n </lc-button>\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n @if (footerRow(); as footer) {\n <!-- 3.36: one footer row below every data row; never sorted, filtered,\n paged, selected or counted. Its first cell is the row header. -->\n <tfoot class=\"lc-table__footer\">\n <tr [attr.role]=\"treeMode() ? 'row' : null\">\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\"></td>\n }\n @for (column of columns(); track column.key; let first = $first) {\n @if (first) {\n <th\n scope=\"row\"\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'rowheader' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </th>\n } @else {\n <td\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </td>\n }\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n ></td>\n }\n </tr>\n </tfoot>\n }\n </table>\n</div>\n\n<!-- 3.36: a footer cell. In the tree column the label lines up with the level-0\n labels (an empty disclosure slot before it). Templates get `footer: true`. -->\n<ng-template #footerCell let-row=\"row\" let-column=\"column\">\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\"></span>\n <span class=\"lc-table__tree-content\">\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n </span>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n }\n</ng-template>\n\n<ng-template #footerContent let-row=\"row\" let-column=\"column\">\n @if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row, footer: true }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, -1) }}\n }\n</ng-template>\n\n<!-- Shared cell content (edit input / custom template / formatted value) -->\n<ng-template #cellContent let-row=\"row\" let-column=\"column\" let-absIndex=\"absIndex\">\n @if (isEditing(row, column.key)) {\n <input\n #editInput\n type=\"text\"\n class=\"lc-table__edit-input\"\n [value]=\"editValue()\"\n (input)=\"editValue.set(getInputValue($event))\"\n (blur)=\"commitEdit(row, column.key)\"\n (keydown)=\"onEditKeydown($event, row, column.key)\"\n />\n } @else if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, absIndex) }}\n }\n</ng-template>\n\n<!-- Pagination -->\n@if (paginate()) {\n <div [class]=\"'lc-table-pagination lc-table-pagination--' + paginationSize()\">\n <div class=\"lc-table-pagination__info\">\n {{ paginationStart }}–{{ paginationEnd }} {{ rangeOfLabel() }} {{ totalRows() }}\n </div>\n <div class=\"lc-table-pagination__controls\">\n <lc-select\n class=\"lc-table-pagination__size\"\n [size]=\"paginationSize()\"\n [options]=\"pageSizeSelectOptions()\"\n [ngModel]=\"internalPageSize()\"\n [ariaLabel]=\"rowsPerPageAriaLabel()\"\n (ngModelChange)=\"onPageSizeModelChange($event)\"\n />\n <div class=\"lc-table-pagination__pager\">\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() === 0\"\n (click)=\"goToPage(currentPage() - 1)\"\n [attr.aria-label]=\"previousPageAriaLabel()\"\n >‹</button>\n <span class=\"lc-table-pagination__page\">\n {{ currentPage() + 1 }} / {{ totalPages() }}\n </span>\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() >= totalPages() - 1\"\n (click)=\"goToPage(currentPage() + 1)\"\n [attr.aria-label]=\"nextPageAriaLabel()\"\n >›</button>\n </div>\n </div>\n </div>\n}\n",
1007
1007
  styles: [".lc-table-wrapper{width:100%}.lc-table-wrapper--responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width:768px){.lc-table-wrapper--responsive::-webkit-scrollbar{height:8px}.lc-table-wrapper--responsive::-webkit-scrollbar-thumb{background-color:var(--color-border);border-radius:var(--border-radius-sm)}}.lc-table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--color-surface);color:var(--color-text-primary);font-family:var(--typography-font-family)}.lc-table thead{background-color:var(--color-surface-2);border-bottom:2px solid var(--color-border-strong)}.lc-table thead tr th{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));text-align:left;font-weight:var(--typography-font-weight-semibold);font-size:var(--typography-font-size-sm);text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-secondary);white-space:nowrap;-webkit-user-select:none;user-select:none}.lc-table thead tr th.sortable{cursor:pointer;position:relative;padding-right:var(--spacing-8);transition:background-color .2s ease}.lc-table thead tr th.sortable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-table thead tr th.sortable:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table thead tr th.sortable .sort-indicator{position:absolute;right:var(--spacing-3);top:50%;transform:translateY(-50%);font-size:var(--typography-font-size-xs);color:var(--color-primary)}.lc-table thead tr th.sorted-asc,.lc-table thead tr th.sorted-desc{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table tbody tr{border-bottom:1px solid var(--color-divider)}.lc-table tbody tr:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-table tbody tr:last-child{border-bottom:none}.lc-table tbody tr td{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4))}.lc-table__empty{text-align:center;padding:var(--spacing-8) var(--spacing-4)!important;font-style:italic}.lc-table--striped tbody tr:nth-child(2n){background-color:var(--color-surface-2)}.lc-table--bordered{border:1px solid var(--color-border)}.lc-table--bordered thead tr th,.lc-table--bordered tbody tr td{border-right:1px solid var(--color-divider)}.lc-table--bordered thead tr th:last-child,.lc-table--bordered tbody tr td:last-child{border-right:none}.lc-table--bordered tbody tr{border-bottom:1px solid var(--color-border)}.lc-table--hoverable tbody tr,.lc-table--clickable tbody tr{transition:background-color .15s ease;cursor:pointer}.lc-table--hoverable tbody tr:hover,.lc-table--clickable tbody tr:hover{background-color:var(--color-surface-hover)}.lc-table--sm thead tr th,.lc-table--sm tbody tr td{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--sm thead tr th{font-size:var(--typography-font-size-xs)}.lc-table--lg thead tr th,.lc-table--lg tbody tr td{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5)}.lc-table--lg thead tr th{font-size:var(--typography-font-size-base)}:where(.lc-table tbody tr td){font-size:var(--typography-font-size-sm);color:var(--color-text-primary)}:where(.lc-table--sm tbody tr td){font-size:var(--typography-font-size-xs)}:where(.lc-table--lg tbody tr td){font-size:var(--typography-font-size-base)}.lc-table thead tr th.lc-table__cell--end,.lc-table tbody tr td.lc-table__cell--end{text-align:end;font-variant-numeric:tabular-nums}.lc-table thead tr th.sortable.lc-table__cell--end{padding-inline-start:var(--spacing-8);padding-inline-end:var(--lc-density-padding-md, var(--spacing-4))}.lc-table thead tr th.sortable.lc-table__cell--end .sort-indicator{right:auto;inset-inline-end:calc(100% + var(--spacing-2))}.lc-table--sm thead tr th.sortable.lc-table__cell--end{padding-inline-end:var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--lg thead tr th.sortable.lc-table__cell--end{padding-inline-end:var(--spacing-5)}.lc-table__sort-label{position:relative;display:inline-block}.lc-table__cell--end .lc-table__edit-input{text-align:end;font-variant-numeric:tabular-nums}.lc-table__caption{caption-side:top;padding:0 0 var(--spacing-3);text-align:start;font-family:var(--typography-font-family);font-size:var(--typography-font-size-base);font-weight:var(--typography-font-weight-semibold);line-height:var(--typography-line-height-normal);color:var(--color-text-primary)}.lc-table__caption--hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(forced-colors:active){.lc-table tbody tr:focus-visible{outline:2px solid Highlight}}.lc-table__filter-row th{padding:var(--spacing-2) var(--spacing-4)!important;background-color:var(--color-surface)!important;border-bottom:1px solid var(--color-divider)!important}.lc-table__filter-input{width:100%;display:block}.lc-table__filter-input .input-field{font-size:var(--typography-font-size-xs)}.lc-table__select-cell{position:relative;width:var(--size-min-touch-height, 2.75rem);text-align:center!important;padding:var(--spacing-2) calc((var(--size-min-touch-height, 2.75rem) - 1rem) / 2)!important}.lc-table__select-hit{cursor:pointer}.lc-table__select-hit:before{content:\"\";position:absolute;inset:0}.lc-table__checkbox{position:relative;width:16px;height:16px;cursor:pointer;accent-color:var(--color-primary);display:block;margin:2.5px auto;margin:max(2.5px,(1lh - 16px) / 2) auto}.lc-table__checkbox:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-table__row--selected{background-color:var(--color-surface-selected)!important}.lc-table__actions-cell{white-space:nowrap}.lc-table thead tr th.lc-table__actions-cell--start,.lc-table tbody tr td.lc-table__actions-cell--start{text-align:left}.lc-table thead tr th.lc-table__actions-cell--center,.lc-table tbody tr td.lc-table__actions-cell--center{text-align:center}.lc-table thead tr th.lc-table__actions-cell--end,.lc-table tbody tr td.lc-table__actions-cell--end{text-align:right}.lc-table__actions{display:flex;align-items:center;gap:var(--spacing-2);justify-content:flex-start}.lc-table__actions-cell--center .lc-table__actions{justify-content:center}.lc-table__actions-cell--end .lc-table__actions{justify-content:flex-end}.lc-table__tree-cell{display:flex;align-items:center;min-width:0}.lc-table__tree-indent{flex:0 0 auto;align-self:stretch;position:relative}.lc-table__tree-indent:before{content:\"\";position:absolute;left:50%;top:0;bottom:0;width:1px;background-color:var(--color-border)}.lc-table__tree-chevron{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center}.lc-table__chevron-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;border-radius:var(--border-radius-sm);transition:transform .2s ease,background-color .15s ease,color .15s ease}.lc-table__chevron-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table__chevron-btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:1px}.lc-table__chevron-btn--expanded{transform:rotate(90deg)}.lc-table__tree-content{flex:1 1 auto;min-width:0}.lc-table__row--group .lc-table__tree-content{font-weight:var(--typography-font-weight-semibold)}.lc-table__edit-input{width:100%;padding:var(--spacing-1) var(--spacing-2);border:2px solid var(--color-primary);border-radius:var(--border-radius-sm);font-size:inherit;background-color:var(--color-surface-2);color:var(--color-text-primary);outline:none}.lc-table-pagination{--_lc-table-pagination-btn: var(--size-interactive-md-height);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--spacing-2) var(--spacing-4);padding:var(--spacing-3) var(--spacing-1);font-size:var(--typography-font-size-sm);color:var(--color-text-secondary)}.lc-table-pagination--sm{--_lc-table-pagination-btn: var(--size-interactive-sm-height)}.lc-table-pagination--lg{--_lc-table-pagination-btn: var(--size-interactive-lg-height)}.lc-table-pagination__info{white-space:nowrap}.lc-table-pagination__controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--spacing-2);margin-inline-start:auto}.lc-table-pagination__pager{display:flex;align-items:center;gap:var(--spacing-2)}.lc-table-pagination__size{--lc-select-clear-display: none;display:block;min-width:110px}.lc-table-pagination__btn{position:relative;appearance:none;border:1px solid var(--color-field-border);background-color:var(--color-surface-2);color:var(--color-text-primary);width:var(--_lc-table-pagination-btn);height:var(--_lc-table-pagination-btn);display:flex;align-items:center;justify-content:center;padding:0;border-radius:var(--border-radius-md);font-size:var(--typography-font-size-xl);line-height:1;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.lc-table-pagination__btn:before{content:\"\";position:absolute;inset:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2)}.lc-table-pagination__btn:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-table-pagination__btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-table-pagination__btn:disabled{opacity:.4;cursor:not-allowed}.lc-table-pagination__page{white-space:nowrap;min-width:60px;text-align:center}@media(max-width:640px){.lc-table-wrapper--stack .lc-table thead{display:none}.lc-table-wrapper--stack .lc-table tbody tr{display:block;margin-bottom:var(--spacing-4);border:1px solid var(--color-border);border-radius:var(--border-radius-md)}.lc-table-wrapper--stack .lc-table tbody tr td{display:block;text-align:right;padding:var(--spacing-2) var(--spacing-4);border-bottom:1px solid var(--color-divider)}.lc-table-wrapper--stack .lc-table tbody tr td:last-child{border-bottom:none}.lc-table-wrapper--stack .lc-table tbody tr td:before{content:attr(data-label);float:left;font-weight:var(--typography-font-weight-semibold);color:var(--color-text-secondary)}}@media(forced-colors:active){.lc-table__checkbox:focus-visible{outline-color:Highlight}}.lc-table tfoot tr{border-top:1px solid var(--color-field-border);border-bottom:3px double var(--color-field-border)}.lc-table tfoot tr th,.lc-table tfoot tr td{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));text-align:start;font-weight:var(--typography-font-weight-semibold);font-variant-numeric:tabular-nums}.lc-table tfoot tr th.lc-table__cell--end,.lc-table tfoot tr td.lc-table__cell--end{text-align:end}.lc-table--sm tfoot tr th,.lc-table--sm tfoot tr td{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--lg tfoot tr th,.lc-table--lg tfoot tr td{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5)}.lc-table--bordered tfoot tr th,.lc-table--bordered tfoot tr td{border-right:1px solid var(--color-divider)}.lc-table--bordered tfoot tr th:last-child,.lc-table--bordered tfoot tr td:last-child{border-right:none}:where(.lc-table tfoot tr th,.lc-table tfoot tr td){font-size:var(--typography-font-size-sm);color:var(--color-text-primary)}:where(.lc-table--sm tfoot tr th,.lc-table--sm tfoot tr td){font-size:var(--typography-font-size-xs)}:where(.lc-table--lg tfoot tr th,.lc-table--lg tfoot tr td){font-size:var(--typography-font-size-base)}\n"],
1008
1008
  dependencies: [
1009
1009
  {
@@ -1179,7 +1179,7 @@ i0.ɵɵngDeclareClassMetadata({
1179
1179
  ],
1180
1180
  viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT])],
1181
1181
  changeDetection: ChangeDetectionStrategy.OnPush,
1182
- template: "<div [class]=\"wrapperClasses()\">\n <table [class]=\"tableClasses()\" [attr.role]=\"treeMode() ? 'treegrid' : 'table'\">\n @if (caption()) {\n <!-- First child of the table, so it names the table (and the treegrid). -->\n <caption class=\"lc-table__caption\" [class.lc-table__caption--hidden]=\"!showCaption()\">{{ caption() }}</caption>\n }\n <thead>\n <!-- Filter row -->\n @if (filterable()) {\n <tr class=\"lc-table__filter-row\">\n @if (selectable()) {\n <th class=\"lc-table__select-cell\"></th>\n }\n @for (column of columns(); track column.key) {\n <th>\n @if (column.filterable !== false) {\n <lc-input\n class=\"lc-table__filter-input\"\n size=\"xs\"\n [placeholder]=\"filterPlaceholderLabel() + ' ' + column.label\"\n [ariaLabel]=\"filterAriaLabel() + ' ' + column.label\"\n [ngModel]=\"getFilterValue(column.key)\"\n (ngModelChange)=\"onFilterChange(column.key, $event)\"\n />\n }\n </th>\n }\n @if (hasActions()) {\n <th class=\"lc-table__actions-cell\"></th>\n }\n </tr>\n }\n <!-- Header row -->\n <tr>\n @if (selectable()) {\n <th class=\"lc-table__select-cell\" scope=\"col\">\n <!-- The label's ::before covers the cell: a 44 px hit area (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (change)=\"toggleSelectAll()\"\n [attr.aria-label]=\"selectAllAriaLabel()\"\n />\n </label>\n </th>\n }\n @for (column of columns(); track column.key) {\n <!-- A sortable header is a keyboard stop too: Enter/Space sort like a click. -->\n <th\n scope=\"col\"\n [class]=\"getHeaderClasses(column) + (column.cssClass ? ' ' + column.cssClass : '')\"\n [attr.aria-sort]=\"getAriaSort(column.key)\"\n [attr.tabindex]=\"column.sortable ? 0 : null\"\n [style.width]=\"column.width\"\n [title]=\"column.tooltip || ''\"\n (click)=\"handleSort(column.key)\"\n (keydown)=\"column.sortable ? onHeaderKeydown($event, column.key) : null\">\n @if (column.align === 'end') {\n <!-- 3.17: the arrow is placed before the label (CSS), so the label stays\n flush with the figures; in the DOM it still follows the label. No\n whitespace between the label and the arrow. -->\n <span class=\"lc-table__sort-label\">{{ column.label }}@if (column.sortable && getSortState(column.key)) {<span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>}</span>\n } @else {\n {{ column.label }}\n @if (column.sortable && getSortState(column.key)) {\n <span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>\n }\n }\n </th>\n }\n @if (hasActions()) {\n <th\n scope=\"col\"\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [style.width]=\"actionsWidth()\"\n >\n {{ actionsLabel() }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (displayRows().length === 0) {\n <tr>\n <td [attr.colspan]=\"columns().length + (selectable() ? 1 : 0) + (hasActions() ? 1 : 0)\" class=\"lc-table__empty\">\n {{ emptyText() }}\n </td>\n </tr>\n } @else {\n @for (dr of displayRows(); track dr.key) {\n <!-- In tree mode the rows form the treegrid's keyboard model: a roving\n tab stop plus Arrow/Home/End/Enter/Space handling (see the\n component). Flat mode keeps plain table rows. -->\n <tr\n #dataRow\n [class.lc-table__row--selected]=\"selectable() && isRowSelected(dr.row)\"\n [class.lc-table__row--group]=\"dr.hasChildren\"\n [attr.role]=\"treeMode() ? 'row' : null\"\n [attr.tabindex]=\"rowTabindex(dr)\"\n [attr.aria-level]=\"treeMode() ? dr.level : null\"\n [attr.aria-expanded]=\"treeMode() && dr.hasChildren ? dr.expanded : null\"\n [attr.aria-posinset]=\"treeMode() ? dr.posInSet : null\"\n [attr.aria-setsize]=\"treeMode() ? dr.setSize : null\"\n [attr.aria-selected]=\"treeMode() && selectable() ? isRowSelected(dr.row) : null\"\n (click)=\"onRowClick(dr.row, $event)\"\n (focus)=\"onRowFocus(dr)\"\n (keydown)=\"onRowKeydown($event, dr)\"\n >\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\">\n <!-- A click in the cell toggles the box, never the row (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"isRowSelected(dr.row)\"\n (change)=\"toggleRowSelect(dr.row)\"\n (click)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (dr.absIndex + 1)\"\n />\n </label>\n </td>\n }\n @for (column of columns(); track column.key) {\n <td\n [class]=\"getCellClasses(dr.row, column, dr.absIndex)\"\n [ngStyle]=\"getCellStyles(dr.row, column, dr.absIndex)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (dblclick)=\"editable() ? startEdit(dr.row, column.key, getCellValue(dr.row, column.key)) : null\"\n >\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n @for (lvl of indentLevels(dr.depth); track lvl) {\n <span class=\"lc-table__tree-indent\" [style.width.px]=\"indentSize()\"></span>\n }\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\">\n @if (dr.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-table__chevron-btn\"\n [class.lc-table__chevron-btn--expanded]=\"dr.expanded\"\n [attr.aria-label]=\"(dr.expanded ? collapseRowAriaLabel() : expandRowAriaLabel()) + ' ' + treeCellLabel(dr.row)\"\n [attr.aria-expanded]=\"dr.expanded\"\n (click)=\"onChevronClick(dr, $event)\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n <span class=\"lc-table__tree-content\">\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n </span>\n </span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n }\n </td>\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"lc-table__actions\">\n @for (action of actions(); track action.key) {\n @if (!isActionHidden(action, dr.row, dr.absIndex)) {\n <lc-button\n [variant]=\"action.variant ?? 'ghost'\"\n size=\"xs\"\n [iconOnly]=\"!action.label\"\n [disabled]=\"isActionDisabled(action, dr.row, dr.absIndex)\"\n [ariaLabel]=\"actionAriaLabel(action, dr.row, dr.absIndex)\"\n (clicked)=\"onActionClick(action, dr.row, dr.absIndex)\"\n >\n @if (action.icon) {\n <lc-icon [name]=\"action.icon\" size=\"sm\" [decorative]=\"true\" />\n }\n @if (action.label) {\n {{ action.label }}\n }\n </lc-button>\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n @if (footerRow(); as footer) {\n <!-- 3.36: one footer row below every data row; never sorted, filtered,\n paged, selected or counted. Its first cell is the row header. -->\n <tfoot class=\"lc-table__footer\">\n <tr [attr.role]=\"treeMode() ? 'row' : null\">\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\"></td>\n }\n @for (column of columns(); track column.key; let first = $first) {\n @if (first) {\n <th\n scope=\"row\"\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'rowheader' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </th>\n } @else {\n <td\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </td>\n }\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n ></td>\n }\n </tr>\n </tfoot>\n }\n </table>\n</div>\n\n<!-- 3.36: a footer cell. In the tree column the label lines up with the level-0\n labels (an empty disclosure slot before it). Templates get `footer: true`. -->\n<ng-template #footerCell let-row=\"row\" let-column=\"column\">\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\"></span>\n <span class=\"lc-table__tree-content\">\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n </span>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n }\n</ng-template>\n\n<ng-template #footerContent let-row=\"row\" let-column=\"column\">\n @if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row, footer: true }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, -1) }}\n }\n</ng-template>\n\n<!-- Shared cell content (edit input / custom template / formatted value) -->\n<ng-template #cellContent let-row=\"row\" let-column=\"column\" let-absIndex=\"absIndex\">\n @if (isEditing(row, column.key)) {\n <input\n #editInput\n type=\"text\"\n class=\"lc-table__edit-input\"\n [value]=\"editValue()\"\n (input)=\"editValue.set(getInputValue($event))\"\n (blur)=\"commitEdit(row, column.key)\"\n (keydown)=\"onEditKeydown($event, row, column.key)\"\n />\n } @else if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, absIndex) }}\n }\n</ng-template>\n\n<!-- Pagination -->\n@if (paginate()) {\n <div [class]=\"'lc-table-pagination lc-table-pagination--' + paginationSize()\">\n <div class=\"lc-table-pagination__info\">\n {{ paginationStart }}–{{ paginationEnd }} {{ rangeOfLabel() }} {{ totalRows() }}\n </div>\n <div class=\"lc-table-pagination__controls\">\n <lc-select\n class=\"lc-table-pagination__size\"\n [size]=\"paginationSize()\"\n [options]=\"pageSizeSelectOptions()\"\n [ngModel]=\"internalPageSize()\"\n [ariaLabel]=\"rowsPerPageAriaLabel()\"\n (ngModelChange)=\"onPageSizeModelChange($event)\"\n />\n <div class=\"lc-table-pagination__pager\">\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() === 0\"\n (click)=\"goToPage(currentPage() - 1)\"\n [attr.aria-label]=\"previousPageAriaLabel()\"\n >‹</button>\n <span class=\"lc-table-pagination__page\">\n {{ currentPage() + 1 }} / {{ totalPages() }}\n </span>\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() >= totalPages() - 1\"\n (click)=\"goToPage(currentPage() + 1)\"\n [attr.aria-label]=\"nextPageAriaLabel()\"\n >›</button>\n </div>\n </div>\n </div>\n}\n",
1182
+ template: "<div [class]=\"wrapperClasses()\">\n <table [class]=\"tableClasses()\" [attr.role]=\"treeMode() ? 'treegrid' : 'table'\">\n @if (caption()) {\n <!-- First child of the table, so it names the table (and the treegrid). -->\n <caption class=\"lc-table__caption\" [class.lc-table__caption--hidden]=\"!showCaption()\">{{ caption() }}</caption>\n }\n <thead>\n <!-- Filter row -->\n @if (filterable()) {\n <tr class=\"lc-table__filter-row\">\n @if (selectable()) {\n <th class=\"lc-table__select-cell\"></th>\n }\n @for (column of columns(); track column.key) {\n <th>\n @if (column.filterable !== false) {\n <lc-input\n class=\"lc-table__filter-input\"\n size=\"xs\"\n [placeholder]=\"filterPlaceholderLabel() + ' ' + column.label\"\n [ariaLabel]=\"filterAriaLabel() + ' ' + column.label\"\n [ngModel]=\"getFilterValue(column.key)\"\n (ngModelChange)=\"onFilterChange(column.key, $event)\"\n />\n }\n </th>\n }\n @if (hasActions()) {\n <th class=\"lc-table__actions-cell\"></th>\n }\n </tr>\n }\n <!-- Header row -->\n <tr>\n @if (selectable()) {\n <th class=\"lc-table__select-cell\" scope=\"col\">\n <!-- The label's ::before covers the cell: a 44 px hit area (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (change)=\"toggleSelectAll()\"\n [attr.aria-label]=\"selectAllAriaLabel()\"\n />\n </label>\n </th>\n }\n @for (column of columns(); track column.key) {\n <!-- A sortable header is a keyboard stop too: Enter/Space sort like a click. -->\n <th\n scope=\"col\"\n [class]=\"getHeaderClasses(column) + (column.cssClass ? ' ' + column.cssClass : '')\"\n [attr.aria-sort]=\"getAriaSort(column.key)\"\n [attr.tabindex]=\"column.sortable ? 0 : null\"\n [style.width]=\"column.width\"\n [title]=\"column.tooltip || ''\"\n (click)=\"handleSort(column.key)\"\n (keydown)=\"column.sortable ? onHeaderKeydown($event, column.key) : null\">\n @if (column.align === 'end') {\n <!-- 3.17: the arrow is placed before the label (CSS), so the label stays\n flush with the figures; in the DOM it still follows the label. No\n whitespace between the label and the arrow. -->\n <span class=\"lc-table__sort-label\">{{ column.label }}@if (column.sortable && getSortState(column.key)) {<span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>}</span>\n } @else {\n {{ column.label }}\n @if (column.sortable && getSortState(column.key)) {\n <span class=\"sort-indicator\" [attr.aria-label]=\"getSortState(column.key) === 'asc' ? sortedAscendingLabel() : sortedDescendingLabel()\">\n @if (getSortState(column.key) === 'asc') {\n ↑\n } @else {\n ↓\n }\n </span>\n }\n }\n </th>\n }\n @if (hasActions()) {\n <th\n scope=\"col\"\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [style.width]=\"actionsWidth()\"\n >\n {{ actionsLabel() }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (displayRows().length === 0) {\n <tr>\n <td [attr.colspan]=\"columns().length + (selectable() ? 1 : 0) + (hasActions() ? 1 : 0)\" class=\"lc-table__empty\">\n {{ emptyText() }}\n </td>\n </tr>\n } @else {\n @for (dr of displayRows(); track dr.key) {\n <!-- In tree mode the rows form the treegrid's keyboard model: a roving\n tab stop plus Arrow/Home/End/Enter/Space handling (see the\n component). Flat mode keeps plain table rows. -->\n <tr\n #dataRow\n [class.lc-table__row--selected]=\"selectable() && isRowSelected(dr.row)\"\n [class.lc-table__row--group]=\"dr.hasChildren\"\n [attr.role]=\"treeMode() ? 'row' : null\"\n [attr.tabindex]=\"rowTabindex(dr)\"\n [attr.aria-level]=\"treeMode() ? dr.level : null\"\n [attr.aria-expanded]=\"treeMode() && dr.hasChildren ? dr.expanded : null\"\n [attr.aria-posinset]=\"treeMode() ? dr.posInSet : null\"\n [attr.aria-setsize]=\"treeMode() ? dr.setSize : null\"\n [attr.aria-selected]=\"treeMode() && selectable() ? isRowSelected(dr.row) : null\"\n (click)=\"onRowClick(dr.row, $event)\"\n (focus)=\"onRowFocus(dr)\"\n (keydown)=\"onRowKeydown($event, dr)\"\n >\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\">\n <!-- A click in the cell toggles the box, never the row (request 043). -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the click; the checkbox inside is the keyboard control -->\n <label class=\"lc-table__select-hit\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"lc-table__checkbox\"\n [checked]=\"isRowSelected(dr.row)\"\n (change)=\"toggleRowSelect(dr.row)\"\n (click)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (dr.absIndex + 1)\"\n />\n </label>\n </td>\n }\n @for (column of columns(); track column.key) {\n <td\n [class]=\"getCellClasses(dr.row, column, dr.absIndex)\"\n [ngStyle]=\"getCellStyles(dr.row, column, dr.absIndex)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (dblclick)=\"editable() ? startEdit(dr.row, column.key, getCellValue(dr.row, column.key)) : null\"\n >\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n @for (lvl of indentLevels(dr.depth); track lvl) {\n <span class=\"lc-table__tree-indent\" [style.width.px]=\"indentSize()\"></span>\n }\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\">\n @if (dr.hasChildren) {\n <button\n type=\"button\"\n class=\"lc-table__chevron-btn\"\n [class.lc-table__chevron-btn--expanded]=\"dr.expanded\"\n [attr.aria-label]=\"(dr.expanded ? collapseRowAriaLabel() : expandRowAriaLabel()) + ' ' + treeCellLabel(dr.row)\"\n [attr.aria-expanded]=\"dr.expanded\"\n (click)=\"onChevronClick(dr, $event)\"\n >\n <lc-icon name=\"chevron-right\" size=\"xs\" [decorative]=\"true\" />\n </button>\n }\n </span>\n <span class=\"lc-table__tree-content\">\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n </span>\n </span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellContent; context: { row: dr.row, column: column, absIndex: dr.absIndex }\"\n ></ng-container>\n }\n </td>\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"lc-table__actions\">\n @for (action of actions(); track action.key) {\n @if (!isActionHidden(action, dr.row, dr.absIndex)) {\n <lc-button\n [variant]=\"action.variant ?? 'ghost'\"\n [size]=\"action.size ?? 'xs'\"\n [iconOnly]=\"!action.label\"\n [disabled]=\"isActionDisabled(action, dr.row, dr.absIndex)\"\n [ariaLabel]=\"actionAriaLabel(action, dr.row, dr.absIndex)\"\n (clicked)=\"onActionClick(action, dr.row, dr.absIndex)\"\n >\n @if (action.icon) {\n <lc-icon [name]=\"action.icon\" size=\"sm\" [decorative]=\"true\" />\n }\n @if (action.label) {\n {{ action.label }}\n }\n </lc-button>\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n @if (footerRow(); as footer) {\n <!-- 3.36: one footer row below every data row; never sorted, filtered,\n paged, selected or counted. Its first cell is the row header. -->\n <tfoot class=\"lc-table__footer\">\n <tr [attr.role]=\"treeMode() ? 'row' : null\">\n @if (selectable()) {\n <td class=\"lc-table__select-cell\" [attr.role]=\"treeMode() ? 'gridcell' : null\"></td>\n }\n @for (column of columns(); track column.key; let first = $first) {\n @if (first) {\n <th\n scope=\"row\"\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'rowheader' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </th>\n } @else {\n <td\n [class]=\"getCellClasses(footer, column, -1)\"\n [ngStyle]=\"getCellStyles(footer, column, -1)\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n >\n <ng-container *ngTemplateOutlet=\"footerCell; context: { row: footer, column: column }\"></ng-container>\n </td>\n }\n }\n @if (hasActions()) {\n <td\n class=\"lc-table__actions-cell\"\n [class]=\"'lc-table__actions-cell--' + actionsAlign()\"\n [attr.role]=\"treeMode() ? 'gridcell' : null\"\n ></td>\n }\n </tr>\n </tfoot>\n }\n </table>\n</div>\n\n<!-- 3.36: a footer cell. In the tree column the label lines up with the level-0\n labels (an empty disclosure slot before it). Templates get `footer: true`. -->\n<ng-template #footerCell let-row=\"row\" let-column=\"column\">\n @if (isTreeColumn(column.key)) {\n <span class=\"lc-table__tree-cell\">\n <span class=\"lc-table__tree-chevron\" [style.width.px]=\"indentSize()\"></span>\n <span class=\"lc-table__tree-content\">\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n </span>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"footerContent; context: { row: row, column: column }\"></ng-container>\n }\n</ng-template>\n\n<ng-template #footerContent let-row=\"row\" let-column=\"column\">\n @if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row, footer: true }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, -1) }}\n }\n</ng-template>\n\n<!-- Shared cell content (edit input / custom template / formatted value) -->\n<ng-template #cellContent let-row=\"row\" let-column=\"column\" let-absIndex=\"absIndex\">\n @if (isEditing(row, column.key)) {\n <input\n #editInput\n type=\"text\"\n class=\"lc-table__edit-input\"\n [value]=\"editValue()\"\n (input)=\"editValue.set(getInputValue($event))\"\n (blur)=\"commitEdit(row, column.key)\"\n (keydown)=\"onEditKeydown($event, row, column.key)\"\n />\n } @else if (hasCustomTemplate(column.key)) {\n <ng-container *ngTemplateOutlet=\"getCellTemplate(column.key)!.template; context: { $implicit: row }\"></ng-container>\n } @else {\n {{ getFormattedCellValue(row, column, absIndex) }}\n }\n</ng-template>\n\n<!-- Pagination -->\n@if (paginate()) {\n <div [class]=\"'lc-table-pagination lc-table-pagination--' + paginationSize()\">\n <div class=\"lc-table-pagination__info\">\n {{ paginationStart }}–{{ paginationEnd }} {{ rangeOfLabel() }} {{ totalRows() }}\n </div>\n <div class=\"lc-table-pagination__controls\">\n <lc-select\n class=\"lc-table-pagination__size\"\n [size]=\"paginationSize()\"\n [options]=\"pageSizeSelectOptions()\"\n [ngModel]=\"internalPageSize()\"\n [ariaLabel]=\"rowsPerPageAriaLabel()\"\n (ngModelChange)=\"onPageSizeModelChange($event)\"\n />\n <div class=\"lc-table-pagination__pager\">\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() === 0\"\n (click)=\"goToPage(currentPage() - 1)\"\n [attr.aria-label]=\"previousPageAriaLabel()\"\n >‹</button>\n <span class=\"lc-table-pagination__page\">\n {{ currentPage() + 1 }} / {{ totalPages() }}\n </span>\n <button\n type=\"button\"\n class=\"lc-table-pagination__btn\"\n [disabled]=\"currentPage() >= totalPages() - 1\"\n (click)=\"goToPage(currentPage() + 1)\"\n [attr.aria-label]=\"nextPageAriaLabel()\"\n >›</button>\n </div>\n </div>\n </div>\n}\n",
1183
1183
  styles: [".lc-table-wrapper{width:100%}.lc-table-wrapper--responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width:768px){.lc-table-wrapper--responsive::-webkit-scrollbar{height:8px}.lc-table-wrapper--responsive::-webkit-scrollbar-thumb{background-color:var(--color-border);border-radius:var(--border-radius-sm)}}.lc-table{width:100%;border-collapse:collapse;border-spacing:0;background-color:var(--color-surface);color:var(--color-text-primary);font-family:var(--typography-font-family)}.lc-table thead{background-color:var(--color-surface-2);border-bottom:2px solid var(--color-border-strong)}.lc-table thead tr th{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));text-align:left;font-weight:var(--typography-font-weight-semibold);font-size:var(--typography-font-size-sm);text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-secondary);white-space:nowrap;-webkit-user-select:none;user-select:none}.lc-table thead tr th.sortable{cursor:pointer;position:relative;padding-right:var(--spacing-8);transition:background-color .2s ease}.lc-table thead tr th.sortable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-table thead tr th.sortable:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table thead tr th.sortable .sort-indicator{position:absolute;right:var(--spacing-3);top:50%;transform:translateY(-50%);font-size:var(--typography-font-size-xs);color:var(--color-primary)}.lc-table thead tr th.sorted-asc,.lc-table thead tr th.sorted-desc{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table tbody tr{border-bottom:1px solid var(--color-divider)}.lc-table tbody tr:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-table tbody tr:last-child{border-bottom:none}.lc-table tbody tr td{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4))}.lc-table__empty{text-align:center;padding:var(--spacing-8) var(--spacing-4)!important;font-style:italic}.lc-table--striped tbody tr:nth-child(2n){background-color:var(--color-surface-2)}.lc-table--bordered{border:1px solid var(--color-border)}.lc-table--bordered thead tr th,.lc-table--bordered tbody tr td{border-right:1px solid var(--color-divider)}.lc-table--bordered thead tr th:last-child,.lc-table--bordered tbody tr td:last-child{border-right:none}.lc-table--bordered tbody tr{border-bottom:1px solid var(--color-border)}.lc-table--hoverable tbody tr,.lc-table--clickable tbody tr{transition:background-color .15s ease;cursor:pointer}.lc-table--hoverable tbody tr:hover,.lc-table--clickable tbody tr:hover{background-color:var(--color-surface-hover)}.lc-table--sm thead tr th,.lc-table--sm tbody tr td{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--sm thead tr th{font-size:var(--typography-font-size-xs)}.lc-table--lg thead tr th,.lc-table--lg tbody tr td{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5)}.lc-table--lg thead tr th{font-size:var(--typography-font-size-base)}:where(.lc-table tbody tr td){font-size:var(--typography-font-size-sm);color:var(--color-text-primary)}:where(.lc-table--sm tbody tr td){font-size:var(--typography-font-size-xs)}:where(.lc-table--lg tbody tr td){font-size:var(--typography-font-size-base)}.lc-table thead tr th.lc-table__cell--end,.lc-table tbody tr td.lc-table__cell--end{text-align:end;font-variant-numeric:tabular-nums}.lc-table thead tr th.sortable.lc-table__cell--end{padding-inline-start:var(--spacing-8);padding-inline-end:var(--lc-density-padding-md, var(--spacing-4))}.lc-table thead tr th.sortable.lc-table__cell--end .sort-indicator{right:auto;inset-inline-end:calc(100% + var(--spacing-2))}.lc-table--sm thead tr th.sortable.lc-table__cell--end{padding-inline-end:var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--lg thead tr th.sortable.lc-table__cell--end{padding-inline-end:var(--spacing-5)}.lc-table__sort-label{position:relative;display:inline-block}.lc-table__cell--end .lc-table__edit-input{text-align:end;font-variant-numeric:tabular-nums}.lc-table__caption{caption-side:top;padding:0 0 var(--spacing-3);text-align:start;font-family:var(--typography-font-family);font-size:var(--typography-font-size-base);font-weight:var(--typography-font-weight-semibold);line-height:var(--typography-line-height-normal);color:var(--color-text-primary)}.lc-table__caption--hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(forced-colors:active){.lc-table tbody tr:focus-visible{outline:2px solid Highlight}}.lc-table__filter-row th{padding:var(--spacing-2) var(--spacing-4)!important;background-color:var(--color-surface)!important;border-bottom:1px solid var(--color-divider)!important}.lc-table__filter-input{width:100%;display:block}.lc-table__filter-input .input-field{font-size:var(--typography-font-size-xs)}.lc-table__select-cell{position:relative;width:var(--size-min-touch-height, 2.75rem);text-align:center!important;padding:var(--spacing-2) calc((var(--size-min-touch-height, 2.75rem) - 1rem) / 2)!important}.lc-table__select-hit{cursor:pointer}.lc-table__select-hit:before{content:\"\";position:absolute;inset:0}.lc-table__checkbox{position:relative;width:16px;height:16px;cursor:pointer;accent-color:var(--color-primary);display:block;margin:2.5px auto;margin:max(2.5px,(1lh - 16px) / 2) auto}.lc-table__checkbox:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-table__row--selected{background-color:var(--color-surface-selected)!important}.lc-table__actions-cell{white-space:nowrap}.lc-table thead tr th.lc-table__actions-cell--start,.lc-table tbody tr td.lc-table__actions-cell--start{text-align:left}.lc-table thead tr th.lc-table__actions-cell--center,.lc-table tbody tr td.lc-table__actions-cell--center{text-align:center}.lc-table thead tr th.lc-table__actions-cell--end,.lc-table tbody tr td.lc-table__actions-cell--end{text-align:right}.lc-table__actions{display:flex;align-items:center;gap:var(--spacing-2);justify-content:flex-start}.lc-table__actions-cell--center .lc-table__actions{justify-content:center}.lc-table__actions-cell--end .lc-table__actions{justify-content:flex-end}.lc-table__tree-cell{display:flex;align-items:center;min-width:0}.lc-table__tree-indent{flex:0 0 auto;align-self:stretch;position:relative}.lc-table__tree-indent:before{content:\"\";position:absolute;left:50%;top:0;bottom:0;width:1px;background-color:var(--color-border)}.lc-table__tree-chevron{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center}.lc-table__chevron-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;border-radius:var(--border-radius-sm);transition:transform .2s ease,background-color .15s ease,color .15s ease}.lc-table__chevron-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-table__chevron-btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:1px}.lc-table__chevron-btn--expanded{transform:rotate(90deg)}.lc-table__tree-content{flex:1 1 auto;min-width:0}.lc-table__row--group .lc-table__tree-content{font-weight:var(--typography-font-weight-semibold)}.lc-table__edit-input{width:100%;padding:var(--spacing-1) var(--spacing-2);border:2px solid var(--color-primary);border-radius:var(--border-radius-sm);font-size:inherit;background-color:var(--color-surface-2);color:var(--color-text-primary);outline:none}.lc-table-pagination{--_lc-table-pagination-btn: var(--size-interactive-md-height);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--spacing-2) var(--spacing-4);padding:var(--spacing-3) var(--spacing-1);font-size:var(--typography-font-size-sm);color:var(--color-text-secondary)}.lc-table-pagination--sm{--_lc-table-pagination-btn: var(--size-interactive-sm-height)}.lc-table-pagination--lg{--_lc-table-pagination-btn: var(--size-interactive-lg-height)}.lc-table-pagination__info{white-space:nowrap}.lc-table-pagination__controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--spacing-2);margin-inline-start:auto}.lc-table-pagination__pager{display:flex;align-items:center;gap:var(--spacing-2)}.lc-table-pagination__size{--lc-select-clear-display: none;display:block;min-width:110px}.lc-table-pagination__btn{position:relative;appearance:none;border:1px solid var(--color-field-border);background-color:var(--color-surface-2);color:var(--color-text-primary);width:var(--_lc-table-pagination-btn);height:var(--_lc-table-pagination-btn);display:flex;align-items:center;justify-content:center;padding:0;border-radius:var(--border-radius-md);font-size:var(--typography-font-size-xl);line-height:1;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}.lc-table-pagination__btn:before{content:\"\";position:absolute;inset:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2)}.lc-table-pagination__btn:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-table-pagination__btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-table-pagination__btn:disabled{opacity:.4;cursor:not-allowed}.lc-table-pagination__page{white-space:nowrap;min-width:60px;text-align:center}@media(max-width:640px){.lc-table-wrapper--stack .lc-table thead{display:none}.lc-table-wrapper--stack .lc-table tbody tr{display:block;margin-bottom:var(--spacing-4);border:1px solid var(--color-border);border-radius:var(--border-radius-md)}.lc-table-wrapper--stack .lc-table tbody tr td{display:block;text-align:right;padding:var(--spacing-2) var(--spacing-4);border-bottom:1px solid var(--color-divider)}.lc-table-wrapper--stack .lc-table tbody tr td:last-child{border-bottom:none}.lc-table-wrapper--stack .lc-table tbody tr td:before{content:attr(data-label);float:left;font-weight:var(--typography-font-weight-semibold);color:var(--color-text-secondary)}}@media(forced-colors:active){.lc-table__checkbox:focus-visible{outline-color:Highlight}}.lc-table tfoot tr{border-top:1px solid var(--color-field-border);border-bottom:3px double var(--color-field-border)}.lc-table tfoot tr th,.lc-table tfoot tr td{padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));text-align:start;font-weight:var(--typography-font-weight-semibold);font-variant-numeric:tabular-nums}.lc-table tfoot tr th.lc-table__cell--end,.lc-table tfoot tr td.lc-table__cell--end{text-align:end}.lc-table--sm tfoot tr th,.lc-table--sm tfoot tr td{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3))}.lc-table--lg tfoot tr th,.lc-table--lg tfoot tr td{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5)}.lc-table--bordered tfoot tr th,.lc-table--bordered tfoot tr td{border-right:1px solid var(--color-divider)}.lc-table--bordered tfoot tr th:last-child,.lc-table--bordered tfoot tr td:last-child{border-right:none}:where(.lc-table tfoot tr th,.lc-table tfoot tr td){font-size:var(--typography-font-size-sm);color:var(--color-text-primary)}:where(.lc-table--sm tfoot tr th,.lc-table--sm tfoot tr td){font-size:var(--typography-font-size-xs)}:where(.lc-table--lg tfoot tr th,.lc-table--lg tfoot tr td){font-size:var(--typography-font-size-base)}\n"]
1184
1184
  }]
1185
1185
  }],