@lifecorner/angular-ui-kit 3.25.1 → 3.27.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.
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +18 -2
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +47 -9
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +8 -4
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_reset.scss +4 -2
- package/src/styles/focus-indicators.spec.ts +199 -0
- package/types/lifecorner-angular-ui-kit-button.d.ts +20 -0
- package/types/lifecorner-angular-ui-kit-dependency-viewer.d.ts +33 -4
- package/types/lifecorner-angular-ui-kit-description-list.d.ts +4 -0
- package/types/lifecorner-angular-ui-kit-file-upload.d.ts +11 -1
|
@@ -94,7 +94,7 @@ var DescriptionListComponent = class DescriptionListComponent {
|
|
|
94
94
|
outputs: { itemClick: "itemClick" },
|
|
95
95
|
ngImport: i0,
|
|
96
96
|
template: "<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the row stays a dt/dd group (F-17): its button is the focusable control, and its click and keys bubble here -->\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\" [attr.id]=\"clickable() ? termId(i) : null\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\" [attr.id]=\"clickable() ? valueId(i) : null\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n @if (clickable()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content -- named by aria-labelledby (the row's term and value); text inside would repeat the value -->\n <button\n type=\"button\"\n class=\"lc-dl__action\"\n [attr.aria-labelledby]=\"termId(i) + ' ' + valueId(i)\"\n ></button>\n }\n </dd>\n </div>\n }\n</dl>\n",
|
|
97
|
-
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{position:relative;cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__action{position:absolute;inset:0;margin:0;padding:0;border:0;background:none;border-radius:var(--border-radius-sm);pointer-events:none}.lc-dl__action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-on-primary-subtle);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-link);text-decoration:none}.lc-dl__link:hover{color:var(--color-link-hover);text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}.lc-dl--rows .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-2),(var(--size-min-touch-height) - 1lh) / 2)}.lc-dl--rows.lc-dl--sm .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-1),(var(--size-min-touch-height) - 1lh) / 2)}@media(forced-colors:active){.lc-dl__action:focus-visible{outline-color:Highlight}}\n"],
|
|
97
|
+
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{position:relative;cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__action{position:absolute;inset:0;margin:0;padding:0;border:0;background:none;border-radius:var(--border-radius-sm);pointer-events:none}.lc-dl__action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-on-primary-subtle);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-link);text-decoration:none}.lc-dl__link:hover{color:var(--color-link-hover);text-decoration:underline}.lc-dl__link:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows .lc-dl__term,.lc-dl--rows .lc-dl__value{flex:1000000 1 0%;max-width:max-content}.lc-dl--rows .lc-dl__term{min-width:0;overflow-wrap:break-word}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}@supports (margin-block-start: 1lh){.lc-dl--leaders .lc-dl__leader{align-self:flex-start;margin-block-start:calc(.5lh - .5px)}}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}.lc-dl--rows .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-2),(var(--size-min-touch-height) - 1lh) / 2)}.lc-dl--rows.lc-dl--sm .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-1),(var(--size-min-touch-height) - 1lh) / 2)}@media(forced-colors:active){.lc-dl__action:focus-visible{outline-color:Highlight}}@media(forced-colors:active){.lc-dl__link:focus-visible{outline-color:Highlight}}\n"],
|
|
98
98
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
99
99
|
});
|
|
100
100
|
};
|
|
@@ -110,7 +110,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
110
110
|
standalone: true,
|
|
111
111
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
112
112
|
template: "<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the row stays a dt/dd group (F-17): its button is the focusable control, and its click and keys bubble here -->\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\" [attr.id]=\"clickable() ? termId(i) : null\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\" [attr.id]=\"clickable() ? valueId(i) : null\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n @if (clickable()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content -- named by aria-labelledby (the row's term and value); text inside would repeat the value -->\n <button\n type=\"button\"\n class=\"lc-dl__action\"\n [attr.aria-labelledby]=\"termId(i) + ' ' + valueId(i)\"\n ></button>\n }\n </dd>\n </div>\n }\n</dl>\n",
|
|
113
|
-
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{position:relative;cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__action{position:absolute;inset:0;margin:0;padding:0;border:0;background:none;border-radius:var(--border-radius-sm);pointer-events:none}.lc-dl__action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-on-primary-subtle);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-link);text-decoration:none}.lc-dl__link:hover{color:var(--color-link-hover);text-decoration:underline}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}.lc-dl--rows .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-2),(var(--size-min-touch-height) - 1lh) / 2)}.lc-dl--rows.lc-dl--sm .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-1),(var(--size-min-touch-height) - 1lh) / 2)}@media(forced-colors:active){.lc-dl__action:focus-visible{outline-color:Highlight}}\n"]
|
|
113
|
+
styles: [":host{display:block}.lc-dl{margin:0}.lc-dl__row{display:flex;align-items:baseline;gap:var(--spacing-2)}.lc-dl__row--clickable{position:relative;cursor:pointer;border-radius:var(--border-radius-sm)}.lc-dl__row--clickable:hover{background:var(--color-surface-hover)}.lc-dl__action{position:absolute;inset:0;margin:0;padding:0;border:0;background:none;border-radius:var(--border-radius-sm);pointer-events:none}.lc-dl__action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl__term{margin:0;flex-shrink:0;color:var(--color-text-secondary);font-weight:var(--font-weight-medium)}.lc-dl__leader{flex:1 1 auto;align-self:center;min-width:var(--spacing-4)}.lc-dl__value{margin:0;flex:0 1 auto;min-width:0;overflow-wrap:anywhere;text-align:right;color:var(--color-text-primary);font-weight:var(--font-weight-medium);font-variant-numeric:tabular-nums}.lc-dl__value--muted{color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__value--strong{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.lc-dl__value--primary{color:var(--color-on-primary-subtle);font-weight:var(--font-weight-semibold)}.lc-dl__value--mono{font-family:var(--font-family-mono);font-variant-numeric:normal}.lc-dl__value-suffix{margin-left:.375rem;color:var(--color-text-tertiary);font-weight:var(--font-weight-normal)}.lc-dl__link{color:var(--color-link);text-decoration:none}.lc-dl__link:hover{color:var(--color-link-hover);text-decoration:underline}.lc-dl__link:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-dl--rows .lc-dl__row{padding-block:var(--spacing-2)}.lc-dl--rows .lc-dl__term,.lc-dl--rows .lc-dl__value{flex:1000000 1 0%;max-width:max-content}.lc-dl--rows .lc-dl__term{min-width:0;overflow-wrap:break-word}.lc-dl--rows.lc-dl--sep-line .lc-dl__row,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom:1px solid var(--color-divider)}.lc-dl--rows.lc-dl--sep-line .lc-dl__row:last-child,.lc-dl--rows.lc-dl--sep-divider .lc-dl__row:last-child{border-bottom:0}.lc-dl--rows.lc-dl--sep-divider .lc-dl__row{border-bottom-style:dashed}.lc-dl--leaders .lc-dl__leader{border-bottom:1px dotted var(--color-border-strong);transform:translateY(-.15em)}@supports (margin-block-start: 1lh){.lc-dl--leaders .lc-dl__leader{align-self:flex-start;margin-block-start:calc(.5lh - .5px)}}.lc-dl--stacked .lc-dl__row{flex-direction:column;align-items:stretch;gap:var(--spacing-1);padding-block:var(--spacing-2)}.lc-dl--stacked .lc-dl__leader{display:none}.lc-dl--stacked .lc-dl__value{text-align:left}.lc-dl--sm{font-size:var(--font-size-xs)}.lc-dl--sm .lc-dl__row{padding-block:var(--spacing-1)}.lc-dl--md{font-size:var(--font-size-sm)}.lc-dl--rows .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-2),(var(--size-min-touch-height) - 1lh) / 2)}.lc-dl--rows.lc-dl--sm .lc-dl__row.lc-dl__row--clickable{padding-block:max(var(--spacing-1),(var(--size-min-touch-height) - 1lh) / 2)}@media(forced-colors:active){.lc-dl__action:focus-visible{outline-color:Highlight}}@media(forced-colors:active){.lc-dl__link:focus-visible{outline-color:Highlight}}\n"]
|
|
114
114
|
}]
|
|
115
115
|
}],
|
|
116
116
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-description-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.ts","../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\n\n/**\n * Emphasis treatment applied to a row's value.\n * - `mono` renders the value in the monospace font (paths, URLs, hashes).\n */\nexport type DescriptionListEmphasis =\n | 'default'\n | 'strong'\n | 'muted'\n | 'primary'\n | 'mono';\n\nexport interface DescriptionListItem {\n /** Label shown on the term side of the row. */\n term: string;\n /** Value shown on the description side of the row. */\n value: string;\n /**\n * Optional qualifying suffix appended after the value on the same line and\n * rendered muted (e.g. a rating grade or \"last checked\" hint). Wraps if the\n * combined value + suffix exceed the available width.\n */\n valueSuffix?: string;\n /** Optional link — renders the value as an anchor. */\n href?: string;\n /**\n * Machine-readable date or time of the value (ISO 8601, e.g.\n * `2026-10-12T18:05:00+02:00`): renders the value inside\n * `<time datetime>`, so assistive tech and parsers get the exact moment\n * behind „heute 18:05“. Ignored together with `href`.\n */\n datetime?: string;\n /** Optional visual emphasis for the value. @default 'default' */\n emphasis?: DescriptionListEmphasis;\n /** Optional opaque payload echoed back in `itemClick`. */\n id?: string;\n}\n\nexport type DescriptionListLayout = 'rows' | 'stacked';\nexport type DescriptionListSize = 'sm' | 'md';\n\n/**\n * Per-row separator style (applies to the `rows` layout).\n * - `line` solid hairline under each row (default — unchanged look)\n * - `divider` subtle dashed line, for the dense card look\n * - `none` no row separator\n *\n * Orthogonal to `leaders`, which draws dotted leaders *between* term and value.\n */\nexport type DescriptionListSeparator = 'line' | 'divider' | 'none';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Description list for key/value metadata (a styled `<dl>`).\n *\n * Features:\n * - `rows` layout: term on the left, value on the right, with an optional\n * dotted leader line connecting them (the DS2.0 \"spec sheet\" look).\n * - `stacked` layout: term above value, for narrow columns.\n * - Per-row value emphasis and optional links.\n * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`\n * rows hold a native button (named by term and value) that the keyboard\n * reaches, and the `dl` keeps its term and value pairs.\n *\n * @example\n * ```html\n * <lc-description-list\n * [items]=\"[\n * { term: 'Repository', value: 'example/project', href: '#', emphasis: 'primary' },\n * { term: 'Access', value: 'Token stored · read only' },\n * { term: 'Status', value: 'Maintained', emphasis: 'strong' }\n * ]\"\n * [leaders]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-description-list',\n standalone: true,\n templateUrl: './description-list.component.html',\n styleUrls: ['./description-list.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DescriptionListComponent {\n /** Rows to render. */\n readonly items = input.required<readonly DescriptionListItem[]>();\n\n /**\n * Row layout.\n * - rows: term left / value right (default)\n * - stacked: term above value\n * @default 'rows'\n */\n readonly layout = input<DescriptionListLayout>('rows');\n\n /**\n * Draw a dotted leader line between term and value. Only applies to the\n * `rows` layout.\n * @default false\n */\n readonly leaders = input<boolean>(false);\n\n /** Density of the rows. @default 'md' */\n readonly size = input<DescriptionListSize>('md');\n\n /**\n * Per-row separator style for the `rows` layout. Defaults to `line`, which is\n * identical to the previous behavior. Orthogonal to `leaders`.\n * @default 'line'\n */\n readonly separator = input<DescriptionListSeparator>('line');\n\n /**\n * Whether rows are interactive. When set, each row holds a native button,\n * named by its term and value, that Tab reaches; Enter, Space and a click\n * anywhere on the row emit `itemClick`. The `dl` keeps its term and value\n * pairs. Set this whenever you handle `itemClick`.\n * @default false\n */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */\n readonly itemClick = output<DescriptionListItem>();\n\n /** Generated id prefix, taken once per instance, so later ids don't shift. */\n private readonly uid = `lc-dl-${nextUniqueId++}`;\n\n /** Id of row `index`'s term (only rendered when `clickable`). */\n protected termId(index: number): string {\n return `${this.uid}-t${index}`;\n }\n\n /** Id of row `index`'s value (only rendered when `clickable`). */\n protected valueId(index: number): string {\n return `${this.uid}-v${index}`;\n }\n\n protected readonly hostClasses = computed(() =>\n [\n 'lc-dl',\n `lc-dl--${this.layout()}`,\n `lc-dl--${this.size()}`,\n `lc-dl--sep-${this.separator()}`,\n this.leaders() && this.layout() === 'rows' ? 'lc-dl--leaders' : '',\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n protected valueClass(item: DescriptionListItem): string {\n return `lc-dl__value lc-dl__value--${item.emphasis ?? 'default'}`;\n }\n\n /**\n * Keydown dispatched on the row itself (a consumer's test, a script). Keys\n * from the row's button or a nested link are theirs: the button's native\n * activation clicks it, and the click bubbles to the row.\n */\n protected onRowKeydown(event: KeyboardEvent, item: DescriptionListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.itemClick.emit(item);\n }\n }\n}\n","<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the row stays a dt/dd group (F-17): its button is the focusable control, and its click and keys bubble here -->\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\" [attr.id]=\"clickable() ? termId(i) : null\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\" [attr.id]=\"clickable() ? valueId(i) : null\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n @if (clickable()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content -- named by aria-labelledby (the row's term and value); text inside would repeat the value -->\n <button\n type=\"button\"\n class=\"lc-dl__action\"\n [attr.aria-labelledby]=\"termId(i) + ' ' + valueId(i)\"\n ></button>\n }\n </dd>\n </div>\n }\n</dl>\n"],"mappings":";;AA2DA,IAAI,eAAe;AAgCnB,IAAa,2BAAb,MAAa,yBAAwB;CAE1B,QAAQ,MAAM,SAA0C,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQxD,SAAS,MAA6B,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAO7C,UAAU,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,OAAO,MAA2B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAOvC,YAAY,MAAgC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CASnD,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAGV,MAAM,SAAS;CAGtB,OAAO,OAAa;EAC5B,OAAO,GAAG,KAAK,IAAG,IAAK;CACzB;CAGU,QAAQ,OAAa;EAC7B,OAAO,GAAG,KAAK,IAAG,IAAK;CACzB;CAEmB,cAAc,eAC/B;EACE;EACA,UAAU,KAAK,OAAM;EACrB,UAAU,KAAK,KAAI;EACnB,cAAc,KAAK,UAAS;EAC5B,KAAK,QAAO,KAAM,KAAK,OAAM,MAAO,SAAS,mBAAmB;CACjE,CAAA,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,GACZ,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAES,WAAW,MAAyB;EAC5C,OAAO,8BAA8B,KAAK,YAAY;CACxD;CAOU,aAAa,OAAsB,MAAyB;EACpE,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CAjFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAwB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAxB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAwB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UC3FrC;EAkCA,QAAA,CAAA,inFAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;ADyDa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAwB,YAAA,CAAA;EAPpC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAqB,YACnB;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,inFAAA;EAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-description-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.ts","../../../../libs/angular-ui-kit/src/lib/description-list/description-list.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n computed,\n} from '@angular/core';\n\n/**\n * Emphasis treatment applied to a row's value.\n * - `mono` renders the value in the monospace font (paths, URLs, hashes).\n */\nexport type DescriptionListEmphasis =\n | 'default'\n | 'strong'\n | 'muted'\n | 'primary'\n | 'mono';\n\nexport interface DescriptionListItem {\n /** Label shown on the term side of the row. */\n term: string;\n /** Value shown on the description side of the row. */\n value: string;\n /**\n * Optional qualifying suffix appended after the value on the same line and\n * rendered muted (e.g. a rating grade or \"last checked\" hint). Wraps if the\n * combined value + suffix exceed the available width.\n */\n valueSuffix?: string;\n /** Optional link — renders the value as an anchor. */\n href?: string;\n /**\n * Machine-readable date or time of the value (ISO 8601, e.g.\n * `2026-10-12T18:05:00+02:00`): renders the value inside\n * `<time datetime>`, so assistive tech and parsers get the exact moment\n * behind „heute 18:05“. Ignored together with `href`.\n */\n datetime?: string;\n /** Optional visual emphasis for the value. @default 'default' */\n emphasis?: DescriptionListEmphasis;\n /** Optional opaque payload echoed back in `itemClick`. */\n id?: string;\n}\n\nexport type DescriptionListLayout = 'rows' | 'stacked';\nexport type DescriptionListSize = 'sm' | 'md';\n\n/**\n * Per-row separator style (applies to the `rows` layout).\n * - `line` solid hairline under each row (default — unchanged look)\n * - `divider` subtle dashed line, for the dense card look\n * - `none` no row separator\n *\n * Orthogonal to `leaders`, which draws dotted leaders *between* term and value.\n */\nexport type DescriptionListSeparator = 'line' | 'divider' | 'none';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Description list for key/value metadata (a styled `<dl>`).\n *\n * Features:\n * - `rows` layout: term on the left, value on the right, with an optional\n * dotted leader line connecting them (the DS2.0 \"spec sheet\" look).\n * - `rows`: a row that is too narrow is shared — term and value each keep\n * their width up to half of the free width (row − 32 px); the longer one\n * wraps in the rest: the term at spaces, the value at spaces or inside an\n * unbroken string (3.26).\n * - `stacked` layout: term above value, for narrow columns.\n * - Per-row value emphasis and optional links.\n * - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`\n * rows hold a native button (named by term and value) that the keyboard\n * reaches, and the `dl` keeps its term and value pairs.\n *\n * @example\n * ```html\n * <lc-description-list\n * [items]=\"[\n * { term: 'Repository', value: 'example/project', href: '#', emphasis: 'primary' },\n * { term: 'Access', value: 'Token stored · read only' },\n * { term: 'Status', value: 'Maintained', emphasis: 'strong' }\n * ]\"\n * [leaders]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-description-list',\n standalone: true,\n templateUrl: './description-list.component.html',\n styleUrls: ['./description-list.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DescriptionListComponent {\n /** Rows to render. */\n readonly items = input.required<readonly DescriptionListItem[]>();\n\n /**\n * Row layout.\n * - rows: term left / value right (default)\n * - stacked: term above value\n * @default 'rows'\n */\n readonly layout = input<DescriptionListLayout>('rows');\n\n /**\n * Draw a dotted leader line between term and value. Only applies to the\n * `rows` layout.\n * @default false\n */\n readonly leaders = input<boolean>(false);\n\n /** Density of the rows. @default 'md' */\n readonly size = input<DescriptionListSize>('md');\n\n /**\n * Per-row separator style for the `rows` layout. Defaults to `line`, which is\n * identical to the previous behavior. Orthogonal to `leaders`.\n * @default 'line'\n */\n readonly separator = input<DescriptionListSeparator>('line');\n\n /**\n * Whether rows are interactive. When set, each row holds a native button,\n * named by its term and value, that Tab reaches; Enter, Space and a click\n * anywhere on the row emit `itemClick`. The `dl` keeps its term and value\n * pairs. Set this whenever you handle `itemClick`.\n * @default false\n */\n readonly clickable = input<boolean>(false);\n\n /** Emitted when a row is clicked (or activated via keyboard when `clickable`). */\n readonly itemClick = output<DescriptionListItem>();\n\n /** Generated id prefix, taken once per instance, so later ids don't shift. */\n private readonly uid = `lc-dl-${nextUniqueId++}`;\n\n /** Id of row `index`'s term (only rendered when `clickable`). */\n protected termId(index: number): string {\n return `${this.uid}-t${index}`;\n }\n\n /** Id of row `index`'s value (only rendered when `clickable`). */\n protected valueId(index: number): string {\n return `${this.uid}-v${index}`;\n }\n\n protected readonly hostClasses = computed(() =>\n [\n 'lc-dl',\n `lc-dl--${this.layout()}`,\n `lc-dl--${this.size()}`,\n `lc-dl--sep-${this.separator()}`,\n this.leaders() && this.layout() === 'rows' ? 'lc-dl--leaders' : '',\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n protected valueClass(item: DescriptionListItem): string {\n return `lc-dl__value lc-dl__value--${item.emphasis ?? 'default'}`;\n }\n\n /**\n * Keydown dispatched on the row itself (a consumer's test, a script). Keys\n * from the row's button or a nested link are theirs: the button's native\n * activation clicks it, and the click bubbles to the row.\n */\n protected onRowKeydown(event: KeyboardEvent, item: DescriptionListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.itemClick.emit(item);\n }\n }\n}\n","<dl [class]=\"hostClasses()\">\n @for (item of items(); track item.term; let i = $index) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -- the row stays a dt/dd group (F-17): its button is the focusable control, and its click and keys bubble here -->\n <div\n class=\"lc-dl__row\"\n [class.lc-dl__row--clickable]=\"clickable()\"\n (click)=\"itemClick.emit(item)\"\n (keydown)=\"onRowKeydown($event, item)\"\n >\n <dt class=\"lc-dl__term\" [attr.id]=\"clickable() ? termId(i) : null\">{{ item.term }}</dt>\n <span class=\"lc-dl__leader\" aria-hidden=\"true\"></span>\n <dd [class]=\"valueClass(item)\" [attr.id]=\"clickable() ? valueId(i) : null\">\n @if (item.href) {\n <a class=\"lc-dl__link\" [href]=\"item.href\">{{ item.value }}</a>\n } @else if (item.datetime) {\n <time [attr.datetime]=\"item.datetime\">{{ item.value }}</time>\n } @else {\n {{ item.value }}\n }\n @if (item.valueSuffix) {\n <span class=\"lc-dl__value-suffix\">{{ item.valueSuffix }}</span>\n }\n @if (clickable()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content -- named by aria-labelledby (the row's term and value); text inside would repeat the value -->\n <button\n type=\"button\"\n class=\"lc-dl__action\"\n [attr.aria-labelledby]=\"termId(i) + ' ' + valueId(i)\"\n ></button>\n }\n </dd>\n </div>\n }\n</dl>\n"],"mappings":";;AA2DA,IAAI,eAAe;AAoCnB,IAAa,2BAAb,MAAa,yBAAwB;CAE1B,QAAQ,MAAM,SAA0C,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQxD,SAAS,MAA6B,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAO7C,UAAU,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,OAAO,MAA2B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAOvC,YAAY,MAAgC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CASnD,YAAY,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,YAAY,OAAM;CAGV,MAAM,SAAS;CAGtB,OAAO,OAAa;EAC5B,OAAO,GAAG,KAAK,IAAG,IAAK;CACzB;CAGU,QAAQ,OAAa;EAC7B,OAAO,GAAG,KAAK,IAAG,IAAK;CACzB;CAEmB,cAAc,eAC/B;EACE;EACA,UAAU,KAAK,OAAM;EACrB,UAAU,KAAK,KAAI;EACnB,cAAc,KAAK,UAAS;EAC5B,KAAK,QAAO,KAAM,KAAK,OAAM,MAAO,SAAS,mBAAmB;CACjE,CAAA,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,GACZ,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAES,WAAW,MAAyB;EAC5C,OAAO,8BAA8B,KAAK,YAAY;CACxD;CAOU,aAAa,OAAsB,MAAyB;EACpE,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,UAAU,KAAK,IAAI;EAC1B;CACF;CAjFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAwB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAxB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAwB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,WAAA,YAAA;EAAA,UAAA;EAAA,UC/FrC;EAkCA,QAAA,CAAA,sjGAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AD6Da,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAwB,YAAA,CAAA;EAPpC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAqB,YACnB;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,sjGAAA;EAAA,CAAA"}
|
|
@@ -450,7 +450,7 @@ var DocumentViewerComponent = class DocumentViewerComponent {
|
|
|
450
450
|
} },
|
|
451
451
|
ngImport: i0,
|
|
452
452
|
template: "<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" [decorative]=\"true\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" [decorative]=\"true\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" [decorative]=\"true\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n",
|
|
453
|
-
styles: [".doc-viewer{display:flex;flex-direction:column;border:1px solid var(--color-border);border-radius:.5rem;overflow:hidden;background:var(--color-surface);font-family:var(--typography-font-family, sans-serif)}.doc-viewer__toolbar{display:flex;justify-content:space-between;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);flex-shrink:0}.doc-viewer__toolbar-left{display:flex;align-items:center;gap:.5rem;min-width:0}.doc-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.doc-viewer__filename{font-size:var(--typography-font-size-sm, .875rem);font-weight:500;color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.doc-viewer__badge{display:inline-flex;padding:.125rem .5rem;background:var(--color-surface-hover);color:var(--color-text-secondary);border-radius:999px;font-size:var(--typography-font-size-xs, .75rem);font-weight:500;white-space:nowrap;-webkit-user-select:none;user-select:none}.doc-viewer__btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:.375rem;background:transparent;color:var(--color-text-tertiary);cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__btn:hover{background:var(--color-surface-hover);color:var(--color-text-primary)}.doc-viewer__zoom{display:flex;align-items:center;gap:.125rem;margin-right:.5rem;padding-right:.5rem;border-right:1px solid var(--color-border)}.doc-viewer__zoom-value{min-width:3rem;text-align:center;font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.doc-viewer__content{flex:1;overflow:auto;position:relative;min-height:200px}.doc-viewer__loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem)}.doc-viewer__error{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem);padding:2rem;text-align:center}.doc-viewer__iframe{width:100%;height:100%;border:none;flex:1}.doc-viewer__image-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%;overflow:auto;padding:1rem}.doc-viewer__image{max-width:100%;max-height:100%;object-fit:contain;transition:transform .2s ease;transform-origin:center center}.doc-viewer__markdown{padding:1.5rem 2rem;max-width:65rem;overflow-wrap:break-word}.doc-viewer__text{margin:0;padding:1.5rem 2rem;white-space:pre-wrap;word-break:break-word;font-family:var(--typography-font-family-mono, \"SF Mono\", monospace);font-size:var(--typography-font-size-sm, .875rem);line-height:1.6;color:var(--color-text-primary)}.doc-viewer__unsupported{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;padding:2rem;color:var(--color-text-tertiary)}.doc-viewer__unsupported-title{font-size:var(--typography-font-size-lg, 1.125rem);font-weight:600;color:var(--color-text-secondary)}.doc-viewer__unsupported-msg{font-size:var(--typography-font-size-sm, .875rem);color:var(--color-text-tertiary);text-align:center}.doc-viewer__download-btn{display:inline-flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.5rem 1rem;border:1px solid var(--color-primary-500);border-radius:.375rem;background:transparent;color:var(--color-primary-500);font-size:var(--typography-font-size-sm, .875rem);font-weight:500;cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__download-btn:hover{background:var(--color-primary-500);color:#fff}.doc-viewer:fullscreen{height:100vh!important;border-radius:0}\n"],
|
|
453
|
+
styles: [".doc-viewer{display:flex;flex-direction:column;border:1px solid var(--color-border);border-radius:.5rem;overflow:hidden;background:var(--color-surface);font-family:var(--typography-font-family, sans-serif)}.doc-viewer__toolbar{display:flex;justify-content:space-between;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);flex-shrink:0}.doc-viewer__toolbar-left{display:flex;align-items:center;gap:.5rem;min-width:0}.doc-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.doc-viewer__filename{font-size:var(--typography-font-size-sm, .875rem);font-weight:500;color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.doc-viewer__badge{display:inline-flex;padding:.125rem .5rem;background:var(--color-surface-hover);color:var(--color-text-secondary);border-radius:999px;font-size:var(--typography-font-size-xs, .75rem);font-weight:500;white-space:nowrap;-webkit-user-select:none;user-select:none}.doc-viewer__btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:.375rem;background:transparent;color:var(--color-text-tertiary);cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__btn:hover{background:var(--color-surface-hover);color:var(--color-text-primary)}.doc-viewer__btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.doc-viewer__zoom{display:flex;align-items:center;gap:.125rem;margin-right:.5rem;padding-right:.5rem;border-right:1px solid var(--color-border)}.doc-viewer__zoom-value{min-width:3rem;text-align:center;font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.doc-viewer__content{flex:1;overflow:auto;position:relative;min-height:200px}.doc-viewer__loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem)}.doc-viewer__error{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem);padding:2rem;text-align:center}.doc-viewer__iframe{width:100%;height:100%;border:none;flex:1}.doc-viewer__image-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%;overflow:auto;padding:1rem}.doc-viewer__image{max-width:100%;max-height:100%;object-fit:contain;transition:transform .2s ease;transform-origin:center center}.doc-viewer__markdown{padding:1.5rem 2rem;max-width:65rem;overflow-wrap:break-word}.doc-viewer__text{margin:0;padding:1.5rem 2rem;white-space:pre-wrap;word-break:break-word;font-family:var(--typography-font-family-mono, \"SF Mono\", monospace);font-size:var(--typography-font-size-sm, .875rem);line-height:1.6;color:var(--color-text-primary)}.doc-viewer__unsupported{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;padding:2rem;color:var(--color-text-tertiary)}.doc-viewer__unsupported-title{font-size:var(--typography-font-size-lg, 1.125rem);font-weight:600;color:var(--color-text-secondary)}.doc-viewer__unsupported-msg{font-size:var(--typography-font-size-sm, .875rem);color:var(--color-text-tertiary);text-align:center}.doc-viewer__download-btn{display:inline-flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.5rem 1rem;border:1px solid var(--color-primary-500);border-radius:.375rem;background:transparent;color:var(--color-primary-500);font-size:var(--typography-font-size-sm, .875rem);font-weight:500;cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__download-btn:hover{background:var(--color-primary-500);color:#fff}.doc-viewer__download-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.doc-viewer:fullscreen{height:100vh!important;border-radius:0}@media(forced-colors:active){.doc-viewer__btn:focus-visible,.doc-viewer__download-btn:focus-visible{outline-color:Highlight}}\n"],
|
|
454
454
|
dependencies: [
|
|
455
455
|
{
|
|
456
456
|
kind: "component",
|
|
@@ -565,7 +565,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
565
565
|
"[style.height]": "height()"
|
|
566
566
|
},
|
|
567
567
|
template: "<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" [decorative]=\"true\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" [decorative]=\"true\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" [decorative]=\"true\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n",
|
|
568
|
-
styles: [".doc-viewer{display:flex;flex-direction:column;border:1px solid var(--color-border);border-radius:.5rem;overflow:hidden;background:var(--color-surface);font-family:var(--typography-font-family, sans-serif)}.doc-viewer__toolbar{display:flex;justify-content:space-between;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);flex-shrink:0}.doc-viewer__toolbar-left{display:flex;align-items:center;gap:.5rem;min-width:0}.doc-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.doc-viewer__filename{font-size:var(--typography-font-size-sm, .875rem);font-weight:500;color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.doc-viewer__badge{display:inline-flex;padding:.125rem .5rem;background:var(--color-surface-hover);color:var(--color-text-secondary);border-radius:999px;font-size:var(--typography-font-size-xs, .75rem);font-weight:500;white-space:nowrap;-webkit-user-select:none;user-select:none}.doc-viewer__btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:.375rem;background:transparent;color:var(--color-text-tertiary);cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__btn:hover{background:var(--color-surface-hover);color:var(--color-text-primary)}.doc-viewer__zoom{display:flex;align-items:center;gap:.125rem;margin-right:.5rem;padding-right:.5rem;border-right:1px solid var(--color-border)}.doc-viewer__zoom-value{min-width:3rem;text-align:center;font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.doc-viewer__content{flex:1;overflow:auto;position:relative;min-height:200px}.doc-viewer__loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem)}.doc-viewer__error{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem);padding:2rem;text-align:center}.doc-viewer__iframe{width:100%;height:100%;border:none;flex:1}.doc-viewer__image-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%;overflow:auto;padding:1rem}.doc-viewer__image{max-width:100%;max-height:100%;object-fit:contain;transition:transform .2s ease;transform-origin:center center}.doc-viewer__markdown{padding:1.5rem 2rem;max-width:65rem;overflow-wrap:break-word}.doc-viewer__text{margin:0;padding:1.5rem 2rem;white-space:pre-wrap;word-break:break-word;font-family:var(--typography-font-family-mono, \"SF Mono\", monospace);font-size:var(--typography-font-size-sm, .875rem);line-height:1.6;color:var(--color-text-primary)}.doc-viewer__unsupported{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;padding:2rem;color:var(--color-text-tertiary)}.doc-viewer__unsupported-title{font-size:var(--typography-font-size-lg, 1.125rem);font-weight:600;color:var(--color-text-secondary)}.doc-viewer__unsupported-msg{font-size:var(--typography-font-size-sm, .875rem);color:var(--color-text-tertiary);text-align:center}.doc-viewer__download-btn{display:inline-flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.5rem 1rem;border:1px solid var(--color-primary-500);border-radius:.375rem;background:transparent;color:var(--color-primary-500);font-size:var(--typography-font-size-sm, .875rem);font-weight:500;cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__download-btn:hover{background:var(--color-primary-500);color:#fff}.doc-viewer:fullscreen{height:100vh!important;border-radius:0}\n"]
|
|
568
|
+
styles: [".doc-viewer{display:flex;flex-direction:column;border:1px solid var(--color-border);border-radius:.5rem;overflow:hidden;background:var(--color-surface);font-family:var(--typography-font-family, sans-serif)}.doc-viewer__toolbar{display:flex;justify-content:space-between;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);flex-shrink:0}.doc-viewer__toolbar-left{display:flex;align-items:center;gap:.5rem;min-width:0}.doc-viewer__toolbar-right{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.doc-viewer__filename{font-size:var(--typography-font-size-sm, .875rem);font-weight:500;color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.doc-viewer__badge{display:inline-flex;padding:.125rem .5rem;background:var(--color-surface-hover);color:var(--color-text-secondary);border-radius:999px;font-size:var(--typography-font-size-xs, .75rem);font-weight:500;white-space:nowrap;-webkit-user-select:none;user-select:none}.doc-viewer__btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:.375rem;background:transparent;color:var(--color-text-tertiary);cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__btn:hover{background:var(--color-surface-hover);color:var(--color-text-primary)}.doc-viewer__btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.doc-viewer__zoom{display:flex;align-items:center;gap:.125rem;margin-right:.5rem;padding-right:.5rem;border-right:1px solid var(--color-border)}.doc-viewer__zoom-value{min-width:3rem;text-align:center;font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.doc-viewer__content{flex:1;overflow:auto;position:relative;min-height:200px}.doc-viewer__loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem)}.doc-viewer__error{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;color:var(--color-text-tertiary);font-size:var(--typography-font-size-sm, .875rem);padding:2rem;text-align:center}.doc-viewer__iframe{width:100%;height:100%;border:none;flex:1}.doc-viewer__image-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%;overflow:auto;padding:1rem}.doc-viewer__image{max-width:100%;max-height:100%;object-fit:contain;transition:transform .2s ease;transform-origin:center center}.doc-viewer__markdown{padding:1.5rem 2rem;max-width:65rem;overflow-wrap:break-word}.doc-viewer__text{margin:0;padding:1.5rem 2rem;white-space:pre-wrap;word-break:break-word;font-family:var(--typography-font-family-mono, \"SF Mono\", monospace);font-size:var(--typography-font-size-sm, .875rem);line-height:1.6;color:var(--color-text-primary)}.doc-viewer__unsupported{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;min-height:200px;padding:2rem;color:var(--color-text-tertiary)}.doc-viewer__unsupported-title{font-size:var(--typography-font-size-lg, 1.125rem);font-weight:600;color:var(--color-text-secondary)}.doc-viewer__unsupported-msg{font-size:var(--typography-font-size-sm, .875rem);color:var(--color-text-tertiary);text-align:center}.doc-viewer__download-btn{display:inline-flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.5rem 1rem;border:1px solid var(--color-primary-500);border-radius:.375rem;background:transparent;color:var(--color-primary-500);font-size:var(--typography-font-size-sm, .875rem);font-weight:500;cursor:pointer;transition:background-color .15s ease,color .15s ease}.doc-viewer__download-btn:hover{background:var(--color-primary-500);color:#fff}.doc-viewer__download-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.doc-viewer:fullscreen{height:100vh!important;border-radius:0}@media(forced-colors:active){.doc-viewer__btn:focus-visible,.doc-viewer__download-btn:focus-visible{outline-color:Highlight}}\n"]
|
|
569
569
|
}]
|
|
570
570
|
}],
|
|
571
571
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-document-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT, ICON_PLUS } from '@lifecorner/angular-ui-kit/icons';\nimport { SpinnerComponent } from '@lifecorner/angular-ui-kit/spinner';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { MarkdownComponent } from '@lifecorner/angular-ui-kit/markdown';\nimport { isSafeResourceUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport type DocumentType = 'pdf' | 'markdown' | 'image' | 'text' | 'code' | 'auto';\n\nconst EXTENSION_TYPE_MAP: Record<string, DocumentType> = {\n pdf: 'pdf',\n md: 'markdown',\n markdown: 'markdown',\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n svg: 'image',\n bmp: 'image',\n ts: 'code',\n js: 'code',\n tsx: 'code',\n jsx: 'code',\n py: 'code',\n java: 'code',\n html: 'code',\n css: 'code',\n scss: 'code',\n json: 'code',\n sh: 'code',\n bash: 'code',\n txt: 'text',\n log: 'text',\n csv: 'text',\n};\n\nconst EXTENSION_LANGUAGE_MAP: Record<string, CodeBlockLanguage> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n java: 'java',\n html: 'html',\n css: 'css',\n scss: 'scss',\n json: 'json',\n sh: 'bash',\n bash: 'bash',\n};\n\nconst TYPE_ICONS: Record<string, string> = {\n pdf: 'document-text',\n markdown: 'document-text',\n image: 'photo',\n text: 'document',\n code: 'code-bracket',\n unknown: 'document',\n};\n\n/**\n * Document viewer component for previewing various file types.\n *\n * Features:\n * - Auto-detects file type from URL extension or explicit type input\n * - PDF rendering via browser-native iframe viewer\n * - Markdown rendered through `lc-markdown` (escaped and sanitised — a fetched\n * `.md` file cannot inject markup)\n * - Image display with zoom controls (25% – 500%)\n * - Code display using the built-in code block component\n * - Plain text display\n * - Toolbar with filename, type badge, zoom, download, and fullscreen\n * - Loading and error states\n * - Dark/light theme support\n *\n * `src` is only ever embedded (iframe) or downloaded when it is a http(s),\n * blob: or relative URL — or a `data:application/pdf` — so a `javascript:` URL\n * handed in as a document can neither run in the frame nor be \"downloaded\".\n *\n * @example\n * ```html\n * <lc-document-viewer src=\"https://example.com/report.pdf\" />\n * <lc-document-viewer [content]=\"markdownString\" type=\"markdown\" filename=\"README.md\" />\n * <lc-document-viewer src=\"/assets/diagram.png\" />\n * ```\n */\n@Component({\n selector: 'lc-document-viewer',\n standalone: true,\n imports: [IconComponent, SpinnerComponent, CodeBlockComponent, MarkdownComponent],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS, ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT])],\n templateUrl: './document-viewer.component.html',\n styleUrls: ['./document-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': '\"doc-viewer\"',\n '[style.height]': 'height()',\n },\n})\nexport class DocumentViewerComponent implements OnDestroy {\n /** URL of the document to display (for PDF, images, or remote files) */\n readonly src = input<string>('');\n\n /** Direct content string (for markdown, text, or code — takes precedence over src for content-based types) */\n readonly content = input<string>('');\n\n /** File type — set to 'auto' to detect from URL extension */\n readonly type = input<DocumentType>('auto');\n\n /** Display name shown in the toolbar */\n readonly filename = input<string>('');\n\n /** Code language for syntax highlighting (when type is 'code') */\n readonly language = input<CodeBlockLanguage>('text');\n\n /** Whether to display the toolbar */\n readonly showToolbar = input(true);\n\n /** Whether to show the download button in the toolbar */\n readonly showDownload = input(true);\n\n /** Height of the viewer container */\n readonly height = input('500px');\n\n /** Accessible name of the zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the toolbar download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the fullscreen button. */\n readonly fullscreenAriaLabel = input('Fullscreen');\n\n /** Accessible name of the fullscreen button while in fullscreen. */\n readonly exitFullscreenAriaLabel = input('Exit fullscreen');\n\n /** Shown while the document loads. */\n readonly loadingLabel = input('Loading document…');\n\n /** Title shown for file types without a preview. */\n readonly previewUnavailableTitle = input('Preview not available');\n\n /** Message shown for file types without a preview. */\n readonly previewUnavailableMessage = input('This file type cannot be previewed.');\n\n /** Label of the download button shown instead of a preview. */\n readonly downloadFileLabel = input('Download file');\n\n /** Toolbar name when neither `filename` nor a file name in `src` is available. */\n readonly untitledLabel = input('Document');\n\n /** Type badge for Markdown documents. */\n readonly markdownTypeLabel = input('Markdown');\n\n /** Type badge for images. */\n readonly imageTypeLabel = input('Image');\n\n /** Type badge for plain text. */\n readonly textTypeLabel = input('Text');\n\n /** Type badge for other files. */\n readonly fileTypeLabel = input('File');\n\n /** Prefix of the error shown when fetching the document fails. */\n readonly loadErrorLabel = input('Failed to load document:');\n\n /** Error shown when `src` is not an allowed URL (e.g. `javascript:`). */\n readonly blockedUrlMessage = input('This document URL cannot be displayed');\n\n // ── Internal state ───────────────────────────────────────────────────────\n\n protected loading = signal(true);\n protected error = signal<string | null>(null);\n protected fetchedContent = signal<string>('');\n protected zoom = signal(100);\n protected isFullscreen = signal(false);\n\n private readonly sanitizer = inject(DomSanitizer);\n private readonly elementRef = inject(ElementRef);\n private fullscreenHandler: (() => void) | null = null;\n\n // ── Computed values ──────────────────────────────────────────────────────\n\n /** Resolved document type from input or URL extension */\n protected resolvedType = computed<DocumentType | 'unknown'>(() => {\n const t = this.type();\n if (t !== 'auto') return t;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_TYPE_MAP[ext] ? EXTENSION_TYPE_MAP[ext] : 'unknown';\n });\n\n /** Display name for the toolbar */\n protected displayName = computed(() => {\n if (this.filename()) return this.filename();\n const src = this.src();\n if (!src) return this.untitledLabel();\n try {\n const url = new URL(src, 'http://localhost');\n const segments = url.pathname.split('/');\n return segments[segments.length - 1] || this.untitledLabel();\n } catch {\n return src.split('/').pop() || this.untitledLabel();\n }\n });\n\n /** Icon name for the document type */\n protected typeIcon = computed(() => TYPE_ICONS[this.resolvedType()] || TYPE_ICONS['unknown']);\n\n /** Type label for the toolbar badge */\n protected typeLabel = computed(() => {\n const t = this.resolvedType();\n switch (t) {\n case 'pdf': return 'PDF';\n case 'markdown': return this.markdownTypeLabel();\n case 'image': return this.imageTypeLabel();\n case 'text': return this.textTypeLabel();\n case 'code': return this.resolvedLanguage().toUpperCase();\n default: return this.fileTypeLabel();\n }\n });\n\n /** Resolved code language */\n protected resolvedLanguage = computed<CodeBlockLanguage>(() => {\n const lang = this.language();\n if (lang !== 'text') return lang;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_LANGUAGE_MAP[ext] ? EXTENSION_LANGUAGE_MAP[ext] : 'text';\n });\n\n /** Whether `src` may be embedded in a frame or downloaded at all. */\n protected srcEmbeddable = computed(() => isSafeResourceUrl(this.src()));\n\n /**\n * `src` as the iframe accepts it. Bypassing the resource-URL check is what\n * lets a PDF URL into an iframe at all; it is only done for URLs that passed\n * `isSafeResourceUrl` — anything else resolves to an empty frame.\n */\n protected safeSrc = computed<SafeResourceUrl>(() => {\n const src = this.src();\n return this.sanitizer.bypassSecurityTrustResourceUrl(this.srcEmbeddable() ? src : 'about:blank');\n });\n\n /** Effective content: direct content input or fetched content */\n protected effectiveContent = computed(() => {\n return this.content() || this.fetchedContent();\n });\n\n /** Image transform style for zoom */\n protected imageTransform = computed(() => {\n const z = this.zoom() / 100;\n return `scale(${z})`;\n });\n\n /** Whether zoom controls should be shown */\n protected showZoom = computed(() => {\n const t = this.resolvedType();\n return t === 'image';\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n constructor() {\n // Load whenever the document changes — not just once. A new `src` starts a\n // fresh load (and cancels the one still in flight, so a slow old response\n // can't overwrite the new document), resets any earlier error, and re-enters\n // the loading state; a `content` string short-circuits all of that.\n effect((onCleanup) => {\n const type = this.resolvedType();\n const src = this.src();\n const content = this.content();\n const controller = typeof AbortController === 'function' ? new AbortController() : null;\n onCleanup(() => controller?.abort());\n untracked(() => this.load(type, src, content, controller));\n });\n }\n\n private load(\n type: DocumentType | 'unknown',\n src: string,\n content: string,\n controller: AbortController | null,\n ): void {\n this.error.set(null);\n this.fetchedContent.set('');\n\n if (content) {\n this.loading.set(false);\n return;\n }\n\n if (!src) {\n this.loading.set(false);\n this.error.set('No source or content provided');\n return;\n }\n\n // PDF and images are embedded directly — no fetch needed. They do have to be\n // embeddable, though: a `javascript:` (or other exotic) URL is refused up\n // front instead of being handed to an iframe.\n if (type === 'pdf' || type === 'image') {\n if (!this.srcEmbeddable()) {\n this.loading.set(false);\n this.error.set(this.blockedUrlMessage());\n return;\n }\n // Not \"loading\": the frame/img element only exists once loading is off,\n // and it is its own load event that would end the state — a deadlock.\n this.loading.set(false);\n return;\n }\n\n // Fetch text-based content\n if (type === 'markdown' || type === 'text' || type === 'code') {\n this.loading.set(true);\n this.fetchContent(src, controller);\n } else {\n this.loading.set(false);\n }\n }\n\n ngOnDestroy(): void {\n if (this.fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n // ── Actions ──────────────────────────────────────────────────────────────\n\n protected zoomIn(): void {\n this.zoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.zoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.zoom.set(100);\n }\n\n protected download(): void {\n const src = this.src();\n // A download is a click the user never sees the target of — only URLs that\n // could be embedded are worth clicking.\n if (!src || !this.srcEmbeddable()) return;\n const a = document.createElement('a');\n a.href = src;\n a.download = this.displayName();\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected toggleFullscreen(): void {\n const el = this.elementRef.nativeElement as HTMLElement;\n if (!document.fullscreenElement) {\n el.requestFullscreen?.();\n this.isFullscreen.set(true);\n } else {\n document.exitFullscreen?.();\n this.isFullscreen.set(false);\n }\n\n if (!this.fullscreenHandler) {\n this.fullscreenHandler = () => {\n this.isFullscreen.set(!!document.fullscreenElement);\n };\n document.addEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n protected onIframeLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageError(): void {\n this.loading.set(false);\n this.error.set('Failed to load image');\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private extractExtension(url: string): string | null {\n if (!url) return null;\n try {\n const pathname = new URL(url, 'http://localhost').pathname;\n const parts = pathname.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase() : null;\n } catch {\n const parts = url.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase().split('?')[0] : null;\n }\n }\n\n private async fetchContent(url: string, controller: AbortController | null): Promise<void> {\n if (typeof fetch !== 'function') {\n this.loading.set(false);\n this.error.set('Failed to load document: fetch is not available');\n return;\n }\n try {\n const response = await fetch(url, controller ? { signal: controller.signal } : undefined);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n const text = await response.text();\n // A response for a document that has since been replaced is dropped: the\n // effect that replaced it aborted this controller and started its own load.\n if (controller?.signal.aborted) return;\n this.fetchedContent.set(text);\n this.loading.set(false);\n } catch (err) {\n if (controller?.signal.aborted) return;\n this.loading.set(false);\n this.error.set(`${this.loadErrorLabel()} ${err instanceof Error ? err.message : 'Unknown error'}`);\n }\n }\n}\n","<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" [decorative]=\"true\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" [decorative]=\"true\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" [decorative]=\"true\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-document-viewer.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.ts","../../../../libs/angular-ui-kit/src/lib/document-viewer/document-viewer.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT, ICON_PLUS } from '@lifecorner/angular-ui-kit/icons';\nimport { SpinnerComponent } from '@lifecorner/angular-ui-kit/spinner';\nimport { CodeBlockComponent, CodeBlockLanguage } from '@lifecorner/angular-ui-kit/code-block';\nimport { MarkdownComponent } from '@lifecorner/angular-ui-kit/markdown';\nimport { isSafeResourceUrl } from '@lifecorner/angular-ui-kit/internal/url';\n\nexport type DocumentType = 'pdf' | 'markdown' | 'image' | 'text' | 'code' | 'auto';\n\nconst EXTENSION_TYPE_MAP: Record<string, DocumentType> = {\n pdf: 'pdf',\n md: 'markdown',\n markdown: 'markdown',\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n svg: 'image',\n bmp: 'image',\n ts: 'code',\n js: 'code',\n tsx: 'code',\n jsx: 'code',\n py: 'code',\n java: 'code',\n html: 'code',\n css: 'code',\n scss: 'code',\n json: 'code',\n sh: 'code',\n bash: 'code',\n txt: 'text',\n log: 'text',\n csv: 'text',\n};\n\nconst EXTENSION_LANGUAGE_MAP: Record<string, CodeBlockLanguage> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n java: 'java',\n html: 'html',\n css: 'css',\n scss: 'scss',\n json: 'json',\n sh: 'bash',\n bash: 'bash',\n};\n\nconst TYPE_ICONS: Record<string, string> = {\n pdf: 'document-text',\n markdown: 'document-text',\n image: 'photo',\n text: 'document',\n code: 'code-bracket',\n unknown: 'document',\n};\n\n/**\n * Document viewer component for previewing various file types.\n *\n * Features:\n * - Auto-detects file type from URL extension or explicit type input\n * - PDF rendering via browser-native iframe viewer\n * - Markdown rendered through `lc-markdown` (escaped and sanitised — a fetched\n * `.md` file cannot inject markup)\n * - Image display with zoom controls (25% – 500%)\n * - Code display using the built-in code block component\n * - Plain text display\n * - Toolbar with filename, type badge, zoom, download, and fullscreen\n * - Loading and error states\n * - Dark/light theme support\n *\n * `src` is only ever embedded (iframe) or downloaded when it is a http(s),\n * blob: or relative URL — or a `data:application/pdf` — so a `javascript:` URL\n * handed in as a document can neither run in the frame nor be \"downloaded\".\n *\n * @example\n * ```html\n * <lc-document-viewer src=\"https://example.com/report.pdf\" />\n * <lc-document-viewer [content]=\"markdownString\" type=\"markdown\" filename=\"README.md\" />\n * <lc-document-viewer src=\"/assets/diagram.png\" />\n * ```\n */\n@Component({\n selector: 'lc-document-viewer',\n standalone: true,\n imports: [IconComponent, SpinnerComponent, CodeBlockComponent, MarkdownComponent],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS, ICON_EXCLAMATION_TRIANGLE, ICON_FILE, ICON_FILE_TEXT])],\n templateUrl: './document-viewer.component.html',\n styleUrls: ['./document-viewer.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': '\"doc-viewer\"',\n '[style.height]': 'height()',\n },\n})\nexport class DocumentViewerComponent implements OnDestroy {\n /** URL of the document to display (for PDF, images, or remote files) */\n readonly src = input<string>('');\n\n /** Direct content string (for markdown, text, or code — takes precedence over src for content-based types) */\n readonly content = input<string>('');\n\n /** File type — set to 'auto' to detect from URL extension */\n readonly type = input<DocumentType>('auto');\n\n /** Display name shown in the toolbar */\n readonly filename = input<string>('');\n\n /** Code language for syntax highlighting (when type is 'code') */\n readonly language = input<CodeBlockLanguage>('text');\n\n /** Whether to display the toolbar */\n readonly showToolbar = input(true);\n\n /** Whether to show the download button in the toolbar */\n readonly showDownload = input(true);\n\n /** Height of the viewer container */\n readonly height = input('500px');\n\n /** Accessible name of the zoom-out button. */\n readonly zoomOutAriaLabel = input('Zoom out');\n\n /** Accessible name of the zoom-in button. */\n readonly zoomInAriaLabel = input('Zoom in');\n\n /** Accessible name of the reset-zoom button. */\n readonly resetZoomAriaLabel = input('Reset zoom');\n\n /** Accessible name of the toolbar download button. */\n readonly downloadAriaLabel = input('Download');\n\n /** Accessible name of the fullscreen button. */\n readonly fullscreenAriaLabel = input('Fullscreen');\n\n /** Accessible name of the fullscreen button while in fullscreen. */\n readonly exitFullscreenAriaLabel = input('Exit fullscreen');\n\n /** Shown while the document loads. */\n readonly loadingLabel = input('Loading document…');\n\n /** Title shown for file types without a preview. */\n readonly previewUnavailableTitle = input('Preview not available');\n\n /** Message shown for file types without a preview. */\n readonly previewUnavailableMessage = input('This file type cannot be previewed.');\n\n /** Label of the download button shown instead of a preview. */\n readonly downloadFileLabel = input('Download file');\n\n /** Toolbar name when neither `filename` nor a file name in `src` is available. */\n readonly untitledLabel = input('Document');\n\n /** Type badge for Markdown documents. */\n readonly markdownTypeLabel = input('Markdown');\n\n /** Type badge for images. */\n readonly imageTypeLabel = input('Image');\n\n /** Type badge for plain text. */\n readonly textTypeLabel = input('Text');\n\n /** Type badge for other files. */\n readonly fileTypeLabel = input('File');\n\n /** Prefix of the error shown when fetching the document fails. */\n readonly loadErrorLabel = input('Failed to load document:');\n\n /** Error shown when `src` is not an allowed URL (e.g. `javascript:`). */\n readonly blockedUrlMessage = input('This document URL cannot be displayed');\n\n // ── Internal state ───────────────────────────────────────────────────────\n\n protected loading = signal(true);\n protected error = signal<string | null>(null);\n protected fetchedContent = signal<string>('');\n protected zoom = signal(100);\n protected isFullscreen = signal(false);\n\n private readonly sanitizer = inject(DomSanitizer);\n private readonly elementRef = inject(ElementRef);\n private fullscreenHandler: (() => void) | null = null;\n\n // ── Computed values ──────────────────────────────────────────────────────\n\n /** Resolved document type from input or URL extension */\n protected resolvedType = computed<DocumentType | 'unknown'>(() => {\n const t = this.type();\n if (t !== 'auto') return t;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_TYPE_MAP[ext] ? EXTENSION_TYPE_MAP[ext] : 'unknown';\n });\n\n /** Display name for the toolbar */\n protected displayName = computed(() => {\n if (this.filename()) return this.filename();\n const src = this.src();\n if (!src) return this.untitledLabel();\n try {\n const url = new URL(src, 'http://localhost');\n const segments = url.pathname.split('/');\n return segments[segments.length - 1] || this.untitledLabel();\n } catch {\n return src.split('/').pop() || this.untitledLabel();\n }\n });\n\n /** Icon name for the document type */\n protected typeIcon = computed(() => TYPE_ICONS[this.resolvedType()] || TYPE_ICONS['unknown']);\n\n /** Type label for the toolbar badge */\n protected typeLabel = computed(() => {\n const t = this.resolvedType();\n switch (t) {\n case 'pdf': return 'PDF';\n case 'markdown': return this.markdownTypeLabel();\n case 'image': return this.imageTypeLabel();\n case 'text': return this.textTypeLabel();\n case 'code': return this.resolvedLanguage().toUpperCase();\n default: return this.fileTypeLabel();\n }\n });\n\n /** Resolved code language */\n protected resolvedLanguage = computed<CodeBlockLanguage>(() => {\n const lang = this.language();\n if (lang !== 'text') return lang;\n const ext = this.extractExtension(this.src());\n return ext && EXTENSION_LANGUAGE_MAP[ext] ? EXTENSION_LANGUAGE_MAP[ext] : 'text';\n });\n\n /** Whether `src` may be embedded in a frame or downloaded at all. */\n protected srcEmbeddable = computed(() => isSafeResourceUrl(this.src()));\n\n /**\n * `src` as the iframe accepts it. Bypassing the resource-URL check is what\n * lets a PDF URL into an iframe at all; it is only done for URLs that passed\n * `isSafeResourceUrl` — anything else resolves to an empty frame.\n */\n protected safeSrc = computed<SafeResourceUrl>(() => {\n const src = this.src();\n return this.sanitizer.bypassSecurityTrustResourceUrl(this.srcEmbeddable() ? src : 'about:blank');\n });\n\n /** Effective content: direct content input or fetched content */\n protected effectiveContent = computed(() => {\n return this.content() || this.fetchedContent();\n });\n\n /** Image transform style for zoom */\n protected imageTransform = computed(() => {\n const z = this.zoom() / 100;\n return `scale(${z})`;\n });\n\n /** Whether zoom controls should be shown */\n protected showZoom = computed(() => {\n const t = this.resolvedType();\n return t === 'image';\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n constructor() {\n // Load whenever the document changes — not just once. A new `src` starts a\n // fresh load (and cancels the one still in flight, so a slow old response\n // can't overwrite the new document), resets any earlier error, and re-enters\n // the loading state; a `content` string short-circuits all of that.\n effect((onCleanup) => {\n const type = this.resolvedType();\n const src = this.src();\n const content = this.content();\n const controller = typeof AbortController === 'function' ? new AbortController() : null;\n onCleanup(() => controller?.abort());\n untracked(() => this.load(type, src, content, controller));\n });\n }\n\n private load(\n type: DocumentType | 'unknown',\n src: string,\n content: string,\n controller: AbortController | null,\n ): void {\n this.error.set(null);\n this.fetchedContent.set('');\n\n if (content) {\n this.loading.set(false);\n return;\n }\n\n if (!src) {\n this.loading.set(false);\n this.error.set('No source or content provided');\n return;\n }\n\n // PDF and images are embedded directly — no fetch needed. They do have to be\n // embeddable, though: a `javascript:` (or other exotic) URL is refused up\n // front instead of being handed to an iframe.\n if (type === 'pdf' || type === 'image') {\n if (!this.srcEmbeddable()) {\n this.loading.set(false);\n this.error.set(this.blockedUrlMessage());\n return;\n }\n // Not \"loading\": the frame/img element only exists once loading is off,\n // and it is its own load event that would end the state — a deadlock.\n this.loading.set(false);\n return;\n }\n\n // Fetch text-based content\n if (type === 'markdown' || type === 'text' || type === 'code') {\n this.loading.set(true);\n this.fetchContent(src, controller);\n } else {\n this.loading.set(false);\n }\n }\n\n ngOnDestroy(): void {\n if (this.fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n // ── Actions ──────────────────────────────────────────────────────────────\n\n protected zoomIn(): void {\n this.zoom.update(z => Math.min(z + 25, 500));\n }\n\n protected zoomOut(): void {\n this.zoom.update(z => Math.max(z - 25, 25));\n }\n\n protected resetZoom(): void {\n this.zoom.set(100);\n }\n\n protected download(): void {\n const src = this.src();\n // A download is a click the user never sees the target of — only URLs that\n // could be embedded are worth clicking.\n if (!src || !this.srcEmbeddable()) return;\n const a = document.createElement('a');\n a.href = src;\n a.download = this.displayName();\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }\n\n protected toggleFullscreen(): void {\n const el = this.elementRef.nativeElement as HTMLElement;\n if (!document.fullscreenElement) {\n el.requestFullscreen?.();\n this.isFullscreen.set(true);\n } else {\n document.exitFullscreen?.();\n this.isFullscreen.set(false);\n }\n\n if (!this.fullscreenHandler) {\n this.fullscreenHandler = () => {\n this.isFullscreen.set(!!document.fullscreenElement);\n };\n document.addEventListener('fullscreenchange', this.fullscreenHandler);\n }\n }\n\n protected onIframeLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageLoad(): void {\n this.loading.set(false);\n }\n\n protected onImageError(): void {\n this.loading.set(false);\n this.error.set('Failed to load image');\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private extractExtension(url: string): string | null {\n if (!url) return null;\n try {\n const pathname = new URL(url, 'http://localhost').pathname;\n const parts = pathname.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase() : null;\n } catch {\n const parts = url.split('.');\n return parts.length > 1 ? parts.pop()!.toLowerCase().split('?')[0] : null;\n }\n }\n\n private async fetchContent(url: string, controller: AbortController | null): Promise<void> {\n if (typeof fetch !== 'function') {\n this.loading.set(false);\n this.error.set('Failed to load document: fetch is not available');\n return;\n }\n try {\n const response = await fetch(url, controller ? { signal: controller.signal } : undefined);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n const text = await response.text();\n // A response for a document that has since been replaced is dropped: the\n // effect that replaced it aborted this controller and started its own load.\n if (controller?.signal.aborted) return;\n this.fetchedContent.set(text);\n this.loading.set(false);\n } catch (err) {\n if (controller?.signal.aborted) return;\n this.loading.set(false);\n this.error.set(`${this.loadErrorLabel()} ${err instanceof Error ? err.message : 'Unknown error'}`);\n }\n }\n}\n","<!-- Toolbar -->\n@if (showToolbar()) {\n <div class=\"doc-viewer__toolbar\">\n <div class=\"doc-viewer__toolbar-left\">\n <lc-icon [name]=\"typeIcon()\" size=\"sm\" [decorative]=\"true\" />\n <span class=\"doc-viewer__filename\">{{ displayName() }}</span>\n <span class=\"doc-viewer__badge\">{{ typeLabel() }}</span>\n </div>\n <div class=\"doc-viewer__toolbar-right\">\n @if (showZoom()) {\n <div class=\"doc-viewer__zoom\">\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomOutAriaLabel()\"\n (click)=\"zoomOut()\"\n >\n <lc-icon name=\"minus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <span class=\"doc-viewer__zoom-value\">{{ zoom() }}%</span>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"zoomInAriaLabel()\"\n (click)=\"zoomIn()\"\n >\n <lc-icon name=\"plus\" size=\"xs\" [decorative]=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"resetZoomAriaLabel()\"\n (click)=\"resetZoom()\"\n >\n <lc-icon name=\"arrows-pointing-out\" size=\"xs\" [decorative]=\"true\" />\n </button>\n </div>\n }\n @if (showDownload() && src()) {\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"downloadAriaLabel()\"\n (click)=\"download()\"\n >\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n <button\n type=\"button\"\n class=\"doc-viewer__btn\"\n [attr.aria-label]=\"isFullscreen() ? exitFullscreenAriaLabel() : fullscreenAriaLabel()\"\n (click)=\"toggleFullscreen()\"\n >\n <lc-icon [name]=\"isFullscreen() ? 'arrows-pointing-in' : 'arrows-pointing-out'\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n </div>\n}\n\n<!-- Content area -->\n<div class=\"doc-viewer__content\">\n @if (loading()) {\n <div class=\"doc-viewer__loading\">\n <lc-spinner size=\"lg\" />\n <span>{{ loadingLabel() }}</span>\n </div>\n }\n\n @if (error(); as errorMsg) {\n <div class=\"doc-viewer__error\">\n <lc-icon name=\"exclamation-triangle\" size=\"lg\" color=\"var(--color-error-default)\" [decorative]=\"true\" />\n <span>{{ errorMsg }}</span>\n </div>\n }\n\n @if (!loading() && !error()) {\n @switch (resolvedType()) {\n @case ('pdf') {\n <iframe\n class=\"doc-viewer__iframe\"\n [src]=\"safeSrc()\"\n [title]=\"displayName()\"\n type=\"application/pdf\"\n (load)=\"onIframeLoad()\"\n ></iframe>\n }\n\n @case ('image') {\n <div class=\"doc-viewer__image-container\">\n <img\n class=\"doc-viewer__image\"\n [src]=\"src()\"\n [alt]=\"displayName()\"\n [style.transform]=\"imageTransform()\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n </div>\n }\n\n @case ('markdown') {\n <!-- lc-markdown escapes the source and runs Angular's sanitizer — the\n one markdown renderer in the kit, so its safety work isn't duplicated\n (or missed) here. -->\n <div class=\"doc-viewer__markdown\">\n <lc-markdown [content]=\"effectiveContent()\" linkTarget=\"_blank\" />\n </div>\n }\n\n @case ('code') {\n <lc-code-block\n [code]=\"effectiveContent()\"\n [language]=\"resolvedLanguage()\"\n [filename]=\"displayName()\"\n [showLineNumbers]=\"true\"\n [showHeader]=\"false\"\n />\n }\n\n @case ('text') {\n <pre class=\"doc-viewer__text\">{{ effectiveContent() }}</pre>\n }\n\n @default {\n <div class=\"doc-viewer__unsupported\">\n <lc-icon name=\"document\" size=\"lg\" [decorative]=\"true\" />\n <span class=\"doc-viewer__unsupported-title\">{{ previewUnavailableTitle() }}</span>\n <span class=\"doc-viewer__unsupported-msg\">{{ previewUnavailableMessage() }}</span>\n @if (src()) {\n <button type=\"button\" class=\"doc-viewer__download-btn\" (click)=\"download()\">\n <lc-icon name=\"arrow-down-tray\" size=\"sm\" [decorative]=\"true\" />\n {{ downloadFileLabel() }}\n </button>\n }\n </div>\n }\n }\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -85,6 +85,10 @@ var FileUploadComponent = class FileUploadComponent {
|
|
|
85
85
|
openFilePicker() {
|
|
86
86
|
if (!this.disabled()) this.fileInputRef()?.nativeElement.click();
|
|
87
87
|
}
|
|
88
|
+
onKeyActivate(event) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
if (!event.repeat) this.openFilePicker();
|
|
91
|
+
}
|
|
88
92
|
removeFile(index) {
|
|
89
93
|
this.selectedFiles.update((files) => files.filter((_, i) => i !== index));
|
|
90
94
|
this.filesSelected.emit(this.selectedFiles());
|
|
@@ -225,8 +229,8 @@ var FileUploadComponent = class FileUploadComponent {
|
|
|
225
229
|
isSignal: true
|
|
226
230
|
}],
|
|
227
231
|
ngImport: i0,
|
|
228
|
-
template: "<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"
|
|
229
|
-
styles: [":host{display:block;width:100%}.file-upload{display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;border:2px dashed var(--color-border-strong);border-radius:var(--border-radius-lg);background-color:var(--color-surface-2);cursor:pointer;transition:border-color .15s ease,background-color .15s ease;text-align:center}.file-upload:hover{border-color:var(--color-primary-500);background-color:var(--color-surface-hover)}.file-upload:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.file-upload--drag-over{border-color:var(--color-primary-500);background-color:var(--color-surface-selected)}.file-upload--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.file-upload__input{position:absolute;width:0;height:0;overflow:hidden;opacity:0}.file-upload__content{display:flex;flex-direction:column;align-items:center;gap:.5rem}.file-upload__icon{--icon-size: 2rem;color:var(--color-primary-500)}.file-upload__hint{margin:0;font-size:.875rem;color:var(--color-text-secondary)}.file-upload__accept{margin:0;font-size:.75rem;color:var(--color-text-tertiary)}.file-upload__list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}.file-upload__file{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--border-radius-md);font-size:.8125rem}.file-upload__file-icon{--icon-size: 1rem;color:var(--color-text-tertiary);flex-shrink:0}.file-upload__file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary)}.file-upload__file-size{flex-shrink:0;color:var(--color-text-tertiary);font-size:.75rem}.file-upload__file-remove{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;--icon-size: .75rem;color:var(--color-text-secondary)}.file-upload__file-remove:hover{background:#9d0e0e2e;color:var(--color-on-error-subtle)}\n"],
|
|
232
|
+
template: "<!-- The native input sits beside the zone, not inside it (3.25.2, axe\n nested-interactive): visibility: hidden keeps it out of the tab order and\n the accessibility tree, and the zone opens it with click(). -->\n<input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n/>\n<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n [attr.tabindex]=\"disabled() ? null : 0\"\n (keydown.enter)=\"onKeyActivate($event)\"\n (keydown.space)=\"onKeyActivate($event)\"\n [attr.aria-label]=\"dropzoneAriaLabel()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\" [decorative]=\"true\"></lc-icon>\n <!-- Real spaces between the lines, so the visible text reads as words\n that the drop zone's name contains (WCAG 2.5.3). -->\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n &ngsp;<p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n &ngsp;<p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel() + ' ' + f.name\"\n >\n <lc-icon name=\"x-mark\" [decorative]=\"true\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n",
|
|
233
|
+
styles: [":host{display:block;width:100%}.file-upload{display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;border:2px dashed var(--color-border-strong);border-radius:var(--border-radius-lg);background-color:var(--color-surface-2);cursor:pointer;transition:border-color .15s ease,background-color .15s ease;text-align:center}.file-upload:hover{border-color:var(--color-primary-500);background-color:var(--color-surface-hover)}.file-upload:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.file-upload--drag-over{border-color:var(--color-primary-500);background-color:var(--color-surface-selected)}.file-upload--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.file-upload__input{position:absolute;width:0;height:0;overflow:hidden;opacity:0;visibility:hidden}.file-upload__content{display:flex;flex-direction:column;align-items:center;gap:.5rem;pointer-events:none}.file-upload__icon{--icon-size: 2rem;color:var(--color-primary-500)}.file-upload__hint{margin:0;font-size:.875rem;color:var(--color-text-secondary)}.file-upload__accept{margin:0;font-size:.75rem;color:var(--color-text-tertiary)}.file-upload__list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}.file-upload__file{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--border-radius-md);font-size:.8125rem}.file-upload__file-icon{--icon-size: 1rem;color:var(--color-text-tertiary);flex-shrink:0}.file-upload__file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary)}.file-upload__file-size{flex-shrink:0;color:var(--color-text-tertiary);font-size:.75rem}.file-upload__file-remove{flex-shrink:0;position:relative;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;--icon-size: .75rem;color:var(--color-text-secondary)}.file-upload__file-remove:before{content:\"\";position:absolute;inset:calc((1.25rem - var(--size-min-touch-height, 2.75rem)) / 2)}.file-upload__file-remove:hover{background:#9d0e0e2e;color:var(--color-on-error-subtle)}.file-upload__file-remove:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.file-upload__file-remove:focus-visible{outline-color:Highlight}}\n"],
|
|
230
234
|
dependencies: [{
|
|
231
235
|
kind: "component",
|
|
232
236
|
type: IconComponent,
|
|
@@ -258,8 +262,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
258
262
|
imports: [IconComponent],
|
|
259
263
|
viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],
|
|
260
264
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
261
|
-
template: "<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"
|
|
262
|
-
styles: [":host{display:block;width:100%}.file-upload{display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;border:2px dashed var(--color-border-strong);border-radius:var(--border-radius-lg);background-color:var(--color-surface-2);cursor:pointer;transition:border-color .15s ease,background-color .15s ease;text-align:center}.file-upload:hover{border-color:var(--color-primary-500);background-color:var(--color-surface-hover)}.file-upload:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.file-upload--drag-over{border-color:var(--color-primary-500);background-color:var(--color-surface-selected)}.file-upload--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.file-upload__input{position:absolute;width:0;height:0;overflow:hidden;opacity:0}.file-upload__content{display:flex;flex-direction:column;align-items:center;gap:.5rem}.file-upload__icon{--icon-size: 2rem;color:var(--color-primary-500)}.file-upload__hint{margin:0;font-size:.875rem;color:var(--color-text-secondary)}.file-upload__accept{margin:0;font-size:.75rem;color:var(--color-text-tertiary)}.file-upload__list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}.file-upload__file{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--border-radius-md);font-size:.8125rem}.file-upload__file-icon{--icon-size: 1rem;color:var(--color-text-tertiary);flex-shrink:0}.file-upload__file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary)}.file-upload__file-size{flex-shrink:0;color:var(--color-text-tertiary);font-size:.75rem}.file-upload__file-remove{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;--icon-size: .75rem;color:var(--color-text-secondary)}.file-upload__file-remove:hover{background:#9d0e0e2e;color:var(--color-on-error-subtle)}\n"]
|
|
265
|
+
template: "<!-- The native input sits beside the zone, not inside it (3.25.2, axe\n nested-interactive): visibility: hidden keeps it out of the tab order and\n the accessibility tree, and the zone opens it with click(). -->\n<input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n/>\n<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n [attr.tabindex]=\"disabled() ? null : 0\"\n (keydown.enter)=\"onKeyActivate($event)\"\n (keydown.space)=\"onKeyActivate($event)\"\n [attr.aria-label]=\"dropzoneAriaLabel()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\" [decorative]=\"true\"></lc-icon>\n <!-- Real spaces between the lines, so the visible text reads as words\n that the drop zone's name contains (WCAG 2.5.3). -->\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n &ngsp;<p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n &ngsp;<p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel() + ' ' + f.name\"\n >\n <lc-icon name=\"x-mark\" [decorative]=\"true\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n",
|
|
266
|
+
styles: [":host{display:block;width:100%}.file-upload{display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;border:2px dashed var(--color-border-strong);border-radius:var(--border-radius-lg);background-color:var(--color-surface-2);cursor:pointer;transition:border-color .15s ease,background-color .15s ease;text-align:center}.file-upload:hover{border-color:var(--color-primary-500);background-color:var(--color-surface-hover)}.file-upload:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.file-upload--drag-over{border-color:var(--color-primary-500);background-color:var(--color-surface-selected)}.file-upload--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.file-upload__input{position:absolute;width:0;height:0;overflow:hidden;opacity:0;visibility:hidden}.file-upload__content{display:flex;flex-direction:column;align-items:center;gap:.5rem;pointer-events:none}.file-upload__icon{--icon-size: 2rem;color:var(--color-primary-500)}.file-upload__hint{margin:0;font-size:.875rem;color:var(--color-text-secondary)}.file-upload__accept{margin:0;font-size:.75rem;color:var(--color-text-tertiary)}.file-upload__list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}.file-upload__file{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--border-radius-md);font-size:.8125rem}.file-upload__file-icon{--icon-size: 1rem;color:var(--color-text-tertiary);flex-shrink:0}.file-upload__file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary)}.file-upload__file-size{flex-shrink:0;color:var(--color-text-tertiary);font-size:.75rem}.file-upload__file-remove{flex-shrink:0;position:relative;display:flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border:none;border-radius:50%;background:var(--color-surface-hover);cursor:pointer;padding:0;--icon-size: .75rem;color:var(--color-text-secondary)}.file-upload__file-remove:before{content:\"\";position:absolute;inset:calc((1.25rem - var(--size-min-touch-height, 2.75rem)) / 2)}.file-upload__file-remove:hover{background:#9d0e0e2e;color:var(--color-on-error-subtle)}.file-upload__file-remove:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.file-upload__file-remove:focus-visible{outline-color:Highlight}}\n"]
|
|
263
267
|
}]
|
|
264
268
|
}],
|
|
265
269
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-file-upload.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.ts","../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n signal,\n computed,\n ElementRef,\n viewChild,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_FILE, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\n\nexport interface FileUploadFile {\n file: File;\n name: string;\n size: number;\n type: string;\n}\n\n/** Why `lc-file-upload` rejected a file. */\nexport type FileUploadRejectionCode = 'size' | 'type';\n\n/** Payload of `lc-file-upload`'s `fileRejected`. */\nexport interface FileUploadRejection {\n file: File;\n /** Fixed English text (\"File type not accepted\", \"File too large\"); translate from `code`. */\n reason: string;\n /** `'type'`: matches no `accept` token (checked first); `'size'`: over `maxSizeBytes`. */\n code: FileUploadRejectionCode;\n}\n\n/** RFC 9110 `token` (lower-cased): one MIME type or subtype, without parameters. */\nconst MIME_PART = \"[!#$%&'*+.^_`|~0-9a-z-]+\";\nconst MIME_TOKEN = new RegExp(`^${MIME_PART}/${MIME_PART}$`);\n\n/**\n * Whether `token` is an `accept` token the check knows: `.ext`, `type/subtype`\n * or `type/*`. A MIME type with parameters (`text/csv;charset=utf-8`) is not\n * one, as for Chromium's picker.\n */\nfunction isAcceptToken(token: string): boolean {\n return (token.length > 1 && token.startsWith('.')) || MIME_TOKEN.test(token);\n}\n\n/** Whether `file` matches one `accept` token (lower-cased). The any-type wildcard matches every file. */\nfunction matchesToken(file: File, token: string): boolean {\n if (token.startsWith('.')) return (file.name || '').toLowerCase().endsWith(token);\n const type = (file.type || '').toLowerCase();\n if (token === '*/*') return true;\n if (token.endsWith('/*')) return type.startsWith(token.slice(0, -1));\n return type === token;\n}\n\n/**\n * File upload / dropzone component with drag & drop support.\n *\n * @example\n * ```html\n * <lc-file-upload\n * accept=\".pdf,.png,.jpg\"\n * [multiple]=\"true\"\n * [maxSizeBytes]=\"5242880\"\n * (filesSelected)=\"onFiles($event)\"\n * ></lc-file-upload>\n * ```\n */\n@Component({\n selector: 'lc-file-upload',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],\n templateUrl: './file-upload.component.html',\n styleUrls: ['./file-upload.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * File upload component with drag-and-drop support.\n *\n * Features:\n * - Drag-and-drop file dropzone with visual feedback\n * - Click-to-browse file selection\n * - File type and size validation, the same for picked and dropped files\n * (3.24): type first (`accept`), then size (`maxSizeBytes`); a file that\n * fails emits one `fileRejected` (`{ file, reason, code }`, `code` `'type'`\n * or `'size'`), before `filesSelected`. The kit shows no rejection message:\n * show your own, translated from `code`.\n * - Multi-file upload support. With `multiple=false` a new pick or drop\n * replaces the selection with its first accepted file; if all its files\n * are rejected, the selection is cleared and `filesSelected` emits `[]`.\n * - File list display with removal\n * - Sizes in the page's notation with `locale` (3.24, `'de-DE'`: \"20,0 MB\")\n * - Disabled state support\n *\n * @example\n * ```html\n * <lc-file-upload accept=\".pdf,.jpg\" [multiple]=\"true\" [maxSizeBytes]=\"5242880\" />\n * ```\n */\nexport class FileUploadComponent {\n /**\n * Accepted types, comma-separated, case-insensitive: extensions (`.csv`),\n * MIME types (`text/csv`), wildcards (`image/*`); `*\\/*` accepts every type.\n * Since 3.24 checked for picked and dropped files alike; a file matching no\n * token is rejected with `code: 'type'`. Tokens that are neither, such as a\n * MIME type with parameters (`text/csv;charset=utf-8`), are ignored, as by\n * the browser's picker; none left = every type. Prefer extensions: systems report MIME\n * types differently (a CSV can arrive as `application/vnd.ms-excel`).\n * @default ''\n */\n accept = input<string>('');\n\n /**\n * Allow multiple files. `false`: one file. A new pick or drop replaces the\n * selection with its first accepted file; if all its files are rejected\n * (size or type), the selection is cleared and `filesSelected` emits `[]`.\n * `true`: accepted files are added to the selection.\n * @default false\n */\n multiple = input<boolean>(false);\n\n /**\n * Maximum file size in bytes. 0 for unlimited.\n * @default 0\n */\n maxSizeBytes = input<number>(0);\n\n /**\n * Whether the dropzone is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Hint text shown in the dropzone. The drop zone's accessible name is\n * `hint`, then the shown accepted and max lines, comma-separated (WCAG 2.5.3\n * Label in Name).\n * @default 'Drag & drop files here, or click to browse'\n */\n hint = input<string>('Drag & drop files here, or click to browse');\n\n /**\n * Prefix of the accepted file types line (`accept`). The shown line is part\n * of the drop zone's accessible name, after `hint`.\n * @default 'Accepted:'\n */\n acceptedLabel = input('Accepted:');\n\n /**\n * Prefix of the maximum file size line (`maxSizeBytes`). The shown line is\n * part of the drop zone's accessible name, after `hint`.\n * @default 'Max:'\n */\n maxSizeLabel = input('Max:');\n\n /** Accessible name of a selected file's remove button. */\n removeFileAriaLabel = input('Remove file');\n\n /**\n * BCP 47 locale of the shown sizes (max line, drop zone name, file list):\n * `'de-DE'` gives \"20,0 MB\". Units stay B/KB/MB (1024 base). Empty keeps\n * the built-in notation (\"20.0 MB\"); an invalid locale falls back to it.\n * @default ''\n */\n locale = input<string>('');\n\n /**\n * Emitted with selected files after validation.\n */\n readonly filesSelected = output<FileUploadFile[]>();\n\n /**\n * Emitted once per rejected file, before `filesSelected`. `code`: `'type'`\n * (matches no `accept` token; checked first) or `'size'` (over\n * `maxSizeBytes`). `reason` is a fixed English text (\"File type not\n * accepted\", \"File too large\"); translate from `code`.\n */\n readonly fileRejected = output<FileUploadRejection>();\n\n protected isDragOver = signal(false);\n protected selectedFiles = signal<FileUploadFile[]>([]);\n protected readonly fileInputRef = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n protected dropzoneClasses = computed(() => {\n return [\n 'file-upload',\n this.isDragOver() ? 'file-upload--drag-over' : '',\n this.disabled() ? 'file-upload--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n /**\n * Name of the drop zone (AS-33, WCAG 2.5.3 Label in Name): `hint`, then the\n * accepted and max lines it shows, comma-separated.\n */\n protected readonly dropzoneAriaLabel = computed(() =>\n [\n this.hint(),\n this.accept() ? `${this.acceptedLabel()} ${this.accept()}` : '',\n this.maxSizeBytes() > 0 ? `${this.maxSizeLabel()} ${this.formatSize(this.maxSizeBytes())}` : '',\n ]\n .filter(Boolean)\n .join(', '),\n );\n\n /**\n * The size notation of `locale` (3.24): whole bytes, KB/MB to one decimal.\n * Null keeps the built-in `toFixed` notation (no or an invalid locale).\n */\n private readonly sizeFormats = computed(() => {\n const locale = this.locale();\n if (!locale) return null;\n try {\n return {\n whole: new Intl.NumberFormat(locale, { minimumFractionDigits: 0, maximumFractionDigits: 0 }),\n tenths: new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 }),\n };\n } catch {\n return null;\n }\n });\n\n /** The `accept` tokens the check knows, lower-cased; empty = every type. */\n private readonly acceptTokens = computed(() =>\n // `|| ''`: an untyped template may bind null, as it could before 3.24.\n (this.accept() || '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter(isAcceptToken),\n );\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this.isDragOver.set(true);\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n if (this.disabled() || !event.dataTransfer?.files.length) return;\n this.processFiles(event.dataTransfer.files);\n }\n\n protected onFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files?.length) {\n this.processFiles(input.files);\n }\n // Reset so the same file can be re-selected\n input.value = '';\n }\n\n protected openFilePicker(): void {\n if (!this.disabled()) {\n this.fileInputRef()?.nativeElement.click();\n }\n }\n\n protected removeFile(index: number): void {\n this.selectedFiles.update((files) => files.filter((_, i) => i !== index));\n this.filesSelected.emit(this.selectedFiles());\n }\n\n protected formatSize(bytes: number): string {\n const formats = this.sizeFormats();\n if (formats) {\n if (bytes < 1024) return `${formats.whole.format(bytes)} B`;\n if (bytes < 1024 * 1024) return `${formats.tenths.format(bytes / 1024)} KB`;\n return `${formats.tenths.format(bytes / (1024 * 1024))} MB`;\n }\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** Why `file` is rejected: type first, then size; null when it is accepted. */\n private rejectionOf(file: File): FileUploadRejection | null {\n const tokens = this.acceptTokens();\n if (tokens.length && !tokens.some((token) => matchesToken(file, token))) {\n return { file, reason: 'File type not accepted', code: 'type' };\n }\n if (this.maxSizeBytes() > 0 && file.size > this.maxSizeBytes()) {\n return { file, reason: 'File too large', code: 'size' };\n }\n return null;\n }\n\n private processFiles(fileList: FileList): void {\n const files = Array.from(fileList);\n const accepted: FileUploadFile[] = [];\n\n for (const file of files) {\n const rejection = this.rejectionOf(file);\n if (rejection) {\n this.fileRejected.emit(rejection);\n continue;\n }\n accepted.push({\n file,\n name: file.name,\n size: file.size,\n type: file.type,\n });\n }\n\n if (this.multiple()) {\n this.selectedFiles.update((prev) => [...prev, ...accepted]);\n } else {\n this.selectedFiles.set(accepted.slice(0, 1));\n }\n\n this.filesSelected.emit(this.selectedFiles());\n }\n}\n","<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openFilePicker()\"\n (keydown.space)=\"openFilePicker()\"\n [attr.aria-label]=\"dropzoneAriaLabel()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\" [decorative]=\"true\"></lc-icon>\n <!-- Real spaces between the lines, so the visible text reads as words\n that the drop zone's name contains (WCAG 2.5.3). -->\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n &ngsp;<p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n &ngsp;<p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel()\"\n >\n <lc-icon name=\"x-mark\" [decorative]=\"true\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-file-upload.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.ts","../../../../libs/angular-ui-kit/src/lib/file-upload/file-upload.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n signal,\n computed,\n ElementRef,\n viewChild,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_FILE, ICON_X_MARK } from '@lifecorner/angular-ui-kit/icons';\n\nexport interface FileUploadFile {\n file: File;\n name: string;\n size: number;\n type: string;\n}\n\n/** Why `lc-file-upload` rejected a file. */\nexport type FileUploadRejectionCode = 'size' | 'type';\n\n/** Payload of `lc-file-upload`'s `fileRejected`. */\nexport interface FileUploadRejection {\n file: File;\n /** Fixed English text (\"File type not accepted\", \"File too large\"); translate from `code`. */\n reason: string;\n /** `'type'`: matches no `accept` token (checked first); `'size'`: over `maxSizeBytes`. */\n code: FileUploadRejectionCode;\n}\n\n/** RFC 9110 `token` (lower-cased): one MIME type or subtype, without parameters. */\nconst MIME_PART = \"[!#$%&'*+.^_`|~0-9a-z-]+\";\nconst MIME_TOKEN = new RegExp(`^${MIME_PART}/${MIME_PART}$`);\n\n/**\n * Whether `token` is an `accept` token the check knows: `.ext`, `type/subtype`\n * or `type/*`. A MIME type with parameters (`text/csv;charset=utf-8`) is not\n * one, as for Chromium's picker.\n */\nfunction isAcceptToken(token: string): boolean {\n return (token.length > 1 && token.startsWith('.')) || MIME_TOKEN.test(token);\n}\n\n/** Whether `file` matches one `accept` token (lower-cased). The any-type wildcard matches every file. */\nfunction matchesToken(file: File, token: string): boolean {\n if (token.startsWith('.')) return (file.name || '').toLowerCase().endsWith(token);\n const type = (file.type || '').toLowerCase();\n if (token === '*/*') return true;\n if (token.endsWith('/*')) return type.startsWith(token.slice(0, -1));\n return type === token;\n}\n\n/**\n * File upload / dropzone component with drag & drop support.\n *\n * @example\n * ```html\n * <lc-file-upload\n * accept=\".pdf,.png,.jpg\"\n * [multiple]=\"true\"\n * [maxSizeBytes]=\"5242880\"\n * (filesSelected)=\"onFiles($event)\"\n * ></lc-file-upload>\n * ```\n */\n@Component({\n selector: 'lc-file-upload',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_FILE, ICON_X_MARK])],\n templateUrl: './file-upload.component.html',\n styleUrls: ['./file-upload.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * File upload component with drag-and-drop support.\n *\n * Features:\n * - Drag-and-drop file dropzone with visual feedback\n * - Click-to-browse file selection\n * - File type and size validation, the same for picked and dropped files\n * (3.24): type first (`accept`), then size (`maxSizeBytes`); a file that\n * fails emits one `fileRejected` (`{ file, reason, code }`, `code` `'type'`\n * or `'size'`), before `filesSelected`. The kit shows no rejection message:\n * show your own, translated from `code`.\n * - Multi-file upload support. With `multiple=false` a new pick or drop\n * replaces the selection with its first accepted file; if all its files\n * are rejected, the selection is cleared and `filesSelected` emits `[]`.\n * - File list display with removal\n * - Sizes in the page's notation with `locale` (3.24, `'de-DE'`: \"20,0 MB\")\n * - Disabled state support\n *\n * @example\n * ```html\n * <lc-file-upload accept=\".pdf,.jpg\" [multiple]=\"true\" [maxSizeBytes]=\"5242880\" />\n * ```\n */\nexport class FileUploadComponent {\n /**\n * Accepted types, comma-separated, case-insensitive: extensions (`.csv`),\n * MIME types (`text/csv`), wildcards (`image/*`); `*\\/*` accepts every type.\n * Since 3.24 checked for picked and dropped files alike; a file matching no\n * token is rejected with `code: 'type'`. Tokens that are neither, such as a\n * MIME type with parameters (`text/csv;charset=utf-8`), are ignored, as by\n * the browser's picker; none left = every type. Prefer extensions: systems report MIME\n * types differently (a CSV can arrive as `application/vnd.ms-excel`).\n * @default ''\n */\n accept = input<string>('');\n\n /**\n * Allow multiple files. `false`: one file. A new pick or drop replaces the\n * selection with its first accepted file; if all its files are rejected\n * (size or type), the selection is cleared and `filesSelected` emits `[]`.\n * `true`: accepted files are added to the selection.\n * @default false\n */\n multiple = input<boolean>(false);\n\n /**\n * Maximum file size in bytes. 0 for unlimited.\n * @default 0\n */\n maxSizeBytes = input<number>(0);\n\n /**\n * Whether the dropzone is disabled.\n * @default false\n */\n disabled = input<boolean>(false);\n\n /**\n * Hint text shown in the dropzone. The drop zone's accessible name is\n * `hint`, then the shown accepted and max lines, comma-separated (WCAG 2.5.3\n * Label in Name).\n * @default 'Drag & drop files here, or click to browse'\n */\n hint = input<string>('Drag & drop files here, or click to browse');\n\n /**\n * Prefix of the accepted file types line (`accept`). The shown line is part\n * of the drop zone's accessible name, after `hint`.\n * @default 'Accepted:'\n */\n acceptedLabel = input('Accepted:');\n\n /**\n * Prefix of the maximum file size line (`maxSizeBytes`). The shown line is\n * part of the drop zone's accessible name, after `hint`.\n * @default 'Max:'\n */\n maxSizeLabel = input('Max:');\n\n /**\n * Accessible name of a selected file's remove button, followed by the file\n * name (\"Remove file bericht.csv\"), so each button in the list has its own\n * name.\n * @default 'Remove file'\n */\n removeFileAriaLabel = input('Remove file');\n\n /**\n * BCP 47 locale of the shown sizes (max line, drop zone name, file list):\n * `'de-DE'` gives \"20,0 MB\". Units stay B/KB/MB (1024 base). Empty keeps\n * the built-in notation (\"20.0 MB\"); an invalid locale falls back to it.\n * @default ''\n */\n locale = input<string>('');\n\n /**\n * Emitted with selected files after validation.\n */\n readonly filesSelected = output<FileUploadFile[]>();\n\n /**\n * Emitted once per rejected file, before `filesSelected`. `code`: `'type'`\n * (matches no `accept` token; checked first) or `'size'` (over\n * `maxSizeBytes`). `reason` is a fixed English text (\"File type not\n * accepted\", \"File too large\"); translate from `code`.\n */\n readonly fileRejected = output<FileUploadRejection>();\n\n protected isDragOver = signal(false);\n protected selectedFiles = signal<FileUploadFile[]>([]);\n protected readonly fileInputRef = viewChild<ElementRef<HTMLInputElement>>('fileInput');\n\n protected dropzoneClasses = computed(() => {\n return [\n 'file-upload',\n this.isDragOver() ? 'file-upload--drag-over' : '',\n this.disabled() ? 'file-upload--disabled' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n /**\n * Name of the drop zone (AS-33, WCAG 2.5.3 Label in Name): `hint`, then the\n * accepted and max lines it shows, comma-separated.\n */\n protected readonly dropzoneAriaLabel = computed(() =>\n [\n this.hint(),\n this.accept() ? `${this.acceptedLabel()} ${this.accept()}` : '',\n this.maxSizeBytes() > 0 ? `${this.maxSizeLabel()} ${this.formatSize(this.maxSizeBytes())}` : '',\n ]\n .filter(Boolean)\n .join(', '),\n );\n\n /**\n * The size notation of `locale` (3.24): whole bytes, KB/MB to one decimal.\n * Null keeps the built-in `toFixed` notation (no or an invalid locale).\n */\n private readonly sizeFormats = computed(() => {\n const locale = this.locale();\n if (!locale) return null;\n try {\n return {\n whole: new Intl.NumberFormat(locale, { minimumFractionDigits: 0, maximumFractionDigits: 0 }),\n tenths: new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 }),\n };\n } catch {\n return null;\n }\n });\n\n /** The `accept` tokens the check knows, lower-cased; empty = every type. */\n private readonly acceptTokens = computed(() =>\n // `|| ''`: an untyped template may bind null, as it could before 3.24.\n (this.accept() || '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter(isAcceptToken),\n );\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.disabled()) {\n this.isDragOver.set(true);\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.isDragOver.set(false);\n if (this.disabled() || !event.dataTransfer?.files.length) return;\n this.processFiles(event.dataTransfer.files);\n }\n\n protected onFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files?.length) {\n this.processFiles(input.files);\n }\n // Reset so the same file can be re-selected\n input.value = '';\n }\n\n protected openFilePicker(): void {\n if (!this.disabled()) {\n this.fileInputRef()?.nativeElement.click();\n }\n }\n\n /**\n * Enter or Space on the zone (3.25.2): no page scroll on Space, one open per\n * press (auto-repeat ignored).\n */\n protected onKeyActivate(event: Event): void {\n event.preventDefault();\n if (!(event as KeyboardEvent).repeat) {\n this.openFilePicker();\n }\n }\n\n protected removeFile(index: number): void {\n this.selectedFiles.update((files) => files.filter((_, i) => i !== index));\n this.filesSelected.emit(this.selectedFiles());\n }\n\n protected formatSize(bytes: number): string {\n const formats = this.sizeFormats();\n if (formats) {\n if (bytes < 1024) return `${formats.whole.format(bytes)} B`;\n if (bytes < 1024 * 1024) return `${formats.tenths.format(bytes / 1024)} KB`;\n return `${formats.tenths.format(bytes / (1024 * 1024))} MB`;\n }\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** Why `file` is rejected: type first, then size; null when it is accepted. */\n private rejectionOf(file: File): FileUploadRejection | null {\n const tokens = this.acceptTokens();\n if (tokens.length && !tokens.some((token) => matchesToken(file, token))) {\n return { file, reason: 'File type not accepted', code: 'type' };\n }\n if (this.maxSizeBytes() > 0 && file.size > this.maxSizeBytes()) {\n return { file, reason: 'File too large', code: 'size' };\n }\n return null;\n }\n\n private processFiles(fileList: FileList): void {\n const files = Array.from(fileList);\n const accepted: FileUploadFile[] = [];\n\n for (const file of files) {\n const rejection = this.rejectionOf(file);\n if (rejection) {\n this.fileRejected.emit(rejection);\n continue;\n }\n accepted.push({\n file,\n name: file.name,\n size: file.size,\n type: file.type,\n });\n }\n\n if (this.multiple()) {\n this.selectedFiles.update((prev) => [...prev, ...accepted]);\n } else {\n this.selectedFiles.set(accepted.slice(0, 1));\n }\n\n this.filesSelected.emit(this.selectedFiles());\n }\n}\n","<!-- The native input sits beside the zone, not inside it (3.25.2, axe\n nested-interactive): visibility: hidden keeps it out of the tab order and\n the accessibility tree, and the zone opens it with click(). -->\n<input\n #fileInput\n type=\"file\"\n class=\"file-upload__input\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [disabled]=\"disabled()\"\n (change)=\"onFileChange($event)\"\n/>\n<div\n [class]=\"dropzoneClasses()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n (click)=\"openFilePicker()\"\n role=\"button\"\n [attr.tabindex]=\"disabled() ? null : 0\"\n (keydown.enter)=\"onKeyActivate($event)\"\n (keydown.space)=\"onKeyActivate($event)\"\n [attr.aria-label]=\"dropzoneAriaLabel()\"\n [attr.aria-disabled]=\"disabled()\"\n>\n <div class=\"file-upload__content\">\n <lc-icon name=\"cloud-arrow-up\" class=\"file-upload__icon\" [decorative]=\"true\"></lc-icon>\n <!-- Real spaces between the lines, so the visible text reads as words\n that the drop zone's name contains (WCAG 2.5.3). -->\n <p class=\"file-upload__hint\">{{ hint() }}</p>\n @if (accept()) {\n &ngsp;<p class=\"file-upload__accept\">{{ acceptedLabel() }} {{ accept() }}</p>\n }\n @if (maxSizeBytes() > 0) {\n &ngsp;<p class=\"file-upload__accept\">{{ maxSizeLabel() }} {{ formatSize(maxSizeBytes()) }}</p>\n }\n </div>\n</div>\n\n@if (selectedFiles().length > 0) {\n <ul class=\"file-upload__list\">\n @for (f of selectedFiles(); track f.name; let i = $index) {\n <li class=\"file-upload__file\">\n <lc-icon name=\"document\" class=\"file-upload__file-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"file-upload__file-name\">{{ f.name }}</span>\n <span class=\"file-upload__file-size\">{{ formatSize(f.size) }}</span>\n <button\n type=\"button\"\n class=\"file-upload__file-remove\"\n (click)=\"removeFile(i); $event.stopPropagation()\"\n [attr.aria-label]=\"removeFileAriaLabel() + ' ' + f.name\"\n >\n <lc-icon name=\"x-mark\" [decorative]=\"true\"></lc-icon>\n </button>\n </li>\n }\n </ul>\n}\n"],"mappings":"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"}
|
|
@@ -108,7 +108,7 @@ var FilterBarComponent = class FilterBarComponent {
|
|
|
108
108
|
outputs: { valuesChange: "valuesChange" },
|
|
109
109
|
ngImport: i0,
|
|
110
110
|
template: "<div class=\"lc-filter-bar\" [class]=\"sizeClass()\">\n @for (filter of filters(); track filter.key) {\n <div class=\"lc-filter-bar__item\">\n @if (filter.label) {\n @if (filter.type === 'search') {\n <label class=\"lc-filter-bar__label\" [id]=\"labelId(filter.key)\" [for]=\"fieldId(filter.key)\">\n {{ filter.label }}\n </label>\n } @else {\n <span class=\"lc-filter-bar__label\" [id]=\"labelId(filter.key)\">{{ filter.label }}</span>\n }\n }\n\n @switch (filter.type) {\n @case ('select') {\n <lc-select\n class=\"lc-filter-bar__select\"\n [size]=\"size() === 'sm' ? 'sm' : 'md'\"\n [placeholder]=\"filter.placeholder ?? selectPlaceholder()\"\n [options]=\"selectOptions(filter.key)\"\n [ariaLabel]=\"filter.label || filter.placeholder || filter.key\"\n [ngModel]=\"getValue(filter.key)\"\n (ngModelChange)=\"onFilterChange(filter.key, normalizeSelectValue($event))\"\n />\n }\n\n @case ('toggle') {\n <div\n class=\"lc-filter-bar__toggle-group\"\n role=\"group\"\n [attr.aria-labelledby]=\"filter.label ? labelId(filter.key) : null\"\n [attr.aria-label]=\"filter.label ? null : filter.key\"\n >\n @for (opt of filter.options; track opt.value) {\n <button\n type=\"button\"\n [class]=\"getToggleClasses(filter.key, opt)\"\n [disabled]=\"opt.disabled ?? false\"\n [attr.aria-pressed]=\"isToggleActive(filter.key, opt.value)\"\n (click)=\"onFilterChange(filter.key, opt.value)\"\n >\n {{ opt.label }}\n </button>\n }\n </div>\n }\n\n @case ('search') {\n <lc-search-input\n class=\"lc-filter-bar__search\"\n [inputId]=\"fieldId(filter.key)\"\n [ariaLabelledBy]=\"filter.label ? labelId(filter.key) : undefined\"\n [ariaLabel]=\"filter.label ? undefined : filter.placeholder || filter.key\"\n [placeholder]=\"filter.placeholder ?? searchPlaceholder()\"\n [size]=\"size() === 'sm' ? 'sm' : 'md'\"\n [debounceMs]=\"0\"\n [value]=\"getValue(filter.key)\"\n (searchChange)=\"onSearchInput(filter.key, $event)\"\n />\n }\n }\n </div>\n }\n</div>\n",
|
|
111
|
-
styles: [".lc-filter-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem}.lc-filter-bar__item{display:flex;flex-direction:column;gap:.25rem}.lc-filter-bar__label{font-size:var(--font-size-xs, .75rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.05em}.lc-filter-bar__select{display:block;min-width:140px}.lc-filter-bar__toggle-group{display:inline-flex;border-radius:.5rem;border:1px solid var(--color-border);background-color:var(--color-surface-hover);overflow:hidden}.lc-filter-bar__toggle-btn{padding:.5rem .75rem;font-size:var(--font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary);transition:color .15s ease,background-color .15s ease;border:none;background:transparent;cursor:pointer}.lc-filter-bar__toggle-btn:hover{color:var(--color-text-primary);background-color:var(--color-surface-hover)}.lc-filter-bar__toggle-btn--active{background-color:var(--color-surface-2);color:var(--color-text-primary);box-shadow:0 1px 2px #0000000d}.lc-filter-bar__toggle-btn--disabled{cursor:not-allowed;opacity:.5}.lc-filter-bar__toggle-btn+.lc-filter-bar__toggle-btn{border-left:1px solid var(--color-border)}.lc-filter-bar__search{display:block;min-width:200px}.lc-filter-bar--sm .lc-filter-bar__select,.lc-filter-bar--sm .lc-filter-bar__toggle-btn{padding-top:.375rem;padding-bottom:.375rem;font-size:var(--font-size-xs, .75rem)}\n"],
|
|
111
|
+
styles: [".lc-filter-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem}.lc-filter-bar__item{display:flex;flex-direction:column;gap:.25rem}.lc-filter-bar__label{font-size:var(--font-size-xs, .75rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.05em}.lc-filter-bar__select{display:block;min-width:140px}.lc-filter-bar__toggle-group{display:inline-flex;border-radius:.5rem;border:1px solid var(--color-border);background-color:var(--color-surface-hover);overflow:hidden}.lc-filter-bar__toggle-btn{padding:.5rem .75rem;font-size:var(--font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary);transition:color .15s ease,background-color .15s ease;border:none;background:transparent;cursor:pointer}.lc-filter-bar__toggle-btn:hover{color:var(--color-text-primary);background-color:var(--color-surface-hover)}.lc-filter-bar__toggle-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-filter-bar__toggle-btn--active{background-color:var(--color-surface-2);color:var(--color-text-primary);box-shadow:0 1px 2px #0000000d}.lc-filter-bar__toggle-btn--disabled{cursor:not-allowed;opacity:.5}.lc-filter-bar__toggle-btn+.lc-filter-bar__toggle-btn{border-left:1px solid var(--color-border)}.lc-filter-bar__search{display:block;min-width:200px}.lc-filter-bar--sm .lc-filter-bar__select,.lc-filter-bar--sm .lc-filter-bar__toggle-btn{padding-top:.375rem;padding-bottom:.375rem;font-size:var(--font-size-xs, .75rem)}@media(forced-colors:active){.lc-filter-bar__toggle-btn:focus-visible{outline-color:Highlight}}\n"],
|
|
112
112
|
dependencies: [
|
|
113
113
|
{
|
|
114
114
|
kind: "ngmodule",
|
|
@@ -203,7 +203,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
203
203
|
],
|
|
204
204
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
205
205
|
template: "<div class=\"lc-filter-bar\" [class]=\"sizeClass()\">\n @for (filter of filters(); track filter.key) {\n <div class=\"lc-filter-bar__item\">\n @if (filter.label) {\n @if (filter.type === 'search') {\n <label class=\"lc-filter-bar__label\" [id]=\"labelId(filter.key)\" [for]=\"fieldId(filter.key)\">\n {{ filter.label }}\n </label>\n } @else {\n <span class=\"lc-filter-bar__label\" [id]=\"labelId(filter.key)\">{{ filter.label }}</span>\n }\n }\n\n @switch (filter.type) {\n @case ('select') {\n <lc-select\n class=\"lc-filter-bar__select\"\n [size]=\"size() === 'sm' ? 'sm' : 'md'\"\n [placeholder]=\"filter.placeholder ?? selectPlaceholder()\"\n [options]=\"selectOptions(filter.key)\"\n [ariaLabel]=\"filter.label || filter.placeholder || filter.key\"\n [ngModel]=\"getValue(filter.key)\"\n (ngModelChange)=\"onFilterChange(filter.key, normalizeSelectValue($event))\"\n />\n }\n\n @case ('toggle') {\n <div\n class=\"lc-filter-bar__toggle-group\"\n role=\"group\"\n [attr.aria-labelledby]=\"filter.label ? labelId(filter.key) : null\"\n [attr.aria-label]=\"filter.label ? null : filter.key\"\n >\n @for (opt of filter.options; track opt.value) {\n <button\n type=\"button\"\n [class]=\"getToggleClasses(filter.key, opt)\"\n [disabled]=\"opt.disabled ?? false\"\n [attr.aria-pressed]=\"isToggleActive(filter.key, opt.value)\"\n (click)=\"onFilterChange(filter.key, opt.value)\"\n >\n {{ opt.label }}\n </button>\n }\n </div>\n }\n\n @case ('search') {\n <lc-search-input\n class=\"lc-filter-bar__search\"\n [inputId]=\"fieldId(filter.key)\"\n [ariaLabelledBy]=\"filter.label ? labelId(filter.key) : undefined\"\n [ariaLabel]=\"filter.label ? undefined : filter.placeholder || filter.key\"\n [placeholder]=\"filter.placeholder ?? searchPlaceholder()\"\n [size]=\"size() === 'sm' ? 'sm' : 'md'\"\n [debounceMs]=\"0\"\n [value]=\"getValue(filter.key)\"\n (searchChange)=\"onSearchInput(filter.key, $event)\"\n />\n }\n }\n </div>\n }\n</div>\n",
|
|
206
|
-
styles: [".lc-filter-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem}.lc-filter-bar__item{display:flex;flex-direction:column;gap:.25rem}.lc-filter-bar__label{font-size:var(--font-size-xs, .75rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.05em}.lc-filter-bar__select{display:block;min-width:140px}.lc-filter-bar__toggle-group{display:inline-flex;border-radius:.5rem;border:1px solid var(--color-border);background-color:var(--color-surface-hover);overflow:hidden}.lc-filter-bar__toggle-btn{padding:.5rem .75rem;font-size:var(--font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary);transition:color .15s ease,background-color .15s ease;border:none;background:transparent;cursor:pointer}.lc-filter-bar__toggle-btn:hover{color:var(--color-text-primary);background-color:var(--color-surface-hover)}.lc-filter-bar__toggle-btn--active{background-color:var(--color-surface-2);color:var(--color-text-primary);box-shadow:0 1px 2px #0000000d}.lc-filter-bar__toggle-btn--disabled{cursor:not-allowed;opacity:.5}.lc-filter-bar__toggle-btn+.lc-filter-bar__toggle-btn{border-left:1px solid var(--color-border)}.lc-filter-bar__search{display:block;min-width:200px}.lc-filter-bar--sm .lc-filter-bar__select,.lc-filter-bar--sm .lc-filter-bar__toggle-btn{padding-top:.375rem;padding-bottom:.375rem;font-size:var(--font-size-xs, .75rem)}\n"]
|
|
206
|
+
styles: [".lc-filter-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem}.lc-filter-bar__item{display:flex;flex-direction:column;gap:.25rem}.lc-filter-bar__label{font-size:var(--font-size-xs, .75rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.05em}.lc-filter-bar__select{display:block;min-width:140px}.lc-filter-bar__toggle-group{display:inline-flex;border-radius:.5rem;border:1px solid var(--color-border);background-color:var(--color-surface-hover);overflow:hidden}.lc-filter-bar__toggle-btn{padding:.5rem .75rem;font-size:var(--font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary);transition:color .15s ease,background-color .15s ease;border:none;background:transparent;cursor:pointer}.lc-filter-bar__toggle-btn:hover{color:var(--color-text-primary);background-color:var(--color-surface-hover)}.lc-filter-bar__toggle-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.lc-filter-bar__toggle-btn--active{background-color:var(--color-surface-2);color:var(--color-text-primary);box-shadow:0 1px 2px #0000000d}.lc-filter-bar__toggle-btn--disabled{cursor:not-allowed;opacity:.5}.lc-filter-bar__toggle-btn+.lc-filter-bar__toggle-btn{border-left:1px solid var(--color-border)}.lc-filter-bar__search{display:block;min-width:200px}.lc-filter-bar--sm .lc-filter-bar__select,.lc-filter-bar--sm .lc-filter-bar__toggle-btn{padding-top:.375rem;padding-bottom:.375rem;font-size:var(--font-size-xs, .75rem)}@media(forced-colors:active){.lc-filter-bar__toggle-btn:focus-visible{outline-color:Highlight}}\n"]
|
|
207
207
|
}]
|
|
208
208
|
}],
|
|
209
209
|
propDecorators: {
|