@lifecorner/angular-ui-kit 3.8.0 → 3.10.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-app-shell.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-app-shell.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +39 -6
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-page-header.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-page-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-skeleton.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-skeleton.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +2 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +75 -8
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_horizon.scss +46 -12
- package/src/styles/horizon.shell.spec.ts +67 -0
- package/src/styles/horizon.spec.ts +6 -4
- package/types/lifecorner-angular-ui-kit-app-shell.d.ts +15 -4
- package/types/lifecorner-angular-ui-kit-button.d.ts +30 -0
- package/types/lifecorner-angular-ui-kit-page-header.d.ts +6 -1
- package/types/lifecorner-angular-ui-kit-skeleton.d.ts +7 -1
- package/types/lifecorner-angular-ui-kit-textarea.d.ts +87 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n afterRenderEffect,\n computed,\n forwardRef,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n /**\n * Visual variant of the textarea\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n */\n readonly required = input<boolean>(false);\n\n /**\n * Whether the textarea is readonly\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Number of visible text rows\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Emitted when value changes\n */\n readonly valueChange = output<string>();\n\n /** Per-instance id: links label, error and helper text to the control. */\n readonly textareaId = `lc-textarea-${nextUniqueId++}`;\n readonly errorId = `${this.textareaId}-error`;\n readonly helperId = `${this.textareaId}-helper`;\n\n // Internal state\n readonly value = signal<string>('');\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n this.value.set(value);\n this.onChange(value);\n this.valueChange.emit(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\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 // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}\n @if (required()) {\n <span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" role=\"alert\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n OutputRef,\n PLATFORM_ID,\n afterRenderEffect,\n computed,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n * - Plain form posts without JavaScript: `name`, and a `value` that is\n * prerendered into the native `<textarea>`\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * <lc-textarea label=\"Message\" name=\"message\" [required]=\"true\" [value]=\"draft\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /**\n * Visual variant of the textarea\n * @default 'outline'\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n * @default false\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Show the `*` after the label of a `required` field. `false` keeps the\n * required semantics (`required`, `aria-required`) without the asterisk —\n * for forms where every field is required.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /**\n * Accessible name of the `*` marker, for API parity with `lc-input`.\n * `lc-textarea` hides the `*` from assistive technology, because the native\n * `required` state is announced already and naming the `*` would say\n * \"required\" twice.\n * @default 'required'\n */\n readonly requiredAriaLabel = input<string>('required');\n\n /**\n * Whether the textarea is readonly\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n * @default ''\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n * @default ''\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n * @default ''\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n * @default undefined\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Native `name` of the inner `<textarea>`, so a form posts the text without\n * JavaScript. Unset = no attribute. A `disabled` textarea is not posted\n * (HTML); use `readonly` to post a fixed text.\n * @default undefined\n */\n readonly name = input<string>();\n\n /**\n * Number of visible text rows\n * @default 3\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n * @default undefined\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n * @default false\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n * @default 3\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n * @default undefined\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Current text, two-way bindable (`[value]` / `[(value)]`). It pre-fills the\n * native textarea, in prerendered HTML too, so a server-rendered page can\n * keep a visitor's text. Emits `valueChange` whenever it changes: once per\n * keystroke that changes the text, and when a bound form control or code\n * sets a different text. With a form control, the control's value wins;\n * don't bind both.\n * @default ''\n */\n readonly value = model<string>('');\n\n /**\n * The model's change output as a class property, so code that subscribes to\n * it (`textarea.valueChange.subscribe(…)`, `outputToObservable(…)`) keeps\n * compiling as in 3.9. Template bindings `(valueChange)` use the model.\n */\n readonly valueChange: OutputRef<string> = this.value;\n\n /** Per-instance id: links label, error and helper text to the control. */\n readonly textareaId = `lc-textarea-${nextUniqueId++}`;\n readonly errorId = `${this.textareaId}-error`;\n readonly helperId = `${this.textareaId}-helper`;\n\n /**\n * The native textarea's value. On the server a leading line break gets one\n * extra `\\n`: the HTML parser drops the first line break after `<textarea>`,\n * and the server's DOM serialiser doesn't add the one the HTML\n * serialisation rules require, so the prerendered page would lose it.\n */\n protected readonly renderedValue = computed(() => {\n const value = this.value();\n return this.isServer && /^[\\r\\n]/.test(value) ? `\\n${value}` : value;\n });\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\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 // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}\n @if (required() && requiredIndicator()) {\n <span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" role=\"alert\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/styles/_horizon.scss
CHANGED
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Inputs (custom properties, set on the block or any ancestor):
|
|
28
28
|
* --lc-horizon-land height of the land below the line
|
|
29
|
-
* (default Today's: 112 px phone,
|
|
30
|
-
* from 1024 · 1280 · 1440 · 1920; with the
|
|
31
|
-
* on the land add its height, e.g. 136 px
|
|
29
|
+
* (default Today's: 112 px phone, 72 · 80 · 96 · 128 px
|
|
30
|
+
* from 1024 · 1280 · 1440 · 1920 (3.9, LK-12); with the
|
|
31
|
+
* compact footer on the land add its height, e.g. 136 px
|
|
32
|
+
* on the phone. Inside lc-app-shell with a [shell-footer]
|
|
33
|
+
* the footer's height is added below it by the shell.)
|
|
32
34
|
* --lc-horizon-sun-radius the sun's radius (58 px phone, 70 · 90 · 120 · 166 px)
|
|
33
35
|
* --lc-horizon-sun-x the sun's centre from the block's left edge (72%)
|
|
34
36
|
* --lc-horizon-figure-size the figure's size in .lc-horizon__figure
|
|
@@ -45,6 +47,9 @@
|
|
|
45
47
|
* <lc-character class="lc-horizon__figure" pose="comforts" />
|
|
46
48
|
* </div>
|
|
47
49
|
*
|
|
50
|
+
* In lc-app-shell (3.9, AS-29) the attribute `shell-horizon-figure` alone does
|
|
51
|
+
* the same: the shell projects the element as a direct child of its horizon.
|
|
52
|
+
*
|
|
48
53
|
* Colours: --color-background-low, --color-horizon-line, --color-land-1…3,
|
|
49
54
|
* --color-sun-1…3, --color-sun-ring, --color-sun-halo (theme tokens).
|
|
50
55
|
*/
|
|
@@ -94,27 +99,28 @@
|
|
|
94
99
|
linear-gradient(180deg, transparent, var(--color-background-low))
|
|
95
100
|
0 0 / 100% calc(100% - var(--_lc-horizon-land)) no-repeat;
|
|
96
101
|
|
|
97
|
-
// Desktop steps of Today's horizon (app-shell 12.2
|
|
98
|
-
//
|
|
102
|
+
// Desktop steps of Today's horizon (app-shell 12.2; the land 72 · 80 · 96 ·
|
|
103
|
+
// 128 px since 3.9, LK-12). A page sets its own geometry through
|
|
104
|
+
// --lc-horizon-land / --lc-horizon-sun-radius.
|
|
99
105
|
@media (min-width: 1024px) {
|
|
100
|
-
--_lc-horizon-land: var(--lc-horizon-land,
|
|
106
|
+
--_lc-horizon-land: var(--lc-horizon-land, 72px);
|
|
101
107
|
--_lc-horizon-r: var(--lc-horizon-sun-radius, 70px);
|
|
102
108
|
--_lc-horizon-fig: var(--lc-horizon-figure-size, 96px);
|
|
103
109
|
--_lc-horizon-fig-gap: var(--lc-horizon-figure-gap, 24px);
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
@media (min-width: 1280px) {
|
|
107
|
-
--_lc-horizon-land: var(--lc-horizon-land,
|
|
113
|
+
--_lc-horizon-land: var(--lc-horizon-land, 80px);
|
|
108
114
|
--_lc-horizon-r: var(--lc-horizon-sun-radius, 90px);
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
@media (min-width: 1440px) {
|
|
112
|
-
--_lc-horizon-land: var(--lc-horizon-land,
|
|
118
|
+
--_lc-horizon-land: var(--lc-horizon-land, 96px);
|
|
113
119
|
--_lc-horizon-r: var(--lc-horizon-sun-radius, 120px);
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
@media (min-width: 1920px) {
|
|
117
|
-
--_lc-horizon-land: var(--lc-horizon-land,
|
|
123
|
+
--_lc-horizon-land: var(--lc-horizon-land, 128px);
|
|
118
124
|
--_lc-horizon-r: var(--lc-horizon-sun-radius, 166px);
|
|
119
125
|
--_lc-horizon-fig: var(--lc-horizon-figure-size, 128px);
|
|
120
126
|
}
|
|
@@ -135,7 +141,7 @@
|
|
|
135
141
|
// Edu on the horizon line (3.8, G-4): left of the sun, the feet on the line.
|
|
136
142
|
// Only a block WITH the figure gets the taller minimum; without it the block
|
|
137
143
|
// renders exactly as before.
|
|
138
|
-
|
|
144
|
+
@mixin figure-min-height {
|
|
139
145
|
min-height: calc(var(--_lc-horizon-land) + max(var(--_lc-horizon-r) + 22px, var(--_lc-horizon-fig) + 8px));
|
|
140
146
|
}
|
|
141
147
|
|
|
@@ -143,7 +149,7 @@
|
|
|
143
149
|
// Its right edge sits the gap left of the sun's left edge; the box bottom lies
|
|
144
150
|
// 12.5 % of its size below the line, where the standing poses' feet end
|
|
145
151
|
// (y 87.5 of 100). Px, not rem: at 200 % text the figure keeps its size.
|
|
146
|
-
|
|
152
|
+
@mixin figure {
|
|
147
153
|
--lc-character-size: var(--_lc-horizon-fig);
|
|
148
154
|
|
|
149
155
|
position: absolute;
|
|
@@ -160,8 +166,36 @@
|
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
// A <picture> or an element holding an inline <svg>.
|
|
163
|
-
|
|
169
|
+
@mixin figure-fill {
|
|
164
170
|
display: block;
|
|
165
171
|
width: 100%;
|
|
166
172
|
height: 100%;
|
|
167
173
|
}
|
|
174
|
+
|
|
175
|
+
.lc-horizon:has(> .lc-horizon__figure) {
|
|
176
|
+
@include figure-min-height;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.lc-horizon__figure {
|
|
180
|
+
@include figure;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.lc-horizon__figure > :is(img, svg) {
|
|
184
|
+
@include figure-fill;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// lc-app-shell's figure slot (3.9, AS-29): the attribute alone places the
|
|
188
|
+
// figure, with the same rules and the same specificity as .lc-horizon__figure
|
|
189
|
+
// (`:where` adds none). Here, not in the shell's own stylesheet: the slot's
|
|
190
|
+
// element belongs to the page's view, and `:has()` must see it.
|
|
191
|
+
.lc-horizon:has(> [shell-horizon-figure]) {
|
|
192
|
+
@include figure-min-height;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.lc-horizon > :where([shell-horizon-figure]) {
|
|
196
|
+
@include figure;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.lc-horizon > :where([shell-horizon-figure]) > :is(img, svg) {
|
|
200
|
+
@include figure-fill;
|
|
201
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { compile } from 'sass';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The horizon in 3.9 (request 006): the slimmer land defaults (LK-12, AC-1,
|
|
6
|
+
* AC-3, AC-8) and lc-app-shell's figure slot `[shell-horizon-figure]` (AS-29,
|
|
7
|
+
* AC-6, AC-7), read from the compiled CSS. The 3.8 figure rules are pinned by
|
|
8
|
+
* horizon.figure.spec.ts.
|
|
9
|
+
*/
|
|
10
|
+
const css = '\n' + compile(resolve(__dirname, '_horizon.scss')).css;
|
|
11
|
+
|
|
12
|
+
/** The declarations of the top-level rule whose selector is exactly `selector` (compiled rules start a line). */
|
|
13
|
+
function rule(selector: string): string {
|
|
14
|
+
const start = css.indexOf(`\n${selector} {`);
|
|
15
|
+
expect(start).toBeGreaterThan(-1);
|
|
16
|
+
return css.slice(start + selector.length + 3, css.indexOf('}', start)).trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The declarations of `selector` inside `@media (forced-colors: active)`. */
|
|
20
|
+
function forcedColors(selector: string): string {
|
|
21
|
+
const match = new RegExp(`@media \\(forced-colors: active\\) \\{\\s*${selector.replace(/[.*+?^${}()|[\]\\>]/g, '\\$&')} \\{([^}]*)\\}`).exec(css);
|
|
22
|
+
expect(match).not.toBeNull();
|
|
23
|
+
return (match?.[1] ?? '').trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('.lc-horizon land defaults (LK-12)', () => {
|
|
27
|
+
it('AC-1: 112 px on the phone, 72 · 80 · 96 · 128 px from 1024 · 1280 · 1440 · 1920', () => {
|
|
28
|
+
const lands = [...css.matchAll(/--_lc-horizon-land: var\(--lc-horizon-land, (\d+)px\)/g)].map((m) => Number(m[1]));
|
|
29
|
+
expect(lands).toEqual([112, 72, 80, 96, 128]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('AC-3, AC-8: --lc-horizon-land wins at every width (every step is only its fallback)', () => {
|
|
33
|
+
const steps = [...css.matchAll(/--_lc-horizon-land:\s*([^;]+);/g)].map((m) => m[1]);
|
|
34
|
+
expect(steps).toHaveLength(5);
|
|
35
|
+
for (const step of steps) expect(step).toMatch(/^var\(--lc-horizon-land, \d+px\)$/);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('[shell-horizon-figure] (AS-29)', () => {
|
|
40
|
+
it('places the shell figure with exactly the rules of .lc-horizon__figure', () => {
|
|
41
|
+
expect(rule('.lc-horizon > :where([shell-horizon-figure])')).toBe(rule('.lc-horizon__figure'));
|
|
42
|
+
expect(rule('.lc-horizon > :where([shell-horizon-figure]) > :is(img, svg)')).toBe(rule('.lc-horizon__figure > :is(img, svg)'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('raises the minimum above the line with a figure child, as .lc-horizon__figure does', () => {
|
|
46
|
+
expect(rule('.lc-horizon:has(> [shell-horizon-figure])')).toBe(rule('.lc-horizon:has(> .lc-horizon__figure)'));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('AC-7: sizes and places the figure, never a pointer target, hidden in forced colours', () => {
|
|
50
|
+
const figure = rule('.lc-horizon > :where([shell-horizon-figure])');
|
|
51
|
+
expect(figure).toMatch(/position:\s*absolute/);
|
|
52
|
+
expect(figure).toMatch(/--lc-character-size:\s*var\(--_lc-horizon-fig\)/);
|
|
53
|
+
expect(figure).toMatch(/pointer-events:\s*none/);
|
|
54
|
+
expect(forcedColors('.lc-horizon > :where([shell-horizon-figure])')).toBe(forcedColors('.lc-horizon__figure'));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('keeps the specificity of .lc-horizon__figure: :where() adds none to the attribute', () => {
|
|
58
|
+
// (0,1,0) like `.lc-horizon__figure`, (0,2,0) for the :has() rule: every
|
|
59
|
+
// selector that names the attribute, in order (the third is in forced colours).
|
|
60
|
+
expect(css.match(/^[^{}\n]*shell-horizon-figure[^{}\n]*(?= \{)/gm)).toEqual([
|
|
61
|
+
'.lc-horizon:has(> [shell-horizon-figure])',
|
|
62
|
+
'.lc-horizon > :where([shell-horizon-figure])',
|
|
63
|
+
' .lc-horizon > :where([shell-horizon-figure])',
|
|
64
|
+
'.lc-horizon > :where([shell-horizon-figure]) > :is(img, svg)',
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -49,11 +49,13 @@ describe('.lc-horizon (LK-1)', () => {
|
|
|
49
49
|
expect(base).toMatch(/--_lc-horizon-x:\s*var\(--lc-horizon-sun-x, 72%\)/);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
// The land steps changed in 3.9 (LK-12, request 006): 100 · 120 · 140 · 184 px
|
|
53
|
+
// became 72 · 80 · 96 · 128 px; the sun radii are unchanged.
|
|
52
54
|
it.each([
|
|
53
|
-
[1024, '
|
|
54
|
-
[1280, '
|
|
55
|
-
[1440, '
|
|
56
|
-
[1920, '
|
|
55
|
+
[1024, '72px', '70px'],
|
|
56
|
+
[1280, '80px', '90px'],
|
|
57
|
+
[1440, '96px', '120px'],
|
|
58
|
+
[1920, '128px', '166px'],
|
|
57
59
|
])('uses Today’s desktop geometry from %s px', (width, land, radius) => {
|
|
58
60
|
const step = block(`@media (min-width: ${width}px) {\n .lc-horizon {`);
|
|
59
61
|
expect(step).toContain(`var(--lc-horizon-land, ${land})`);
|
|
@@ -20,8 +20,17 @@ import * as i0 from "@angular/core";
|
|
|
20
20
|
* reserves its height at the end of the page (16 + 72 + 24 px plus the
|
|
21
21
|
* safe-area inset; `--lc-app-shell-bottom-clearance`), so it never covers
|
|
22
22
|
* content. With the horizon, the bar rests on the land instead.
|
|
23
|
-
* - `[shell-footer]` — e.g. a compact `lc-footer`;
|
|
24
|
-
* `horizon`
|
|
23
|
+
* - `[shell-footer]` — e.g. a compact `lc-footer`; after the page, or with
|
|
24
|
+
* `horizon` in a row of its own below the horizon, painted in the land's
|
|
25
|
+
* colour (`--color-land-3`): the land grows by the footer's height, so
|
|
26
|
+
* `--lc-horizon-land` stays the land above the footer and no page overrides
|
|
27
|
+
* it (3.9, LK-12).
|
|
28
|
+
* - `[shell-horizon-figure]` — Edu (or any decorative figure) on the horizon
|
|
29
|
+
* line, left of the sun: `lc-character`, `<img>`, `<picture>` or an element
|
|
30
|
+
* holding an inline `<svg>`. Projected as a direct child of `.lc-horizon`,
|
|
31
|
+
* sized and placed by the horizon (`--lc-horizon-figure-size` / `-gap`).
|
|
32
|
+
* Decorative: the horizon is `aria-hidden`, so give an `<img>` `alt=""` and
|
|
33
|
+
* nothing focusable. Shown only when `horizon` is on (3.9, AS-29).
|
|
25
34
|
*
|
|
26
35
|
* `horizon` closes the page with the "Weite" horizon (`.lc-horizon` from the
|
|
27
36
|
* kit's styles): full-bleed, below the side navigation and the page, filling
|
|
@@ -47,7 +56,9 @@ export declare class AppShellComponent {
|
|
|
47
56
|
/**
|
|
48
57
|
* Close the page with the horizon (`.lc-horizon`): full width, below the
|
|
49
58
|
* side navigation and the page, filling the rest of the screen; the footer
|
|
50
|
-
* and the floating bar rest
|
|
59
|
+
* (in a row of its own, which adds to the land) and the floating bar rest
|
|
60
|
+
* on its land, and `[shell-horizon-figure]` stands on its line.
|
|
61
|
+
* @default false
|
|
51
62
|
*/
|
|
52
63
|
readonly horizon: import("@angular/core").InputSignal<boolean>;
|
|
53
64
|
protected readonly shellClasses: import("@angular/core").Signal<string>;
|
|
@@ -58,7 +69,7 @@ export declare class AppShellComponent {
|
|
|
58
69
|
"required": false;
|
|
59
70
|
"isSignal": true;
|
|
60
71
|
};
|
|
61
|
-
}, {}, never, ["[shell-header]", "[shell-nav]", "*", "[shell-footer]", "[shell-bottom-nav]"], true, never>;
|
|
72
|
+
}, {}, never, ["[shell-header]", "[shell-nav]", "*", "[shell-horizon-figure]", "[shell-footer]", "[shell-bottom-nav]"], true, never>;
|
|
62
73
|
}
|
|
63
74
|
/**
|
|
64
75
|
* A page inside `lc-app-shell`: the `<main>` landmark with the page's content
|
|
@@ -30,6 +30,16 @@ export declare class ButtonComponent {
|
|
|
30
30
|
* @default false
|
|
31
31
|
*/
|
|
32
32
|
readonly inline: import("@angular/core").InputSignal<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Drop the inline padding so a `variant="link"` button sits flush with the
|
|
35
|
+
* content edge: the label starts at the edge, and the button keeps its height
|
|
36
|
+
* and a 44 × 44 px minimum target. At the end edge of a row, set the CSS
|
|
37
|
+
* hook `--lc-btn-flush-align: end` (default `start`) on the host or an
|
|
38
|
+
* ancestor: every line of the label then ends at the edge. Ignored on other
|
|
39
|
+
* variants and with `iconOnly` (3.9, AS-26).
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
readonly flush: import("@angular/core").InputSignal<boolean>;
|
|
33
43
|
readonly clicked: import("@angular/core").OutputEmitterRef<void>;
|
|
34
44
|
readonly focused: import("@angular/core").OutputEmitterRef<void>;
|
|
35
45
|
readonly blurred: import("@angular/core").OutputEmitterRef<void>;
|
|
@@ -102,6 +112,11 @@ export declare class ButtonComponent {
|
|
|
102
112
|
"required": false;
|
|
103
113
|
"isSignal": true;
|
|
104
114
|
};
|
|
115
|
+
"flush": {
|
|
116
|
+
"alias": "flush";
|
|
117
|
+
"required": false;
|
|
118
|
+
"isSignal": true;
|
|
119
|
+
};
|
|
105
120
|
}, {
|
|
106
121
|
"clicked": "clicked";
|
|
107
122
|
"focused": "focused";
|
|
@@ -148,6 +163,16 @@ export declare class ButtonLinkComponent {
|
|
|
148
163
|
* size, weight and line-height). Pair with `variant="link"`.
|
|
149
164
|
*/
|
|
150
165
|
readonly inline: import("@angular/core").InputSignal<boolean>;
|
|
166
|
+
/**
|
|
167
|
+
* Drop the inline padding so a `variant="link"` link sits flush with the
|
|
168
|
+
* content edge: the label starts at the edge, and the link keeps its height
|
|
169
|
+
* and a 44 × 44 px minimum target. At the end edge of a row, set the CSS
|
|
170
|
+
* hook `--lc-btn-flush-align: end` (default `start`) on the link or an
|
|
171
|
+
* ancestor: every line of the label then ends at the edge. Ignored on other
|
|
172
|
+
* variants and with `iconOnly` (3.9, AS-26).
|
|
173
|
+
* @default false
|
|
174
|
+
*/
|
|
175
|
+
readonly flush: import("@angular/core").InputSignal<boolean>;
|
|
151
176
|
/** Classes on the anchor; merged with the consumer's own `class`. */
|
|
152
177
|
protected readonly hostClasses: import("@angular/core").Signal<string>;
|
|
153
178
|
static ɵfac: i0.ɵɵFactoryDeclaration<ButtonLinkComponent, never>;
|
|
@@ -177,5 +202,10 @@ export declare class ButtonLinkComponent {
|
|
|
177
202
|
"required": false;
|
|
178
203
|
"isSignal": true;
|
|
179
204
|
};
|
|
205
|
+
"flush": {
|
|
206
|
+
"alias": "flush";
|
|
207
|
+
"required": false;
|
|
208
|
+
"isSignal": true;
|
|
209
|
+
};
|
|
180
210
|
}, {}, never, ["[slot='icon-before']", "*", "[slot='icon-after']"], true, never>;
|
|
181
211
|
}
|
|
@@ -2,6 +2,9 @@ import * as i0 from "@angular/core";
|
|
|
2
2
|
/**
|
|
3
3
|
* Visual size of the page header.
|
|
4
4
|
* - `compact` — dense data-tool pages and section headings (18 px)
|
|
5
|
+
* - `secondary` — a second title under the page's main one, e.g. the other
|
|
6
|
+
* language of a bilingual page (20 px, `--font-size-xl`;
|
|
7
|
+
* the `compact` spacing; 3.9, AS-25)
|
|
5
8
|
* - `default` — standard app pages (24 px)
|
|
6
9
|
* - `comfortable` — landing-page hero feel (30 px)
|
|
7
10
|
* - `title` — the "Weite" page title: 32 px on phones, 40 / 44 / 48 px
|
|
@@ -11,7 +14,7 @@ import * as i0 from "@angular/core";
|
|
|
11
14
|
* - `auth` — the sign-in and registration `<h1>`: 30 px, 34 px from
|
|
12
15
|
* 1024 px (`--font-size-auth-title`)
|
|
13
16
|
*/
|
|
14
|
-
export type PageHeaderSize = 'compact' | 'default' | 'comfortable' | 'title' | 'display' | 'auth';
|
|
17
|
+
export type PageHeaderSize = 'compact' | 'secondary' | 'default' | 'comfortable' | 'title' | 'display' | 'auth';
|
|
15
18
|
/**
|
|
16
19
|
* Page header — top-of-page title block with optional subtitle,
|
|
17
20
|
* breadcrumbs, badge, description and an actions area.
|
|
@@ -88,6 +91,8 @@ export declare class PageHeaderComponent {
|
|
|
88
91
|
/**
|
|
89
92
|
* Visual size of the page header.
|
|
90
93
|
* - `compact` — dense data-tool pages and section headings (18 px)
|
|
94
|
+
* - `secondary` — a second title under the main one, e.g. the other
|
|
95
|
+
* language of a bilingual page (20 px, compact spacing)
|
|
91
96
|
* - `default` — standard app pages (24 px)
|
|
92
97
|
* - `comfortable` — landing-page hero feel (30 px)
|
|
93
98
|
* - `title` — page title on the responsive title scale, 32–48 px
|
|
@@ -9,6 +9,9 @@ export type SkeletonVariant = 'line' | 'circle' | 'rect';
|
|
|
9
9
|
* - Colours from `--color-skeleton` / `--color-skeleton-highlight`, overridable
|
|
10
10
|
* with the `--lc-skeleton-bg` / `--lc-skeleton-highlight` hooks
|
|
11
11
|
* - Custom width, height, and border radius
|
|
12
|
+
* - `--lc-skeleton-height` hook: the height from CSS (a container query, `cqi`),
|
|
13
|
+
* winning over the `height` input; set it on the `lc-skeleton` element (on a
|
|
14
|
+
* wrapper it reaches every skeleton inside)
|
|
12
15
|
* - Composable for building complex loading layouts
|
|
13
16
|
* - Dark mode compatible
|
|
14
17
|
*
|
|
@@ -24,7 +27,10 @@ export declare class SkeletonComponent {
|
|
|
24
27
|
readonly variant: import("@angular/core").InputSignal<SkeletonVariant>;
|
|
25
28
|
/** Width (CSS value). Defaults to '100%' for line/rect, '40px' for circle. */
|
|
26
29
|
readonly width: import("@angular/core").InputSignal<string | undefined>;
|
|
27
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Height (CSS value). Defaults to '0.875rem' for line, '40px' for circle, '100px' for rect.
|
|
32
|
+
* The `--lc-skeleton-height` custom property, when set, wins over it.
|
|
33
|
+
*/
|
|
28
34
|
readonly height: import("@angular/core").InputSignal<string | undefined>;
|
|
29
35
|
/** Border radius override (CSS value). */
|
|
30
36
|
readonly borderRadius: import("@angular/core").InputSignal<string | undefined>;
|