@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
|
@@ -259,7 +259,7 @@ var NumberInputComponent = class NumberInputComponent {
|
|
|
259
259
|
}],
|
|
260
260
|
ngImport: i0,
|
|
261
261
|
template: "<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"number-input__label\" [for]=\"inputId\">{{ label() }}</label>\n }\n <div class=\"number-input__control\">\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--decrement\"\n [disabled]=\"isDisabled() || !canDecrement()\"\n (click)=\"decrement()\"\n [attr.aria-label]=\"decrementAriaLabel()\"\n >\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <input\n #inputEl\n type=\"text\"\n [id]=\"inputId\"\n [attr.inputmode]=\"inputMode()\"\n class=\"number-input__input\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.aria-label]=\"label() ? null : ariaLabel()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--increment\"\n [disabled]=\"isDisabled() || !canIncrement()\"\n (click)=\"increment()\"\n [attr.aria-label]=\"incrementAriaLabel()\"\n >\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"number-input__error\" role=\"alert\">{{ error() }}</p>\n } @else if (helperText()) {\n <p [id]=\"inputId + '-helper'\" class=\"number-input__helper\">{{ helperText() }}</p>\n }\n</div>\n",
|
|
262
|
-
styles: [".number-input{display:inline-flex;flex-direction:column;gap:.375rem}.number-input__label{font-size:var(--typography-font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary)}.number-input__control{display:inline-flex;align-items:center;border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);overflow:hidden;background:var(--color-surface-2);transition:border-color .15s ease}.number-input__control:hover{border-color:var(--color-text-secondary)}.number-input__control:focus-within{border-color:var(--color-primary-500);box-shadow:0 0 0 3px #20849733}.number-input__button{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;flex-shrink:0;transition:background-color .15s ease,color .15s ease}.number-input__button:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.number-input__button:disabled{opacity:.4;cursor:not-allowed}.number-input__button--decrement{border-right:1px solid var(--color-border)}.number-input__button--increment{border-left:1px solid var(--color-border)}.number-input__input{width:4rem;text-align:center;border:none;outline:none;background:transparent;font-size:var(--typography-font-size-base, 1rem);font-variant-numeric:tabular-nums;color:var(--color-text-primary);padding:.5rem .25rem}.number-input__input::placeholder{color:var(--color-text-tertiary)}.number-input__input:disabled{cursor:not-allowed}.number-input__helper,.number-input__error{margin:0;font-size:var(--typography-font-size-xs, .75rem);line-height:1.4}.number-input__helper{color:var(--color-text-tertiary)}.number-input__error{color:var(--color-text-error)}.number-input--error .number-input__control{border-color:var(--color-error-600)}.number-input--error .number-input__control:focus-within{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.number-input--disabled{opacity:.5;pointer-events:none}@media(forced-colors:active){.number-input--error .number-input__control:focus-within{outline-color:Highlight}}\n"],
|
|
262
|
+
styles: [".number-input{display:inline-flex;flex-direction:column;gap:.375rem}.number-input__label{font-size:var(--typography-font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary)}.number-input__control{display:inline-flex;align-items:center;border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);overflow:hidden;background:var(--color-surface-2);transition:border-color .15s ease}.number-input__control:hover{border-color:var(--color-text-secondary)}.number-input__control:focus-within{border-color:var(--color-primary-500);box-shadow:0 0 0 3px #20849733}.number-input__button{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;flex-shrink:0;transition:background-color .15s ease,color .15s ease}.number-input__button:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.number-input__button:disabled{opacity:.4;cursor:not-allowed}.number-input__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.number-input__button--decrement{border-right:1px solid var(--color-border)}.number-input__button--increment{border-left:1px solid var(--color-border)}.number-input__input{width:4rem;text-align:center;border:none;outline:none;background:transparent;font-size:var(--typography-font-size-base, 1rem);font-variant-numeric:tabular-nums;color:var(--color-text-primary);padding:.5rem .25rem}.number-input__input::placeholder{color:var(--color-text-tertiary)}.number-input__input:disabled{cursor:not-allowed}.number-input__helper,.number-input__error{margin:0;font-size:var(--typography-font-size-xs, .75rem);line-height:1.4}.number-input__helper{color:var(--color-text-tertiary)}.number-input__error{color:var(--color-text-error)}.number-input--error .number-input__control{border-color:var(--color-error-600)}.number-input--error .number-input__control:focus-within{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.number-input--disabled{opacity:.5;pointer-events:none}@media(forced-colors:active){.number-input--error .number-input__control:focus-within,.number-input__button:focus-visible{outline-color:Highlight}}\n"],
|
|
263
263
|
dependencies: [{
|
|
264
264
|
kind: "component",
|
|
265
265
|
type: IconComponent,
|
|
@@ -297,7 +297,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
297
297
|
multi: true
|
|
298
298
|
}],
|
|
299
299
|
template: "<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"number-input__label\" [for]=\"inputId\">{{ label() }}</label>\n }\n <div class=\"number-input__control\">\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--decrement\"\n [disabled]=\"isDisabled() || !canDecrement()\"\n (click)=\"decrement()\"\n [attr.aria-label]=\"decrementAriaLabel()\"\n >\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <input\n #inputEl\n type=\"text\"\n [id]=\"inputId\"\n [attr.inputmode]=\"inputMode()\"\n class=\"number-input__input\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.aria-label]=\"label() ? null : ariaLabel()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--increment\"\n [disabled]=\"isDisabled() || !canIncrement()\"\n (click)=\"increment()\"\n [attr.aria-label]=\"incrementAriaLabel()\"\n >\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"number-input__error\" role=\"alert\">{{ error() }}</p>\n } @else if (helperText()) {\n <p [id]=\"inputId + '-helper'\" class=\"number-input__helper\">{{ helperText() }}</p>\n }\n</div>\n",
|
|
300
|
-
styles: [".number-input{display:inline-flex;flex-direction:column;gap:.375rem}.number-input__label{font-size:var(--typography-font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary)}.number-input__control{display:inline-flex;align-items:center;border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);overflow:hidden;background:var(--color-surface-2);transition:border-color .15s ease}.number-input__control:hover{border-color:var(--color-text-secondary)}.number-input__control:focus-within{border-color:var(--color-primary-500);box-shadow:0 0 0 3px #20849733}.number-input__button{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;flex-shrink:0;transition:background-color .15s ease,color .15s ease}.number-input__button:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.number-input__button:disabled{opacity:.4;cursor:not-allowed}.number-input__button--decrement{border-right:1px solid var(--color-border)}.number-input__button--increment{border-left:1px solid var(--color-border)}.number-input__input{width:4rem;text-align:center;border:none;outline:none;background:transparent;font-size:var(--typography-font-size-base, 1rem);font-variant-numeric:tabular-nums;color:var(--color-text-primary);padding:.5rem .25rem}.number-input__input::placeholder{color:var(--color-text-tertiary)}.number-input__input:disabled{cursor:not-allowed}.number-input__helper,.number-input__error{margin:0;font-size:var(--typography-font-size-xs, .75rem);line-height:1.4}.number-input__helper{color:var(--color-text-tertiary)}.number-input__error{color:var(--color-text-error)}.number-input--error .number-input__control{border-color:var(--color-error-600)}.number-input--error .number-input__control:focus-within{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.number-input--disabled{opacity:.5;pointer-events:none}@media(forced-colors:active){.number-input--error .number-input__control:focus-within{outline-color:Highlight}}\n"]
|
|
300
|
+
styles: [".number-input{display:inline-flex;flex-direction:column;gap:.375rem}.number-input__label{font-size:var(--typography-font-size-sm, .875rem);font-weight:var(--typography-font-weight-medium, 500);color:var(--color-text-secondary)}.number-input__control{display:inline-flex;align-items:center;border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);overflow:hidden;background:var(--color-surface-2);transition:border-color .15s ease}.number-input__control:hover{border-color:var(--color-text-secondary)}.number-input__control:focus-within{border-color:var(--color-primary-500);box-shadow:0 0 0 3px #20849733}.number-input__button{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:none;background:transparent;color:var(--color-text-secondary);cursor:pointer;flex-shrink:0;transition:background-color .15s ease,color .15s ease}.number-input__button:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.number-input__button:disabled{opacity:.4;cursor:not-allowed}.number-input__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:-2px}.number-input__button--decrement{border-right:1px solid var(--color-border)}.number-input__button--increment{border-left:1px solid var(--color-border)}.number-input__input{width:4rem;text-align:center;border:none;outline:none;background:transparent;font-size:var(--typography-font-size-base, 1rem);font-variant-numeric:tabular-nums;color:var(--color-text-primary);padding:.5rem .25rem}.number-input__input::placeholder{color:var(--color-text-tertiary)}.number-input__input:disabled{cursor:not-allowed}.number-input__helper,.number-input__error{margin:0;font-size:var(--typography-font-size-xs, .75rem);line-height:1.4}.number-input__helper{color:var(--color-text-tertiary)}.number-input__error{color:var(--color-text-error)}.number-input--error .number-input__control{border-color:var(--color-error-600)}.number-input--error .number-input__control:focus-within{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.number-input--disabled{opacity:.5;pointer-events:none}@media(forced-colors:active){.number-input--error .number-input__control:focus-within,.number-input__button:focus-visible{outline-color:Highlight}}\n"]
|
|
301
301
|
}]
|
|
302
302
|
}],
|
|
303
303
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-number-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/number-input/number-input.component.ts","../../../../libs/angular-ui-kit/src/lib/number-input/number-input.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n input,\n output,\n computed,\n signal,\n forwardRef,\n viewChild,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_PLUS } from '@lifecorner/angular-ui-kit/icons';\n\n/** Number of decimals a number is written with (`0.001` → 3, `1e-7` → 7, `5` → 0). */\nfunction decimalsOf(n: number): number {\n if (!Number.isFinite(n)) return 0;\n const [mantissa, exponent] = String(n).split('e');\n const decimals = (mantissa.split('.')[1]?.length ?? 0) - (exponent ? Number(exponent) : 0);\n return Math.max(0, decimals);\n}\n\n/**\n * Reduces raw text to something `Number()` can parse: digits, one leading\n * minus and one decimal point. A comma is taken as a decimal separator so a\n * German keyboard layout does not silently lose the fraction.\n */\nfunction sanitizeNumericText(raw: string): string {\n let out = '';\n let seenPoint = false;\n for (const ch of raw) {\n if (ch >= '0' && ch <= '9') {\n out += ch;\n } else if (ch === '-' && out.length === 0) {\n out += ch;\n } else if ((ch === '.' || ch === ',') && !seenPoint) {\n seenPoint = true;\n out += '.';\n }\n }\n return out;\n}\n\n@Component({\n selector: 'lc-number-input',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS])],\n templateUrl: './number-input.component.html',\n styleUrls: ['./number-input.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => NumberInputComponent),\n multi: true,\n },\n ],\n})\n/**\n * Number input component with increment/decrement controls.\n *\n * Features:\n * - Increment and decrement buttons (also ArrowUp / ArrowDown)\n * - Configurable min, max, and step values\n * - Value clamping within bounds; stepping is rounded to the step's precision\n * - Non-numeric characters are rejected as they are typed\n * - Disabled state support\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-number-input label=\"Quantity\" [min]=\"0\" [max]=\"100\" [step]=\"1\" />\n * ```\n */\nexport class NumberInputComponent implements ControlValueAccessor {\n private static nextId = 0;\n\n /** Minimum value */\n min = input<number | undefined>(undefined);\n\n /** Maximum value */\n max = input<number | undefined>(undefined);\n\n /** Step increment */\n step = input<number>(1);\n\n /** Label text */\n label = input<string>();\n\n /** Accessible name for the field when no visible `label` is rendered. */\n ariaLabel = input<string>();\n\n /** Placeholder */\n placeholder = input<string>('0');\n\n /** Helper text shown below the control (linked via `aria-describedby`). */\n helperText = input<string>();\n\n /** Error message shown below the control; marks the field `aria-invalid`. */\n error = input<string>();\n\n /** Disabled state */\n disabled = input<boolean>(false);\n\n /** Accessible name of the minus button. */\n decrementAriaLabel = input('Decrease value');\n\n /** Accessible name of the plus button. */\n incrementAriaLabel = input('Increase value');\n\n /** Emits value on change */\n valueChange = output<number>();\n\n /** Per-instance id wiring the label, hint and error to the field. */\n readonly inputId = `lc-number-input-${++NumberInputComponent.nextId}`;\n\n protected value = signal<number | null>(null);\n\n /**\n * Text shown in the field. It is re-derived from the model on `writeValue`,\n * stepping and blur — never while typing — so an in-progress entry such as\n * \"1.\" or \"-\" is not snapped back to \"1\" under the user's cursor.\n */\n protected readonly text = signal('');\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n private readonly inputEl = viewChild<ElementRef<HTMLInputElement>>('inputEl');\n\n /** Decimals implied by `step` — stepped results are rounded to it so 0.1 + 0.2 does not drift. */\n private readonly stepPrecision = computed(() => decimalsOf(this.step()));\n\n /** Fractions need a decimal key on touch keyboards; integers do not. */\n protected readonly inputMode = computed(() => (this.stepPrecision() > 0 ? 'decimal' : 'numeric'));\n\n protected readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.error()) ids.push(`${this.inputId}-error`);\n else if (this.helperText()) ids.push(`${this.inputId}-helper`);\n return ids.length ? ids.join(' ') : null;\n });\n\n protected containerClasses = computed(() => {\n const classes = ['number-input'];\n if (this.isDisabled()) classes.push('number-input--disabled');\n if (this.error()) classes.push('number-input--error');\n return classes.join(' ');\n });\n\n protected canDecrement = computed(() => {\n const val = this.value();\n const minV = this.min();\n if (val === null || minV === undefined) return true;\n return val > minV;\n });\n\n protected canIncrement = computed(() => {\n const val = this.value();\n const maxV = this.max();\n if (val === null || maxV === undefined) return true;\n return val < maxV;\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (val: number | null) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n decrement(): void {\n this.stepBy(-1);\n }\n\n increment(): void {\n this.stepBy(1);\n }\n\n private stepBy(direction: 1 | -1): void {\n const cur = this.value() ?? 0;\n // Keep whatever precision the user typed (1.25 + 0.1 → 1.35), but never\n // less than the step's own — that is what removes the binary drift.\n const precision = Math.max(this.stepPrecision(), decimalsOf(cur));\n const next = this.clamp(Number((cur + direction * this.step()).toFixed(precision)));\n this.setValue(next);\n this.text.set(this.format(next));\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n if (this.canIncrement()) this.increment();\n } else if (event.key === 'ArrowDown') {\n event.preventDefault();\n if (this.canDecrement()) this.decrement();\n }\n }\n\n onInput(event: Event): void {\n const target = event.target as HTMLInputElement;\n const raw = target.value;\n const text = sanitizeNumericText(raw);\n // Property binding alone would not repaint the field when the model does\n // not change (typing \"a\" after \"12\" leaves 12 as-is), so the stripped\n // text is written straight back to the element.\n if (text !== raw) {\n const caret = Math.max(0, (target.selectionStart ?? text.length) - (raw.length - text.length));\n target.value = text;\n target.setSelectionRange(caret, caret);\n }\n this.text.set(text);\n // \"\" would parse as 0; it and the other in-progress entries (\"-\", \".\")\n // mean the model has no number yet.\n const parsed = text === '' ? NaN : Number(text);\n if (Number.isNaN(parsed)) {\n if (this.value() !== null) {\n this.value.set(null);\n this.onChange(null);\n }\n return;\n }\n this.setValue(parsed);\n }\n\n onBlur(): void {\n this.onTouched();\n // Clamp on blur\n const val = this.value();\n if (val !== null) {\n const clamped = this.clamp(val);\n if (clamped !== val) this.setValue(clamped);\n }\n // Whatever half-typed text is left (\"1.\", \"-\", \"007\") is replaced by the\n // model's canonical form. The element is written directly as well, in\n // case the DOM drifted from the bound text without an input event.\n const canonical = this.format(this.value());\n this.text.set(canonical);\n const el = this.inputEl()?.nativeElement;\n if (el) el.value = canonical;\n }\n\n private format(val: number | null): string {\n return val === null ? '' : String(val);\n }\n\n private clamp(val: number): number {\n const minV = this.min();\n const maxV = this.max();\n if (minV !== undefined) val = Math.max(minV, val);\n if (maxV !== undefined) val = Math.min(maxV, val);\n return val;\n }\n\n private setValue(val: number): void {\n if (val === this.value()) return;\n this.value.set(val);\n this.valueChange.emit(val);\n this.onChange(val);\n }\n\n // ControlValueAccessor\n writeValue(val: number | null): void {\n this.value.set(val);\n this.text.set(this.format(val));\n }\n\n registerOnChange(fn: (val: number | null) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n}\n","<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"number-input__label\" [for]=\"inputId\">{{ label() }}</label>\n }\n <div class=\"number-input__control\">\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--decrement\"\n [disabled]=\"isDisabled() || !canDecrement()\"\n (click)=\"decrement()\"\n [attr.aria-label]=\"decrementAriaLabel()\"\n >\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <input\n #inputEl\n type=\"text\"\n [id]=\"inputId\"\n [attr.inputmode]=\"inputMode()\"\n class=\"number-input__input\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.aria-label]=\"label() ? null : ariaLabel()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--increment\"\n [disabled]=\"isDisabled() || !canIncrement()\"\n (click)=\"increment()\"\n [attr.aria-label]=\"incrementAriaLabel()\"\n >\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"number-input__error\" role=\"alert\">{{ error() }}</p>\n } @else if (helperText()) {\n <p [id]=\"inputId + '-helper'\" class=\"number-input__helper\">{{ helperText() }}</p>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-number-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/number-input/number-input.component.ts","../../../../libs/angular-ui-kit/src/lib/number-input/number-input.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n input,\n output,\n computed,\n signal,\n forwardRef,\n viewChild,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_PLUS } from '@lifecorner/angular-ui-kit/icons';\n\n/** Number of decimals a number is written with (`0.001` → 3, `1e-7` → 7, `5` → 0). */\nfunction decimalsOf(n: number): number {\n if (!Number.isFinite(n)) return 0;\n const [mantissa, exponent] = String(n).split('e');\n const decimals = (mantissa.split('.')[1]?.length ?? 0) - (exponent ? Number(exponent) : 0);\n return Math.max(0, decimals);\n}\n\n/**\n * Reduces raw text to something `Number()` can parse: digits, one leading\n * minus and one decimal point. A comma is taken as a decimal separator so a\n * German keyboard layout does not silently lose the fraction.\n */\nfunction sanitizeNumericText(raw: string): string {\n let out = '';\n let seenPoint = false;\n for (const ch of raw) {\n if (ch >= '0' && ch <= '9') {\n out += ch;\n } else if (ch === '-' && out.length === 0) {\n out += ch;\n } else if ((ch === '.' || ch === ',') && !seenPoint) {\n seenPoint = true;\n out += '.';\n }\n }\n return out;\n}\n\n@Component({\n selector: 'lc-number-input',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [ɵprovideKitIcons([ICON_PLUS])],\n templateUrl: './number-input.component.html',\n styleUrls: ['./number-input.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => NumberInputComponent),\n multi: true,\n },\n ],\n})\n/**\n * Number input component with increment/decrement controls.\n *\n * Features:\n * - Increment and decrement buttons (also ArrowUp / ArrowDown)\n * - Configurable min, max, and step values\n * - Value clamping within bounds; stepping is rounded to the step's precision\n * - Non-numeric characters are rejected as they are typed\n * - Disabled state support\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-number-input label=\"Quantity\" [min]=\"0\" [max]=\"100\" [step]=\"1\" />\n * ```\n */\nexport class NumberInputComponent implements ControlValueAccessor {\n private static nextId = 0;\n\n /** Minimum value */\n min = input<number | undefined>(undefined);\n\n /** Maximum value */\n max = input<number | undefined>(undefined);\n\n /** Step increment */\n step = input<number>(1);\n\n /** Label text */\n label = input<string>();\n\n /** Accessible name for the field when no visible `label` is rendered. */\n ariaLabel = input<string>();\n\n /** Placeholder */\n placeholder = input<string>('0');\n\n /** Helper text shown below the control (linked via `aria-describedby`). */\n helperText = input<string>();\n\n /** Error message shown below the control; marks the field `aria-invalid`. */\n error = input<string>();\n\n /** Disabled state */\n disabled = input<boolean>(false);\n\n /** Accessible name of the minus button. */\n decrementAriaLabel = input('Decrease value');\n\n /** Accessible name of the plus button. */\n incrementAriaLabel = input('Increase value');\n\n /** Emits value on change */\n valueChange = output<number>();\n\n /** Per-instance id wiring the label, hint and error to the field. */\n readonly inputId = `lc-number-input-${++NumberInputComponent.nextId}`;\n\n protected value = signal<number | null>(null);\n\n /**\n * Text shown in the field. It is re-derived from the model on `writeValue`,\n * stepping and blur — never while typing — so an in-progress entry such as\n * \"1.\" or \"-\" is not snapped back to \"1\" under the user's cursor.\n */\n protected readonly text = signal('');\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n private readonly inputEl = viewChild<ElementRef<HTMLInputElement>>('inputEl');\n\n /** Decimals implied by `step` — stepped results are rounded to it so 0.1 + 0.2 does not drift. */\n private readonly stepPrecision = computed(() => decimalsOf(this.step()));\n\n /** Fractions need a decimal key on touch keyboards; integers do not. */\n protected readonly inputMode = computed(() => (this.stepPrecision() > 0 ? 'decimal' : 'numeric'));\n\n protected readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.error()) ids.push(`${this.inputId}-error`);\n else if (this.helperText()) ids.push(`${this.inputId}-helper`);\n return ids.length ? ids.join(' ') : null;\n });\n\n protected containerClasses = computed(() => {\n const classes = ['number-input'];\n if (this.isDisabled()) classes.push('number-input--disabled');\n if (this.error()) classes.push('number-input--error');\n return classes.join(' ');\n });\n\n protected canDecrement = computed(() => {\n const val = this.value();\n const minV = this.min();\n if (val === null || minV === undefined) return true;\n return val > minV;\n });\n\n protected canIncrement = computed(() => {\n const val = this.value();\n const maxV = this.max();\n if (val === null || maxV === undefined) return true;\n return val < maxV;\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (val: number | null) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n decrement(): void {\n this.stepBy(-1);\n }\n\n increment(): void {\n this.stepBy(1);\n }\n\n private stepBy(direction: 1 | -1): void {\n const cur = this.value() ?? 0;\n // Keep whatever precision the user typed (1.25 + 0.1 → 1.35), but never\n // less than the step's own — that is what removes the binary drift.\n const precision = Math.max(this.stepPrecision(), decimalsOf(cur));\n const next = this.clamp(Number((cur + direction * this.step()).toFixed(precision)));\n this.setValue(next);\n this.text.set(this.format(next));\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n if (this.canIncrement()) this.increment();\n } else if (event.key === 'ArrowDown') {\n event.preventDefault();\n if (this.canDecrement()) this.decrement();\n }\n }\n\n onInput(event: Event): void {\n const target = event.target as HTMLInputElement;\n const raw = target.value;\n const text = sanitizeNumericText(raw);\n // Property binding alone would not repaint the field when the model does\n // not change (typing \"a\" after \"12\" leaves 12 as-is), so the stripped\n // text is written straight back to the element.\n if (text !== raw) {\n const caret = Math.max(0, (target.selectionStart ?? text.length) - (raw.length - text.length));\n target.value = text;\n target.setSelectionRange(caret, caret);\n }\n this.text.set(text);\n // \"\" would parse as 0; it and the other in-progress entries (\"-\", \".\")\n // mean the model has no number yet.\n const parsed = text === '' ? NaN : Number(text);\n if (Number.isNaN(parsed)) {\n if (this.value() !== null) {\n this.value.set(null);\n this.onChange(null);\n }\n return;\n }\n this.setValue(parsed);\n }\n\n onBlur(): void {\n this.onTouched();\n // Clamp on blur\n const val = this.value();\n if (val !== null) {\n const clamped = this.clamp(val);\n if (clamped !== val) this.setValue(clamped);\n }\n // Whatever half-typed text is left (\"1.\", \"-\", \"007\") is replaced by the\n // model's canonical form. The element is written directly as well, in\n // case the DOM drifted from the bound text without an input event.\n const canonical = this.format(this.value());\n this.text.set(canonical);\n const el = this.inputEl()?.nativeElement;\n if (el) el.value = canonical;\n }\n\n private format(val: number | null): string {\n return val === null ? '' : String(val);\n }\n\n private clamp(val: number): number {\n const minV = this.min();\n const maxV = this.max();\n if (minV !== undefined) val = Math.max(minV, val);\n if (maxV !== undefined) val = Math.min(maxV, val);\n return val;\n }\n\n private setValue(val: number): void {\n if (val === this.value()) return;\n this.value.set(val);\n this.valueChange.emit(val);\n this.onChange(val);\n }\n\n // ControlValueAccessor\n writeValue(val: number | null): void {\n this.value.set(val);\n this.text.set(this.format(val));\n }\n\n registerOnChange(fn: (val: number | null) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n}\n","<div [class]=\"containerClasses()\">\n @if (label()) {\n <label class=\"number-input__label\" [for]=\"inputId\">{{ label() }}</label>\n }\n <div class=\"number-input__control\">\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--decrement\"\n [disabled]=\"isDisabled() || !canDecrement()\"\n (click)=\"decrement()\"\n [attr.aria-label]=\"decrementAriaLabel()\"\n >\n <lc-icon name=\"minus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n <input\n #inputEl\n type=\"text\"\n [id]=\"inputId\"\n [attr.inputmode]=\"inputMode()\"\n class=\"number-input__input\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.aria-label]=\"label() ? null : ariaLabel()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"number-input__button number-input__button--increment\"\n [disabled]=\"isDisabled() || !canIncrement()\"\n (click)=\"increment()\"\n [attr.aria-label]=\"incrementAriaLabel()\"\n >\n <lc-icon name=\"plus\" size=\"sm\" [decorative]=\"true\" />\n </button>\n </div>\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"number-input__error\" role=\"alert\">{{ error() }}</p>\n } @else if (helperText()) {\n <p [id]=\"inputId + '-helper'\" class=\"number-input__helper\">{{ helperText() }}</p>\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -528,7 +528,7 @@ var RichTextEditorComponent = class RichTextEditorComponent {
|
|
|
528
528
|
}],
|
|
529
529
|
ngImport: i0,
|
|
530
530
|
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
531
|
-
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}\n"],
|
|
531
|
+
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__mode-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}@media(forced-colors:active){.lc-rich-text-editor__toolbar-btn:focus-visible,.lc-rich-text-editor__mode-btn:focus-visible{outline-color:Highlight}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:Highlight;outline-color:Highlight}}\n"],
|
|
532
532
|
dependencies: [{
|
|
533
533
|
kind: "ngmodule",
|
|
534
534
|
type: CommonModule
|
|
@@ -554,7 +554,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
554
554
|
multi: true
|
|
555
555
|
}],
|
|
556
556
|
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
557
|
-
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}\n"]
|
|
557
|
+
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary);color:#fff}.lc-rich-text-editor__mode-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}@media(forced-colors:active){.lc-rich-text-editor__toolbar-btn:focus-visible,.lc-rich-text-editor__mode-btn:focus-visible{outline-color:Highlight}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:Highlight;outline-color:Highlight}}\n"]
|
|
558
558
|
}]
|
|
559
559
|
}],
|
|
560
560
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-rich-text-editor.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.ts","../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n SecurityContext,\n computed,\n effect,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type RichTextEditorMode = 'rich' | 'markdown' | 'split';\nexport type ToolbarAction =\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strikethrough'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'ul'\n | 'ol'\n | 'blockquote'\n | 'code'\n | 'codeblock'\n | 'link'\n | 'image'\n | 'hr';\n\nexport interface ToolbarConfig {\n actions: ToolbarAction[];\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes text for HTML — every character that could open a tag or an attribute. */\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/**\n * The URL as it may go into `href`/`src`, or null if it must not: only relative\n * URLs and the allow-listed schemes pass (see `safe-url.ts`), so a link in\n * user-supplied markdown can never run script when clicked.\n */\nfunction safeUrl(url: string, image = false): string | null {\n const value = url.trim();\n if (!value) return null;\n return (image ? isSafeImageUrl(value) : isSafeLinkUrl(value)) ? value : null;\n}\n\nconst DEFAULT_TOOLBAR: ToolbarConfig = {\n actions: [\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'h1',\n 'h2',\n 'h3',\n 'ul',\n 'ol',\n 'blockquote',\n 'code',\n 'codeblock',\n 'link',\n 'hr',\n ],\n};\n\n/**\n * Rich Text Editor with Markdown support.\n *\n * Supports three modes:\n * - `rich`: WYSIWYG editing with toolbar\n * - `markdown`: Raw markdown editing with syntax highlighting\n * - `split`: Side-by-side markdown and preview\n *\n * Implements ControlValueAccessor for reactive form integration.\n * The value is always stored as Markdown.\n *\n * The built-in markdown → HTML conversion escapes the source before it applies\n * any markup and only lets http(s)/mailto/tel and relative URLs through as\n * links, so markdown that arrives from a server or another user cannot inject\n * markup or script. The result is additionally run through Angular's HTML\n * sanitizer before it reaches the DOM.\n *\n * @example\n * ```html\n * <lc-rich-text-editor\n * [(ngModel)]=\"content\"\n * mode=\"split\"\n * placeholder=\"Write something...\" />\n * ```\n */\n@Component({\n selector: 'lc-rich-text-editor',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './rich-text-editor.component.html',\n styleUrl: './rich-text-editor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n ],\n})\nexport class RichTextEditorComponent implements ControlValueAccessor {\n /** Editor mode */\n mode = input<RichTextEditorMode>('rich');\n\n /** Placeholder text */\n placeholder = input<string>('Start typing...');\n\n /** Minimum height in px */\n minHeight = input<number>(200);\n\n /** Maximum height in px (0 = unlimited) */\n maxHeight = input<number>(0);\n\n /** Whether the editor is disabled */\n disabled = input<boolean>(false);\n\n /** Whether the editor is readonly */\n readonly = input<boolean>(false);\n\n /** Toolbar configuration */\n toolbar = input<ToolbarConfig>(DEFAULT_TOOLBAR);\n\n /** Whether to show word count */\n showWordCount = input<boolean>(true);\n\n /**\n * Tooltips and accessible names of the toolbar actions, by action\n * (`{ bold: 'Fett' }`); missing ones keep the English default. A visible\n * button label with letters or digits that the title doesn't contain is\n * appended (`Heading 1, H1`, WCAG 2.5.3 Label in Name).\n * @default {}\n */\n actionTitles = input<Partial<Record<ToolbarAction, string>>>({});\n\n /** Label of the rich-text mode button. */\n richModeLabel = input('Rich');\n\n /** Tooltip of the rich-text mode button. */\n richModeTitle = input('Rich Text');\n\n /** Label of the Markdown mode button. */\n markdownModeLabel = input('MD');\n\n /** Tooltip of the Markdown mode button. */\n markdownModeTitle = input('Markdown');\n\n /** Label of the split-view mode button. */\n splitModeLabel = input('Split');\n\n /** Tooltip of the split-view mode button. */\n splitModeTitle = input('Split View');\n\n /** Accessible name of the Markdown text area. */\n markdownEditorAriaLabel = input('Markdown editor');\n\n /** Unit after the word count (`showWordCount`). */\n wordsLabel = input('words');\n\n /** Unit after the character count (`showWordCount`). */\n charactersLabel = input('characters');\n\n /** Emitted on every content change */\n readonly contentChange = output<string>();\n\n private readonly editorArea = viewChild<ElementRef<HTMLTextAreaElement>>('editorArea');\n private readonly richArea = viewChild<ElementRef<HTMLDivElement>>('richArea');\n private readonly sanitizer = inject(DomSanitizer);\n\n /** Internal markdown value */\n protected markdown = signal('');\n\n /** Current active mode: follows the `mode` input until the user toggles it. */\n protected activeMode = linkedSignal<RichTextEditorMode>(() => this.mode());\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n /** Whether the content can currently be edited at all. */\n protected readonly editable = computed(() => !this.isDisabled() && !this.readonly());\n\n /** Word count */\n protected wordCount = computed(() => {\n const text = this.markdown().trim();\n if (!text) return 0;\n return text.split(/\\s+/).length;\n });\n\n /** Character count */\n protected charCount = computed(() => this.markdown().length);\n\n /**\n * Rendered HTML from markdown — escaped by the converter and, on top of that,\n * passed through Angular's HTML sanitizer, so it is safe to assign to\n * `innerHTML` directly.\n */\n protected renderedHtml = computed(() => this.sanitizeHtml(this.markdownToHtml(this.markdown())));\n\n // CVA\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n constructor() {\n // The rich area lives inside an `@switch`, so it comes and goes with the\n // mode. Whenever it (re)appears, it starts empty and has to be filled from\n // the markdown — including the very first time, which is after any\n // `writeValue` a form control performs before the view exists. Only the\n // element's presence is tracked: re-rendering on every markdown change would\n // wipe the caret while the user types in it.\n effect(() => {\n const el = this.richArea()?.nativeElement;\n if (el) untracked(() => this.renderRich(el));\n });\n }\n\n // -- ControlValueAccessor --\n writeValue(value: string | null | undefined): void {\n this.markdown.set(value || '');\n const el = this.richArea()?.nativeElement;\n if (el) this.renderRich(el);\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // -- Mode switching --\n protected switchMode(mode: RichTextEditorMode): void {\n if (this.activeMode() === 'rich' && mode !== 'rich') {\n // Sync rich content to markdown before the rich area is torn down.\n const el = this.richArea()?.nativeElement;\n if (el) this.markdown.set(this.htmlToMarkdown(el.innerHTML));\n }\n // Entering rich mode: the effect in the constructor renders the markdown\n // into the freshly created rich area.\n this.activeMode.set(mode);\n }\n\n // -- Toolbar actions --\n protected onToolbarAction(action: ToolbarAction): void {\n if (!this.editable()) return;\n\n if (this.activeMode() === 'rich') {\n this.execRichCommand(action);\n } else {\n this.insertMarkdownSyntax(action);\n }\n }\n\n private execRichCommand(action: ToolbarAction): void {\n switch (action) {\n case 'bold':\n document.execCommand('bold');\n break;\n case 'italic':\n document.execCommand('italic');\n break;\n case 'underline':\n document.execCommand('underline');\n break;\n case 'strikethrough':\n document.execCommand('strikeThrough');\n break;\n case 'h1':\n document.execCommand('formatBlock', false, 'h1');\n break;\n case 'h2':\n document.execCommand('formatBlock', false, 'h2');\n break;\n case 'h3':\n document.execCommand('formatBlock', false, 'h3');\n break;\n case 'ul':\n document.execCommand('insertUnorderedList');\n break;\n case 'ol':\n document.execCommand('insertOrderedList');\n break;\n case 'blockquote':\n document.execCommand('formatBlock', false, 'blockquote');\n break;\n case 'code':\n document.execCommand('insertHTML', false, '<code>' + this.getSelection() + '</code>');\n break;\n case 'codeblock':\n document.execCommand('insertHTML', false, '<pre><code>' + this.getSelection() + '</code></pre>');\n break;\n case 'link': {\n const url = prompt('Enter URL:');\n if (url) document.execCommand('createLink', false, url);\n break;\n }\n case 'hr':\n document.execCommand('insertHorizontalRule');\n break;\n }\n this.syncFromRich();\n }\n\n private insertMarkdownSyntax(action: ToolbarAction): void {\n const textarea = this.editorArea()?.nativeElement;\n if (!textarea) return;\n\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const text = this.markdown();\n const selected = text.substring(start, end);\n\n let insertion = '';\n let cursorOffset = 0;\n\n switch (action) {\n case 'bold':\n insertion = `**${selected || 'bold text'}**`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'italic':\n insertion = `*${selected || 'italic text'}*`;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'underline':\n insertion = `<u>${selected || 'underlined text'}</u>`;\n cursorOffset = selected ? insertion.length : 3;\n break;\n case 'strikethrough':\n insertion = `~~${selected || 'strikethrough'}~~`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'h1':\n insertion = `# ${selected || 'Heading 1'}`;\n cursorOffset = insertion.length;\n break;\n case 'h2':\n insertion = `## ${selected || 'Heading 2'}`;\n cursorOffset = insertion.length;\n break;\n case 'h3':\n insertion = `### ${selected || 'Heading 3'}`;\n cursorOffset = insertion.length;\n break;\n case 'ul':\n insertion = `- ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'ol':\n insertion = `1. ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'blockquote':\n insertion = `> ${selected || 'Quote'}`;\n cursorOffset = insertion.length;\n break;\n case 'code':\n insertion = `\\`${selected || 'code'}\\``;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'codeblock':\n insertion = `\\`\\`\\`\\n${selected || 'code'}\\n\\`\\`\\``;\n cursorOffset = 4;\n break;\n case 'link':\n insertion = `[${selected || 'link text'}](url)`;\n cursorOffset = selected ? insertion.length - 5 : 1;\n break;\n case 'image':\n insertion = ``;\n cursorOffset = selected ? insertion.length - 5 : 2;\n break;\n case 'hr':\n insertion = `\\n---\\n`;\n cursorOffset = insertion.length;\n break;\n }\n\n const newText = text.substring(0, start) + insertion + text.substring(end);\n this.markdown.set(newText);\n this.emitChange(newText);\n\n setTimeout(() => {\n textarea.focus();\n textarea.setSelectionRange(start + cursorOffset, start + cursorOffset);\n });\n }\n\n // -- Content input handlers --\n protected onMarkdownInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n this.markdown.set(value);\n this.emitChange(value);\n }\n\n protected onRichInput(): void {\n this.syncFromRich();\n }\n\n protected onBlur(): void {\n this.onTouched();\n }\n\n private syncFromRich(): void {\n const el = this.richArea()?.nativeElement;\n if (!el) return;\n const md = this.htmlToMarkdown(el.innerHTML);\n this.markdown.set(md);\n this.emitChange(md);\n }\n\n /** Puts the current markdown into the rich area, as sanitised HTML. */\n private renderRich(el: HTMLDivElement): void {\n el.innerHTML = this.renderedHtml();\n }\n\n /**\n * Angular's HTML sanitizer, as the last line of defence behind the escaping\n * `markdownToHtml` does itself: whatever it might miss, this strips.\n */\n private sanitizeHtml(html: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';\n }\n\n private emitChange(value: string): void {\n this.onChange(value);\n this.contentChange.emit(value);\n }\n\n private getSelection(): string {\n return window.getSelection()?.toString() || '';\n }\n\n // -- Toolbar label helpers --\n protected getActionLabel(action: ToolbarAction): string {\n const labels: Record<ToolbarAction, string> = {\n bold: 'B',\n italic: 'I',\n underline: 'U',\n strikethrough: 'S',\n h1: 'H1',\n h2: 'H2',\n h3: 'H3',\n ul: '•',\n ol: '1.',\n blockquote: '\"',\n code: '<>',\n codeblock: '{ }',\n link: '🔗',\n image: '🖼',\n hr: '—',\n };\n return labels[action] || action;\n }\n\n protected getActionTitle(action: ToolbarAction): string {\n const titles: Record<ToolbarAction, string> = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n h1: 'Heading 1',\n h2: 'Heading 2',\n h3: 'Heading 3',\n ul: 'Unordered List',\n ol: 'Ordered List',\n blockquote: 'Blockquote',\n code: 'Inline Code',\n codeblock: 'Code Block',\n link: 'Insert Link',\n image: 'Insert Image',\n hr: 'Horizontal Rule',\n };\n return this.actionTitles()[action] || titles[action] || action;\n }\n\n /**\n * Tooltip and accessible name of a toolbar button (AS-33, WCAG 2.5.3 Label\n * in Name): the title, then the visible label when it has letters or digits\n * the title doesn't contain (`Heading 1, H1`). Symbol labels keep the title.\n */\n protected getActionName(action: ToolbarAction): string {\n return nameWithText(this.getActionTitle(action), this.getActionLabel(action));\n }\n\n // -- Markdown <-> HTML conversion (simple built-in) --\n /**\n * Converts the editor's markdown subset to HTML. The source is HTML-escaped\n * *first*, so nothing in it can open a tag or an attribute; the conversions\n * below then only ever add markup of their own. Raw HTML in the markdown is\n * therefore shown as text — with one exception, `<u>`, which the toolbar's\n * underline action emits (markdown has no underline) and which is let back\n * through in escaped form only.\n */\n protected markdownToHtml(md: string): string {\n if (!md) return '';\n\n let html = escapeHtml(md);\n\n // Code blocks (must be first)\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n\n // Headings\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // Horizontal rule\n html = html.replace(/^---$/gm, '<hr>');\n\n // Bold/Italic\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // Underline: the one raw tag the editor itself writes into the markdown.\n html = html.replace(/<u>(.+?)<\\/u>/g, '<u>$1</u>');\n\n // Inline code\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // Links and images. Only safe URLs become attributes; anything else is\n // shown as its text — a `javascript:` link that just isn't a link is far\n // better than one that runs. Quotes are already escaped (see above), so\n // the URL cannot break out of the attribute either.\n html = html.replace(/!\\[(.+?)\\]\\((.+?)\\)/g, (_m, alt: string, src: string) => {\n const url = safeUrl(src, true);\n return url ? `<img alt=\"${alt}\" src=\"${url}\" />` : alt;\n });\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, (_m, text: string, href: string) => {\n const url = safeUrl(href);\n return url ? `<a href=\"${url}\">${text}</a>` : text;\n });\n\n // Blockquotes — the marker is `>` by now, the source having been escaped.\n html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');\n\n // Unordered lists\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');\n\n // Ordered lists\n html = html.replace(/^\\d+\\. (.+)$/gm, '<li>$1</li>');\n\n // Paragraphs (lines that aren't already wrapped)\n html = html.replace(/^(?!<[a-z])((?!^\\s*$).+)$/gm, '<p>$1</p>');\n\n // Clean up empty paragraphs\n html = html.replace(/<p>\\s*<\\/p>/g, '');\n\n return html.trim();\n }\n\n private htmlToMarkdown(html: string): string {\n if (!html) return '';\n\n let md = html;\n\n // Block elements\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, '# $1\\n');\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, '## $1\\n');\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, '### $1\\n');\n md = md.replace(/<hr\\s*\\/?>/gi, '---\\n');\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gi, '> $1\\n');\n md = md.replace(/<pre><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n');\n\n // Lists\n md = md.replace(/<ul[^>]*>([\\s\\S]*?)<\\/ul>/gi, '$1');\n md = md.replace(/<ol[^>]*>([\\s\\S]*?)<\\/ol>/gi, '$1');\n md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, '- $1\\n');\n\n // Inline\n md = md.replace(/<strong[^>]*>(.*?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.*?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.*?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.*?)<\\/i>/gi, '*$1*');\n md = md.replace(/<del[^>]*>(.*?)<\\/del>/gi, '~~$1~~');\n md = md.replace(/<u[^>]*>(.*?)<\\/u>/gi, '<u>$1</u>');\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, '`$1`');\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, '[$2]($1)');\n md = md.replace(/<img[^>]*alt=\"([^\"]*)\"[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, '');\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>(.*?)<\\/p>/gi, '$1\\n\\n');\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<div[^>]*>(.*?)<\\/div>/gi, '$1\\n');\n\n // Strip remaining HTML tags\n md = md.replace(/<[^>]+>/g, '');\n\n // Decode HTML entities\n md = md.replace(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/ /g, ' ');\n\n // Normalize whitespace\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n }\n}\n","<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-rich-text-editor.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.ts","../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n SecurityContext,\n computed,\n effect,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type RichTextEditorMode = 'rich' | 'markdown' | 'split';\nexport type ToolbarAction =\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strikethrough'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'ul'\n | 'ol'\n | 'blockquote'\n | 'code'\n | 'codeblock'\n | 'link'\n | 'image'\n | 'hr';\n\nexport interface ToolbarConfig {\n actions: ToolbarAction[];\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes text for HTML — every character that could open a tag or an attribute. */\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/**\n * The URL as it may go into `href`/`src`, or null if it must not: only relative\n * URLs and the allow-listed schemes pass (see `safe-url.ts`), so a link in\n * user-supplied markdown can never run script when clicked.\n */\nfunction safeUrl(url: string, image = false): string | null {\n const value = url.trim();\n if (!value) return null;\n return (image ? isSafeImageUrl(value) : isSafeLinkUrl(value)) ? value : null;\n}\n\nconst DEFAULT_TOOLBAR: ToolbarConfig = {\n actions: [\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'h1',\n 'h2',\n 'h3',\n 'ul',\n 'ol',\n 'blockquote',\n 'code',\n 'codeblock',\n 'link',\n 'hr',\n ],\n};\n\n/**\n * Rich Text Editor with Markdown support.\n *\n * Supports three modes:\n * - `rich`: WYSIWYG editing with toolbar\n * - `markdown`: Raw markdown editing with syntax highlighting\n * - `split`: Side-by-side markdown and preview\n *\n * Implements ControlValueAccessor for reactive form integration.\n * The value is always stored as Markdown.\n *\n * The built-in markdown → HTML conversion escapes the source before it applies\n * any markup and only lets http(s)/mailto/tel and relative URLs through as\n * links, so markdown that arrives from a server or another user cannot inject\n * markup or script. The result is additionally run through Angular's HTML\n * sanitizer before it reaches the DOM.\n *\n * @example\n * ```html\n * <lc-rich-text-editor\n * [(ngModel)]=\"content\"\n * mode=\"split\"\n * placeholder=\"Write something...\" />\n * ```\n */\n@Component({\n selector: 'lc-rich-text-editor',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './rich-text-editor.component.html',\n styleUrl: './rich-text-editor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n ],\n})\nexport class RichTextEditorComponent implements ControlValueAccessor {\n /** Editor mode */\n mode = input<RichTextEditorMode>('rich');\n\n /** Placeholder text */\n placeholder = input<string>('Start typing...');\n\n /** Minimum height in px */\n minHeight = input<number>(200);\n\n /** Maximum height in px (0 = unlimited) */\n maxHeight = input<number>(0);\n\n /** Whether the editor is disabled */\n disabled = input<boolean>(false);\n\n /** Whether the editor is readonly */\n readonly = input<boolean>(false);\n\n /** Toolbar configuration */\n toolbar = input<ToolbarConfig>(DEFAULT_TOOLBAR);\n\n /** Whether to show word count */\n showWordCount = input<boolean>(true);\n\n /**\n * Tooltips and accessible names of the toolbar actions, by action\n * (`{ bold: 'Fett' }`); missing ones keep the English default. A visible\n * button label with letters or digits that the title doesn't contain is\n * appended (`Heading 1, H1`, WCAG 2.5.3 Label in Name).\n * @default {}\n */\n actionTitles = input<Partial<Record<ToolbarAction, string>>>({});\n\n /** Label of the rich-text mode button. */\n richModeLabel = input('Rich');\n\n /** Tooltip of the rich-text mode button. */\n richModeTitle = input('Rich Text');\n\n /** Label of the Markdown mode button. */\n markdownModeLabel = input('MD');\n\n /** Tooltip of the Markdown mode button. */\n markdownModeTitle = input('Markdown');\n\n /** Label of the split-view mode button. */\n splitModeLabel = input('Split');\n\n /** Tooltip of the split-view mode button. */\n splitModeTitle = input('Split View');\n\n /** Accessible name of the Markdown text area. */\n markdownEditorAriaLabel = input('Markdown editor');\n\n /** Unit after the word count (`showWordCount`). */\n wordsLabel = input('words');\n\n /** Unit after the character count (`showWordCount`). */\n charactersLabel = input('characters');\n\n /** Emitted on every content change */\n readonly contentChange = output<string>();\n\n private readonly editorArea = viewChild<ElementRef<HTMLTextAreaElement>>('editorArea');\n private readonly richArea = viewChild<ElementRef<HTMLDivElement>>('richArea');\n private readonly sanitizer = inject(DomSanitizer);\n\n /** Internal markdown value */\n protected markdown = signal('');\n\n /** Current active mode: follows the `mode` input until the user toggles it. */\n protected activeMode = linkedSignal<RichTextEditorMode>(() => this.mode());\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n /** Whether the content can currently be edited at all. */\n protected readonly editable = computed(() => !this.isDisabled() && !this.readonly());\n\n /** Word count */\n protected wordCount = computed(() => {\n const text = this.markdown().trim();\n if (!text) return 0;\n return text.split(/\\s+/).length;\n });\n\n /** Character count */\n protected charCount = computed(() => this.markdown().length);\n\n /**\n * Rendered HTML from markdown — escaped by the converter and, on top of that,\n * passed through Angular's HTML sanitizer, so it is safe to assign to\n * `innerHTML` directly.\n */\n protected renderedHtml = computed(() => this.sanitizeHtml(this.markdownToHtml(this.markdown())));\n\n // CVA\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n constructor() {\n // The rich area lives inside an `@switch`, so it comes and goes with the\n // mode. Whenever it (re)appears, it starts empty and has to be filled from\n // the markdown — including the very first time, which is after any\n // `writeValue` a form control performs before the view exists. Only the\n // element's presence is tracked: re-rendering on every markdown change would\n // wipe the caret while the user types in it.\n effect(() => {\n const el = this.richArea()?.nativeElement;\n if (el) untracked(() => this.renderRich(el));\n });\n }\n\n // -- ControlValueAccessor --\n writeValue(value: string | null | undefined): void {\n this.markdown.set(value || '');\n const el = this.richArea()?.nativeElement;\n if (el) this.renderRich(el);\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // -- Mode switching --\n protected switchMode(mode: RichTextEditorMode): void {\n if (this.activeMode() === 'rich' && mode !== 'rich') {\n // Sync rich content to markdown before the rich area is torn down.\n const el = this.richArea()?.nativeElement;\n if (el) this.markdown.set(this.htmlToMarkdown(el.innerHTML));\n }\n // Entering rich mode: the effect in the constructor renders the markdown\n // into the freshly created rich area.\n this.activeMode.set(mode);\n }\n\n // -- Toolbar actions --\n protected onToolbarAction(action: ToolbarAction): void {\n if (!this.editable()) return;\n\n if (this.activeMode() === 'rich') {\n this.execRichCommand(action);\n } else {\n this.insertMarkdownSyntax(action);\n }\n }\n\n private execRichCommand(action: ToolbarAction): void {\n switch (action) {\n case 'bold':\n document.execCommand('bold');\n break;\n case 'italic':\n document.execCommand('italic');\n break;\n case 'underline':\n document.execCommand('underline');\n break;\n case 'strikethrough':\n document.execCommand('strikeThrough');\n break;\n case 'h1':\n document.execCommand('formatBlock', false, 'h1');\n break;\n case 'h2':\n document.execCommand('formatBlock', false, 'h2');\n break;\n case 'h3':\n document.execCommand('formatBlock', false, 'h3');\n break;\n case 'ul':\n document.execCommand('insertUnorderedList');\n break;\n case 'ol':\n document.execCommand('insertOrderedList');\n break;\n case 'blockquote':\n document.execCommand('formatBlock', false, 'blockquote');\n break;\n case 'code':\n document.execCommand('insertHTML', false, '<code>' + this.getSelection() + '</code>');\n break;\n case 'codeblock':\n document.execCommand('insertHTML', false, '<pre><code>' + this.getSelection() + '</code></pre>');\n break;\n case 'link': {\n const url = prompt('Enter URL:');\n if (url) document.execCommand('createLink', false, url);\n break;\n }\n case 'hr':\n document.execCommand('insertHorizontalRule');\n break;\n }\n this.syncFromRich();\n }\n\n private insertMarkdownSyntax(action: ToolbarAction): void {\n const textarea = this.editorArea()?.nativeElement;\n if (!textarea) return;\n\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const text = this.markdown();\n const selected = text.substring(start, end);\n\n let insertion = '';\n let cursorOffset = 0;\n\n switch (action) {\n case 'bold':\n insertion = `**${selected || 'bold text'}**`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'italic':\n insertion = `*${selected || 'italic text'}*`;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'underline':\n insertion = `<u>${selected || 'underlined text'}</u>`;\n cursorOffset = selected ? insertion.length : 3;\n break;\n case 'strikethrough':\n insertion = `~~${selected || 'strikethrough'}~~`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'h1':\n insertion = `# ${selected || 'Heading 1'}`;\n cursorOffset = insertion.length;\n break;\n case 'h2':\n insertion = `## ${selected || 'Heading 2'}`;\n cursorOffset = insertion.length;\n break;\n case 'h3':\n insertion = `### ${selected || 'Heading 3'}`;\n cursorOffset = insertion.length;\n break;\n case 'ul':\n insertion = `- ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'ol':\n insertion = `1. ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'blockquote':\n insertion = `> ${selected || 'Quote'}`;\n cursorOffset = insertion.length;\n break;\n case 'code':\n insertion = `\\`${selected || 'code'}\\``;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'codeblock':\n insertion = `\\`\\`\\`\\n${selected || 'code'}\\n\\`\\`\\``;\n cursorOffset = 4;\n break;\n case 'link':\n insertion = `[${selected || 'link text'}](url)`;\n cursorOffset = selected ? insertion.length - 5 : 1;\n break;\n case 'image':\n insertion = ``;\n cursorOffset = selected ? insertion.length - 5 : 2;\n break;\n case 'hr':\n insertion = `\\n---\\n`;\n cursorOffset = insertion.length;\n break;\n }\n\n const newText = text.substring(0, start) + insertion + text.substring(end);\n this.markdown.set(newText);\n this.emitChange(newText);\n\n setTimeout(() => {\n textarea.focus();\n textarea.setSelectionRange(start + cursorOffset, start + cursorOffset);\n });\n }\n\n // -- Content input handlers --\n protected onMarkdownInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n this.markdown.set(value);\n this.emitChange(value);\n }\n\n protected onRichInput(): void {\n this.syncFromRich();\n }\n\n protected onBlur(): void {\n this.onTouched();\n }\n\n private syncFromRich(): void {\n const el = this.richArea()?.nativeElement;\n if (!el) return;\n const md = this.htmlToMarkdown(el.innerHTML);\n this.markdown.set(md);\n this.emitChange(md);\n }\n\n /** Puts the current markdown into the rich area, as sanitised HTML. */\n private renderRich(el: HTMLDivElement): void {\n el.innerHTML = this.renderedHtml();\n }\n\n /**\n * Angular's HTML sanitizer, as the last line of defence behind the escaping\n * `markdownToHtml` does itself: whatever it might miss, this strips.\n */\n private sanitizeHtml(html: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';\n }\n\n private emitChange(value: string): void {\n this.onChange(value);\n this.contentChange.emit(value);\n }\n\n private getSelection(): string {\n return window.getSelection()?.toString() || '';\n }\n\n // -- Toolbar label helpers --\n protected getActionLabel(action: ToolbarAction): string {\n const labels: Record<ToolbarAction, string> = {\n bold: 'B',\n italic: 'I',\n underline: 'U',\n strikethrough: 'S',\n h1: 'H1',\n h2: 'H2',\n h3: 'H3',\n ul: '•',\n ol: '1.',\n blockquote: '\"',\n code: '<>',\n codeblock: '{ }',\n link: '🔗',\n image: '🖼',\n hr: '—',\n };\n return labels[action] || action;\n }\n\n protected getActionTitle(action: ToolbarAction): string {\n const titles: Record<ToolbarAction, string> = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n h1: 'Heading 1',\n h2: 'Heading 2',\n h3: 'Heading 3',\n ul: 'Unordered List',\n ol: 'Ordered List',\n blockquote: 'Blockquote',\n code: 'Inline Code',\n codeblock: 'Code Block',\n link: 'Insert Link',\n image: 'Insert Image',\n hr: 'Horizontal Rule',\n };\n return this.actionTitles()[action] || titles[action] || action;\n }\n\n /**\n * Tooltip and accessible name of a toolbar button (AS-33, WCAG 2.5.3 Label\n * in Name): the title, then the visible label when it has letters or digits\n * the title doesn't contain (`Heading 1, H1`). Symbol labels keep the title.\n */\n protected getActionName(action: ToolbarAction): string {\n return nameWithText(this.getActionTitle(action), this.getActionLabel(action));\n }\n\n // -- Markdown <-> HTML conversion (simple built-in) --\n /**\n * Converts the editor's markdown subset to HTML. The source is HTML-escaped\n * *first*, so nothing in it can open a tag or an attribute; the conversions\n * below then only ever add markup of their own. Raw HTML in the markdown is\n * therefore shown as text — with one exception, `<u>`, which the toolbar's\n * underline action emits (markdown has no underline) and which is let back\n * through in escaped form only.\n */\n protected markdownToHtml(md: string): string {\n if (!md) return '';\n\n let html = escapeHtml(md);\n\n // Code blocks (must be first)\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n\n // Headings\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // Horizontal rule\n html = html.replace(/^---$/gm, '<hr>');\n\n // Bold/Italic\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // Underline: the one raw tag the editor itself writes into the markdown.\n html = html.replace(/<u>(.+?)<\\/u>/g, '<u>$1</u>');\n\n // Inline code\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // Links and images. Only safe URLs become attributes; anything else is\n // shown as its text — a `javascript:` link that just isn't a link is far\n // better than one that runs. Quotes are already escaped (see above), so\n // the URL cannot break out of the attribute either.\n html = html.replace(/!\\[(.+?)\\]\\((.+?)\\)/g, (_m, alt: string, src: string) => {\n const url = safeUrl(src, true);\n return url ? `<img alt=\"${alt}\" src=\"${url}\" />` : alt;\n });\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, (_m, text: string, href: string) => {\n const url = safeUrl(href);\n return url ? `<a href=\"${url}\">${text}</a>` : text;\n });\n\n // Blockquotes — the marker is `>` by now, the source having been escaped.\n html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');\n\n // Unordered lists\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');\n\n // Ordered lists\n html = html.replace(/^\\d+\\. (.+)$/gm, '<li>$1</li>');\n\n // Paragraphs (lines that aren't already wrapped)\n html = html.replace(/^(?!<[a-z])((?!^\\s*$).+)$/gm, '<p>$1</p>');\n\n // Clean up empty paragraphs\n html = html.replace(/<p>\\s*<\\/p>/g, '');\n\n return html.trim();\n }\n\n private htmlToMarkdown(html: string): string {\n if (!html) return '';\n\n let md = html;\n\n // Block elements\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, '# $1\\n');\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, '## $1\\n');\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, '### $1\\n');\n md = md.replace(/<hr\\s*\\/?>/gi, '---\\n');\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gi, '> $1\\n');\n md = md.replace(/<pre><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n');\n\n // Lists\n md = md.replace(/<ul[^>]*>([\\s\\S]*?)<\\/ul>/gi, '$1');\n md = md.replace(/<ol[^>]*>([\\s\\S]*?)<\\/ol>/gi, '$1');\n md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, '- $1\\n');\n\n // Inline\n md = md.replace(/<strong[^>]*>(.*?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.*?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.*?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.*?)<\\/i>/gi, '*$1*');\n md = md.replace(/<del[^>]*>(.*?)<\\/del>/gi, '~~$1~~');\n md = md.replace(/<u[^>]*>(.*?)<\\/u>/gi, '<u>$1</u>');\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, '`$1`');\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, '[$2]($1)');\n md = md.replace(/<img[^>]*alt=\"([^\"]*)\"[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, '');\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>(.*?)<\\/p>/gi, '$1\\n\\n');\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<div[^>]*>(.*?)<\\/div>/gi, '$1\\n');\n\n // Strip remaining HTML tags\n md = md.replace(/<[^>]+>/g, '');\n\n // Decode HTML entities\n md = md.replace(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/ /g, ' ');\n\n // Normalize whitespace\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n }\n}\n","<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -272,7 +272,7 @@ var ScatterPlotComponent = class ScatterPlotComponent {
|
|
|
272
272
|
outputs: { pointClick: "pointClick" },
|
|
273
273
|
ngImport: i0,
|
|
274
274
|
template: "<div class=\"lc-scatter-plot\">\n <svg\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n [attr.role]=\"interactive() ? 'group' : 'img'\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-scatter-plot__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid, axes and tick labels are decoration for AT; the summary carries the meaning. -->\n <g aria-hidden=\"true\">\n <!-- Grid lines Y -->\n @for (line of gridLinesY(); track line.y) {\n <line\n [attr.x1]=\"axisLine().hx1\"\n [attr.y1]=\"line.y\"\n [attr.x2]=\"axisLine().hx2\"\n [attr.y2]=\"line.y\"\n class=\"lc-scatter-plot__grid-line\"\n />\n @if (showYLabels()) {\n <text\n [attr.x]=\"axisLine().vx1 - 6\"\n [attr.y]=\"line.y\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-scatter-plot__label\"\n >{{ line.label }}</text>\n }\n }\n\n <!-- Grid lines X -->\n @for (line of gridLinesX(); track line.x) {\n <line\n [attr.x1]=\"line.x\"\n [attr.y1]=\"axisLine().vy1\"\n [attr.x2]=\"line.x\"\n [attr.y2]=\"axisLine().vy2\"\n class=\"lc-scatter-plot__grid-line\"\n />\n @if (showXLabels()) {\n <!-- Tick labels sit just under the plot; the axis title takes the last line of\n the bottom padding, so the two never overlap. -->\n <text\n [attr.x]=\"line.x\"\n [attr.y]=\"plotArea().y + plotArea().h + 14\"\n text-anchor=\"middle\"\n class=\"lc-scatter-plot__label\"\n >{{ line.label }}</text>\n }\n }\n\n <!-- Axes -->\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-scatter-plot__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-scatter-plot__axis\" />\n\n <!-- Axis labels -->\n @if (xAxisLabel()) {\n <text [attr.x]=\"plotArea().x + plotArea().w / 2\" [attr.y]=\"height() - 3\" text-anchor=\"middle\" class=\"lc-scatter-plot__axis-label\">{{ xAxisLabel() }}</text>\n }\n @if (yAxisLabel()) {\n <text [attr.x]=\"12\" [attr.y]=\"plotArea().y + plotArea().h / 2\" text-anchor=\"middle\" [attr.transform]=\"'rotate(-90, 12, ' + (plotArea().y + plotArea().h / 2) + ')'\" class=\"lc-scatter-plot__axis-label\">{{ yAxisLabel() }}</text>\n }\n </g>\n\n <!-- Series dots -->\n @for (ser of renderedSeries(); track $index) {\n @for (pt of ser.points; track $index) {\n <circle\n [attr.cx]=\"pt.cx\"\n [attr.cy]=\"pt.cy\"\n [attr.r]=\"pt.r\"\n [attr.fill]=\"ser.color\"\n class=\"lc-scatter-plot__dot\"\n [class.lc-scatter-plot__dot--interactive]=\"interactive()\"\n [attr.tabindex]=\"interactive() ? 0 : null\"\n [attr.role]=\"interactive() ? 'button' : null\"\n [attr.aria-label]=\"interactive() ? pt.ariaLabel : null\"\n (mouseenter)=\"onDotEnter(pt.cx, pt.cy, pt.label)\"\n (mouseleave)=\"onDotLeave()\"\n (focus)=\"onDotEnter(pt.cx, pt.cy, pt.label)\"\n (blur)=\"onDotLeave()\"\n (click)=\"onDotClick(ser.label, pt.orig)\"\n (keydown.enter)=\"onDotClick(ser.label, pt.orig)\"\n (keydown.space)=\"$event.preventDefault(); onDotClick(ser.label, pt.orig)\"\n />\n }\n }\n\n <!-- Tooltip -->\n @if (hoveredPoint(); as hp) {\n <g class=\"lc-scatter-plot__tooltip\" aria-hidden=\"true\">\n <rect\n [attr.x]=\"hp.sx - hp.w / 2\"\n [attr.y]=\"hp.ty\"\n [attr.width]=\"hp.w\"\n [attr.height]=\"tooltipH\"\n rx=\"4\"\n class=\"lc-scatter-plot__tooltip-bg\"\n />\n <text\n [attr.x]=\"hp.sx\"\n [attr.y]=\"hp.ty + tooltipH / 2\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n class=\"lc-scatter-plot__tooltip-text\"\n >{{ hp.label }}</text>\n </g>\n }\n </svg>\n\n @if (legendItems().length) {\n <div class=\"lc-scatter-plot__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-scatter-plot__legend-item\">\n <span class=\"lc-scatter-plot__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-scatter-plot__legend-label\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
275
|
-
styles: [".lc-scatter-plot{display:inline-flex;flex-direction:column;align-items:center;gap:.5rem}.lc-scatter-plot__svg{overflow:visible}.lc-scatter-plot__axis{stroke:var(--color-border);stroke-width:1}.lc-scatter-plot__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-scatter-plot__label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-scatter-plot__axis-label{font-size:.6875rem;fill:var(--color-text-secondary);font-weight:500}.lc-scatter-plot__dot{opacity:.8;transition:opacity .15s,r .15s}.lc-scatter-plot__dot--interactive{cursor:pointer}.lc-scatter-plot__dot:hover{opacity:1;r:6}.lc-scatter-plot__dot:focus-visible{outline:none;opacity:1;stroke:var(--color-text-primary);stroke-width:2}.lc-scatter-plot__tooltip{pointer-events:none}.lc-scatter-plot__tooltip-bg{fill:var(--color-surface-raised);stroke:var(--color-border-strong);stroke-width:1}.lc-scatter-plot__tooltip-text{font-size:.625rem;fill:var(--color-text-primary)}.lc-scatter-plot__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-scatter-plot__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-scatter-plot__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.lc-scatter-plot__legend-label{color:var(--color-text-secondary)}\n"],
|
|
275
|
+
styles: [".lc-scatter-plot{display:inline-flex;flex-direction:column;align-items:center;gap:.5rem}.lc-scatter-plot__svg{overflow:visible}.lc-scatter-plot__axis{stroke:var(--color-border);stroke-width:1}.lc-scatter-plot__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-scatter-plot__label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-scatter-plot__axis-label{font-size:.6875rem;fill:var(--color-text-secondary);font-weight:500}.lc-scatter-plot__dot{opacity:.8;transition:opacity .15s,r .15s}.lc-scatter-plot__dot--interactive{cursor:pointer}.lc-scatter-plot__dot:hover{opacity:1;r:6}.lc-scatter-plot__dot:focus-visible{outline:none;opacity:1;stroke:var(--color-text-primary);stroke-width:2}@media(forced-colors:active){.lc-scatter-plot__dot:focus-visible{stroke:Highlight}}.lc-scatter-plot__tooltip{pointer-events:none}.lc-scatter-plot__tooltip-bg{fill:var(--color-surface-raised);stroke:var(--color-border-strong);stroke-width:1}.lc-scatter-plot__tooltip-text{font-size:.625rem;fill:var(--color-text-primary)}.lc-scatter-plot__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-scatter-plot__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-scatter-plot__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.lc-scatter-plot__legend-label{color:var(--color-text-secondary)}\n"],
|
|
276
276
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
277
277
|
});
|
|
278
278
|
};
|
|
@@ -288,7 +288,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
288
288
|
standalone: true,
|
|
289
289
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
290
290
|
template: "<div class=\"lc-scatter-plot\">\n <svg\n [attr.width]=\"width()\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n [attr.role]=\"interactive() ? 'group' : 'img'\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-scatter-plot__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid, axes and tick labels are decoration for AT; the summary carries the meaning. -->\n <g aria-hidden=\"true\">\n <!-- Grid lines Y -->\n @for (line of gridLinesY(); track line.y) {\n <line\n [attr.x1]=\"axisLine().hx1\"\n [attr.y1]=\"line.y\"\n [attr.x2]=\"axisLine().hx2\"\n [attr.y2]=\"line.y\"\n class=\"lc-scatter-plot__grid-line\"\n />\n @if (showYLabels()) {\n <text\n [attr.x]=\"axisLine().vx1 - 6\"\n [attr.y]=\"line.y\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-scatter-plot__label\"\n >{{ line.label }}</text>\n }\n }\n\n <!-- Grid lines X -->\n @for (line of gridLinesX(); track line.x) {\n <line\n [attr.x1]=\"line.x\"\n [attr.y1]=\"axisLine().vy1\"\n [attr.x2]=\"line.x\"\n [attr.y2]=\"axisLine().vy2\"\n class=\"lc-scatter-plot__grid-line\"\n />\n @if (showXLabels()) {\n <!-- Tick labels sit just under the plot; the axis title takes the last line of\n the bottom padding, so the two never overlap. -->\n <text\n [attr.x]=\"line.x\"\n [attr.y]=\"plotArea().y + plotArea().h + 14\"\n text-anchor=\"middle\"\n class=\"lc-scatter-plot__label\"\n >{{ line.label }}</text>\n }\n }\n\n <!-- Axes -->\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-scatter-plot__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-scatter-plot__axis\" />\n\n <!-- Axis labels -->\n @if (xAxisLabel()) {\n <text [attr.x]=\"plotArea().x + plotArea().w / 2\" [attr.y]=\"height() - 3\" text-anchor=\"middle\" class=\"lc-scatter-plot__axis-label\">{{ xAxisLabel() }}</text>\n }\n @if (yAxisLabel()) {\n <text [attr.x]=\"12\" [attr.y]=\"plotArea().y + plotArea().h / 2\" text-anchor=\"middle\" [attr.transform]=\"'rotate(-90, 12, ' + (plotArea().y + plotArea().h / 2) + ')'\" class=\"lc-scatter-plot__axis-label\">{{ yAxisLabel() }}</text>\n }\n </g>\n\n <!-- Series dots -->\n @for (ser of renderedSeries(); track $index) {\n @for (pt of ser.points; track $index) {\n <circle\n [attr.cx]=\"pt.cx\"\n [attr.cy]=\"pt.cy\"\n [attr.r]=\"pt.r\"\n [attr.fill]=\"ser.color\"\n class=\"lc-scatter-plot__dot\"\n [class.lc-scatter-plot__dot--interactive]=\"interactive()\"\n [attr.tabindex]=\"interactive() ? 0 : null\"\n [attr.role]=\"interactive() ? 'button' : null\"\n [attr.aria-label]=\"interactive() ? pt.ariaLabel : null\"\n (mouseenter)=\"onDotEnter(pt.cx, pt.cy, pt.label)\"\n (mouseleave)=\"onDotLeave()\"\n (focus)=\"onDotEnter(pt.cx, pt.cy, pt.label)\"\n (blur)=\"onDotLeave()\"\n (click)=\"onDotClick(ser.label, pt.orig)\"\n (keydown.enter)=\"onDotClick(ser.label, pt.orig)\"\n (keydown.space)=\"$event.preventDefault(); onDotClick(ser.label, pt.orig)\"\n />\n }\n }\n\n <!-- Tooltip -->\n @if (hoveredPoint(); as hp) {\n <g class=\"lc-scatter-plot__tooltip\" aria-hidden=\"true\">\n <rect\n [attr.x]=\"hp.sx - hp.w / 2\"\n [attr.y]=\"hp.ty\"\n [attr.width]=\"hp.w\"\n [attr.height]=\"tooltipH\"\n rx=\"4\"\n class=\"lc-scatter-plot__tooltip-bg\"\n />\n <text\n [attr.x]=\"hp.sx\"\n [attr.y]=\"hp.ty + tooltipH / 2\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n class=\"lc-scatter-plot__tooltip-text\"\n >{{ hp.label }}</text>\n </g>\n }\n </svg>\n\n @if (legendItems().length) {\n <div class=\"lc-scatter-plot__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-scatter-plot__legend-item\">\n <span class=\"lc-scatter-plot__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-scatter-plot__legend-label\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
291
|
-
styles: [".lc-scatter-plot{display:inline-flex;flex-direction:column;align-items:center;gap:.5rem}.lc-scatter-plot__svg{overflow:visible}.lc-scatter-plot__axis{stroke:var(--color-border);stroke-width:1}.lc-scatter-plot__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-scatter-plot__label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-scatter-plot__axis-label{font-size:.6875rem;fill:var(--color-text-secondary);font-weight:500}.lc-scatter-plot__dot{opacity:.8;transition:opacity .15s,r .15s}.lc-scatter-plot__dot--interactive{cursor:pointer}.lc-scatter-plot__dot:hover{opacity:1;r:6}.lc-scatter-plot__dot:focus-visible{outline:none;opacity:1;stroke:var(--color-text-primary);stroke-width:2}.lc-scatter-plot__tooltip{pointer-events:none}.lc-scatter-plot__tooltip-bg{fill:var(--color-surface-raised);stroke:var(--color-border-strong);stroke-width:1}.lc-scatter-plot__tooltip-text{font-size:.625rem;fill:var(--color-text-primary)}.lc-scatter-plot__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-scatter-plot__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-scatter-plot__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.lc-scatter-plot__legend-label{color:var(--color-text-secondary)}\n"]
|
|
291
|
+
styles: [".lc-scatter-plot{display:inline-flex;flex-direction:column;align-items:center;gap:.5rem}.lc-scatter-plot__svg{overflow:visible}.lc-scatter-plot__axis{stroke:var(--color-border);stroke-width:1}.lc-scatter-plot__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-scatter-plot__label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-scatter-plot__axis-label{font-size:.6875rem;fill:var(--color-text-secondary);font-weight:500}.lc-scatter-plot__dot{opacity:.8;transition:opacity .15s,r .15s}.lc-scatter-plot__dot--interactive{cursor:pointer}.lc-scatter-plot__dot:hover{opacity:1;r:6}.lc-scatter-plot__dot:focus-visible{outline:none;opacity:1;stroke:var(--color-text-primary);stroke-width:2}@media(forced-colors:active){.lc-scatter-plot__dot:focus-visible{stroke:Highlight}}.lc-scatter-plot__tooltip{pointer-events:none}.lc-scatter-plot__tooltip-bg{fill:var(--color-surface-raised);stroke:var(--color-border-strong);stroke-width:1}.lc-scatter-plot__tooltip-text{font-size:.625rem;fill:var(--color-text-primary)}.lc-scatter-plot__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-scatter-plot__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-scatter-plot__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.lc-scatter-plot__legend-label{color:var(--color-text-secondary)}\n"]
|
|
292
292
|
}]
|
|
293
293
|
}],
|
|
294
294
|
propDecorators: {
|