@lifecorner/angular-ui-kit 3.11.4 → 3.12.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-accordion.mjs +6 -3
- package/fesm2022/lifecorner-angular-ui-kit-accordion.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-calendar.mjs +11 -6
- package/fesm2022/lifecorner-angular-ui-kit-calendar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +2 -0
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +2 -0
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-email-input.mjs +2 -0
- package/fesm2022/lifecorner-angular-ui-kit-email-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +7 -2
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +7 -2
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +4 -2
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs +42 -5
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs +20 -0
- package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-pagination.mjs +5 -2
- package/fesm2022/lifecorner-angular-ui-kit-pagination.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +6 -2
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-stepper.mjs +11 -6
- package/fesm2022/lifecorner-angular-ui-kit-stepper.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +2 -0
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +47 -7
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/package.json +5 -1
- package/types/lifecorner-angular-ui-kit-accordion.d.ts +31 -9
- package/types/lifecorner-angular-ui-kit-alert.d.ts +8 -1
- package/types/lifecorner-angular-ui-kit-calendar.d.ts +1 -0
- package/types/lifecorner-angular-ui-kit-file-upload.d.ts +18 -3
- package/types/lifecorner-angular-ui-kit-gallery.d.ts +17 -2
- package/types/lifecorner-angular-ui-kit-header.d.ts +12 -1
- package/types/lifecorner-angular-ui-kit-input.d.ts +47 -2
- package/types/lifecorner-angular-ui-kit-internal-a11y.d.ts +27 -0
- package/types/lifecorner-angular-ui-kit-pagination.d.ts +15 -2
- package/types/lifecorner-angular-ui-kit-rich-text-editor.d.ts +13 -1
- package/types/lifecorner-angular-ui-kit-stepper.d.ts +16 -5
- package/types/lifecorner-angular-ui-kit-textarea.d.ts +46 -8
|
@@ -2,6 +2,7 @@ import * as i0 from "@angular/core";
|
|
|
2
2
|
import { ChangeDetectionStrategy, Component, computed, forwardRef, input, model, output, signal, viewChild } from "@angular/core";
|
|
3
3
|
import { NG_VALUE_ACCESSOR } from "@angular/forms";
|
|
4
4
|
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
5
|
+
let nextUniqueId = 0;
|
|
5
6
|
var InputComponent = class InputComponent {
|
|
6
7
|
label = input(...ngDevMode ? [void 0, { debugName: "label" }] : /* istanbul ignore next */ []);
|
|
7
8
|
placeholder = input("", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
|
|
@@ -27,11 +28,17 @@ var InputComponent = class InputComponent {
|
|
|
27
28
|
spellcheck = input(...ngDevMode ? [void 0, { debugName: "spellcheck" }] : /* istanbul ignore next */ []);
|
|
28
29
|
enterkeyhint = input(...ngDevMode ? [void 0, { debugName: "enterkeyhint" }] : /* istanbul ignore next */ []);
|
|
29
30
|
requiredAriaLabel = input("required", ...ngDevMode ? [{ debugName: "requiredAriaLabel" }] : /* istanbul ignore next */ []);
|
|
31
|
+
controlId = input(void 0, ...ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []);
|
|
32
|
+
announceError = input(true, ...ngDevMode ? [{ debugName: "announceError" }] : /* istanbul ignore next */ []);
|
|
30
33
|
value = model("", ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
|
|
31
34
|
focused = output();
|
|
32
35
|
blurred = output();
|
|
33
36
|
enterPressed = output();
|
|
34
|
-
|
|
37
|
+
generatedId = `lc-input-${nextUniqueId++}`;
|
|
38
|
+
resolvedId = computed(() => this.controlId() || this.generatedId, ...ngDevMode ? [{ debugName: "resolvedId" }] : /* istanbul ignore next */ []);
|
|
39
|
+
get inputId() {
|
|
40
|
+
return this.resolvedId();
|
|
41
|
+
}
|
|
35
42
|
inputElement = viewChild("inputRef", ...ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ []);
|
|
36
43
|
readonlyValueElement = viewChild("readonlyValueRef", ...ngDevMode ? [{ debugName: "readonlyValueElement" }] : /* istanbul ignore next */ []);
|
|
37
44
|
_formDisabled = signal(false, ...ngDevMode ? [{ debugName: "_formDisabled" }] : /* istanbul ignore next */ []);
|
|
@@ -292,6 +299,20 @@ var InputComponent = class InputComponent {
|
|
|
292
299
|
isRequired: false,
|
|
293
300
|
transformFunction: null
|
|
294
301
|
},
|
|
302
|
+
controlId: {
|
|
303
|
+
classPropertyName: "controlId",
|
|
304
|
+
publicName: "controlId",
|
|
305
|
+
isSignal: true,
|
|
306
|
+
isRequired: false,
|
|
307
|
+
transformFunction: null
|
|
308
|
+
},
|
|
309
|
+
announceError: {
|
|
310
|
+
classPropertyName: "announceError",
|
|
311
|
+
publicName: "announceError",
|
|
312
|
+
isSignal: true,
|
|
313
|
+
isRequired: false,
|
|
314
|
+
transformFunction: null
|
|
315
|
+
},
|
|
295
316
|
value: {
|
|
296
317
|
classPropertyName: "value",
|
|
297
318
|
publicName: "value",
|
|
@@ -325,8 +346,8 @@ var InputComponent = class InputComponent {
|
|
|
325
346
|
isSignal: true
|
|
326
347
|
}],
|
|
327
348
|
ngImport: i0,
|
|
328
|
-
template: "<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}
|
|
329
|
-
styles: [".input-wrapper{position:relative;display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));width:100%}.input-label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);margin-bottom:var(--spacing-1)}.input-label .required-indicator{color:var(--color-error-600);margin-
|
|
349
|
+
template: "<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <!-- Input Container -->\n <div\n class=\"input-container\"\n [class.input-xs]=\"size() === 'xs'\"\n [class.input-sm]=\"size() === 'sm'\"\n [class.input-md]=\"size() === 'md'\"\n [class.input-lg]=\"size() === 'lg'\"\n [class.input-error]=\"hasError\"\n [class.input-disabled]=\"isDisabled\"\n [class.input-readonly]=\"readonly()\"\n [class.input-readonly-wrap]=\"wrapsReadonlyValue()\"\n >\n <!-- Icon Before -->\n @if (iconBefore()) {\n <span class=\"input-icon input-icon-before\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconBefore()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n\n <!-- Read-only value that wraps (SI-10): a read-only textbox named by the label -->\n @if (wrapsReadonlyValue()) {\n <div\n #readonlyValueRef\n class=\"input-field input-readonly-value\"\n role=\"textbox\"\n aria-readonly=\"true\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? inputId + '-label' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n >{{ value() }}</div>\n }\n\n <!-- Input Element (hidden while the read-only value wraps; it keeps value, name and autocomplete) -->\n <input\n #inputRef\n [id]=\"inputId\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [hidden]=\"wrapsReadonlyValue()\"\n [attr.maxlength]=\"maxLength() || null\"\n [attr.autocomplete]=\"autocomplete() ?? null\"\n [attr.name]=\"name() ?? null\"\n [attr.autocapitalize]=\"autocapitalize() ?? null\"\n [attr.spellcheck]=\"spellcheck() === undefined ? null : spellcheck() ? 'true' : 'false'\"\n [attr.enterkeyhint]=\"enterkeyhint() ?? null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-required]=\"required() || undefined\"\n [attr.aria-invalid]=\"hasError || undefined\"\n [attr.aria-describedby]=\"describedBy()\"\n class=\"input-field\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n <!-- Icon After -->\n @if (iconAfter()) {\n <span class=\"input-icon input-icon-after\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconAfter()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n </div>\n\n <!-- Helper Text / Error Message / Character Count -->\n @if (error() || helperText() || (showCharCount() && maxLength())) {\n <div class=\"input-footer\">\n <!-- Error Message -->\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"input-error\" [attr.role]=\"announceError() ? 'alert' : null\">\n {{ error() }}\n </p>\n }\n\n <!-- Helper Text -->\n @if (helperText() && !error()) {\n <p [id]=\"inputId + '-helper'\" class=\"input-helper\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Character Count: always visible; spoken only near the limit (live region below) -->\n @if (showCharCount() && maxLength()) {\n <p class=\"char-count\">\n {{ charCount }} / {{ maxLength() }}\n </p>\n }\n </div>\n }\n\n @if (showCharCount() && maxLength()) {\n <span class=\"input-char-count-live\" aria-live=\"polite\">{{ charCountAnnouncement() }}</span>\n }\n</div>\n",
|
|
350
|
+
styles: [".input-wrapper{position:relative;display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));width:100%}.input-label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);margin-bottom:var(--spacing-1)}.input-label .required-indicator{color:var(--color-error-600);margin-inline-start:var(--spacing-1)}.input-container{position:relative;display:flex;align-items:center;width:100%;background-color:var(--color-surface-2);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease)}.input-container:hover:not(.input-disabled):not(.input-readonly){border-color:var(--color-text-secondary)}.input-container:focus-within{outline:2px solid var(--color-primary-500);outline-offset:0px;border-color:var(--color-primary-500)}.input-container.input-error{border-color:var(--color-error-600)}.input-container.input-error:focus-within{outline-color:var(--color-error-600);border-color:var(--color-error-600)}.input-container.input-disabled{background-color:var(--color-surface-muted);border-color:var(--color-border);cursor:not-allowed;opacity:.6}.input-container.input-readonly{background-color:var(--color-surface-muted)}.input-container.input-readonly-wrap{height:auto;min-height:var(--lc-input-height);align-items:stretch}.input-readonly-value{display:flex;align-items:center;min-width:0;white-space:normal;overflow-wrap:anywhere;-webkit-user-select:text;user-select:text}.input-char-count-live{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.input-field{flex:1;width:100%;font-family:var(--font-family-sans);font-weight:var(--font-weight-regular);line-height:var(--line-height-normal);color:var(--color-text-primary);background:transparent;border:none;outline:none}.input-field::placeholder{color:var(--color-text-secondary)}.input-field:disabled{cursor:not-allowed}.input-field:read-only{cursor:default}.input-field[type=number]::-webkit-inner-spin-button,.input-field[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.input-field[type=number]{-moz-appearance:textfield}.input-icon{display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary);pointer-events:none}.input-icon .icon{width:1.25rem;height:1.25rem}.input-icon.input-icon-before{margin-left:var(--spacing-3);margin-right:var(--spacing-2)}.input-icon.input-icon-after{margin-left:var(--spacing-2);margin-right:var(--spacing-3)}.input-xs{--lc-input-height: var(--size-interactive-xs-height);height:var(--lc-input-height)}.input-xs .input-field{padding:var(--size-interactive-xs-padding);font-size:var(--size-interactive-xs-font-size)}.input-xs .icon{width:1rem;height:1rem}.input-sm{--lc-input-height: var(--size-interactive-sm-height);height:var(--lc-input-height)}.input-sm .input-field{padding:var(--size-interactive-sm-padding);font-size:var(--size-interactive-sm-font-size)}.input-md{--lc-input-height: var(--size-interactive-md-height);height:var(--lc-input-height)}.input-md .input-field{padding:var(--size-interactive-md-padding);font-size:var(--size-interactive-md-font-size)}.input-lg{--lc-input-height: var(--size-interactive-lg-height);height:var(--lc-input-height)}.input-lg .input-field{padding:var(--size-interactive-lg-padding);font-size:var(--size-interactive-lg-font-size)}.input-lg .icon{width:1.5rem;height:1.5rem}.input-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:1.25rem}.input-error{flex:1;font-size:var(--font-size-xs);color:var(--color-error-600);margin:0}.input-helper{flex:1;font-size:var(--font-size-xs);color:var(--color-text-secondary);margin:0}.char-count{font-size:var(--font-size-xs);color:var(--color-text-secondary);white-space:nowrap;margin:0}\n"],
|
|
330
351
|
dependencies: [{
|
|
331
352
|
kind: "component",
|
|
332
353
|
type: IconComponent,
|
|
@@ -360,8 +381,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
360
381
|
useExisting: forwardRef(() => InputComponent),
|
|
361
382
|
multi: true
|
|
362
383
|
}],
|
|
363
|
-
template: "<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}
|
|
364
|
-
styles: [".input-wrapper{position:relative;display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));width:100%}.input-label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);margin-bottom:var(--spacing-1)}.input-label .required-indicator{color:var(--color-error-600);margin-
|
|
384
|
+
template: "<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <!-- Input Container -->\n <div\n class=\"input-container\"\n [class.input-xs]=\"size() === 'xs'\"\n [class.input-sm]=\"size() === 'sm'\"\n [class.input-md]=\"size() === 'md'\"\n [class.input-lg]=\"size() === 'lg'\"\n [class.input-error]=\"hasError\"\n [class.input-disabled]=\"isDisabled\"\n [class.input-readonly]=\"readonly()\"\n [class.input-readonly-wrap]=\"wrapsReadonlyValue()\"\n >\n <!-- Icon Before -->\n @if (iconBefore()) {\n <span class=\"input-icon input-icon-before\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconBefore()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n\n <!-- Read-only value that wraps (SI-10): a read-only textbox named by the label -->\n @if (wrapsReadonlyValue()) {\n <div\n #readonlyValueRef\n class=\"input-field input-readonly-value\"\n role=\"textbox\"\n aria-readonly=\"true\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? inputId + '-label' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n >{{ value() }}</div>\n }\n\n <!-- Input Element (hidden while the read-only value wraps; it keeps value, name and autocomplete) -->\n <input\n #inputRef\n [id]=\"inputId\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [hidden]=\"wrapsReadonlyValue()\"\n [attr.maxlength]=\"maxLength() || null\"\n [attr.autocomplete]=\"autocomplete() ?? null\"\n [attr.name]=\"name() ?? null\"\n [attr.autocapitalize]=\"autocapitalize() ?? null\"\n [attr.spellcheck]=\"spellcheck() === undefined ? null : spellcheck() ? 'true' : 'false'\"\n [attr.enterkeyhint]=\"enterkeyhint() ?? null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-required]=\"required() || undefined\"\n [attr.aria-invalid]=\"hasError || undefined\"\n [attr.aria-describedby]=\"describedBy()\"\n class=\"input-field\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n <!-- Icon After -->\n @if (iconAfter()) {\n <span class=\"input-icon input-icon-after\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconAfter()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n </div>\n\n <!-- Helper Text / Error Message / Character Count -->\n @if (error() || helperText() || (showCharCount() && maxLength())) {\n <div class=\"input-footer\">\n <!-- Error Message -->\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"input-error\" [attr.role]=\"announceError() ? 'alert' : null\">\n {{ error() }}\n </p>\n }\n\n <!-- Helper Text -->\n @if (helperText() && !error()) {\n <p [id]=\"inputId + '-helper'\" class=\"input-helper\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Character Count: always visible; spoken only near the limit (live region below) -->\n @if (showCharCount() && maxLength()) {\n <p class=\"char-count\">\n {{ charCount }} / {{ maxLength() }}\n </p>\n }\n </div>\n }\n\n @if (showCharCount() && maxLength()) {\n <span class=\"input-char-count-live\" aria-live=\"polite\">{{ charCountAnnouncement() }}</span>\n }\n</div>\n",
|
|
385
|
+
styles: [".input-wrapper{position:relative;display:flex;flex-direction:column;gap:var(--lc-density-gap-xs, var(--spacing-1));width:100%}.input-label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);margin-bottom:var(--spacing-1)}.input-label .required-indicator{color:var(--color-error-600);margin-inline-start:var(--spacing-1)}.input-container{position:relative;display:flex;align-items:center;width:100%;background-color:var(--color-surface-2);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease)}.input-container:hover:not(.input-disabled):not(.input-readonly){border-color:var(--color-text-secondary)}.input-container:focus-within{outline:2px solid var(--color-primary-500);outline-offset:0px;border-color:var(--color-primary-500)}.input-container.input-error{border-color:var(--color-error-600)}.input-container.input-error:focus-within{outline-color:var(--color-error-600);border-color:var(--color-error-600)}.input-container.input-disabled{background-color:var(--color-surface-muted);border-color:var(--color-border);cursor:not-allowed;opacity:.6}.input-container.input-readonly{background-color:var(--color-surface-muted)}.input-container.input-readonly-wrap{height:auto;min-height:var(--lc-input-height);align-items:stretch}.input-readonly-value{display:flex;align-items:center;min-width:0;white-space:normal;overflow-wrap:anywhere;-webkit-user-select:text;user-select:text}.input-char-count-live{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.input-field{flex:1;width:100%;font-family:var(--font-family-sans);font-weight:var(--font-weight-regular);line-height:var(--line-height-normal);color:var(--color-text-primary);background:transparent;border:none;outline:none}.input-field::placeholder{color:var(--color-text-secondary)}.input-field:disabled{cursor:not-allowed}.input-field:read-only{cursor:default}.input-field[type=number]::-webkit-inner-spin-button,.input-field[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.input-field[type=number]{-moz-appearance:textfield}.input-icon{display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary);pointer-events:none}.input-icon .icon{width:1.25rem;height:1.25rem}.input-icon.input-icon-before{margin-left:var(--spacing-3);margin-right:var(--spacing-2)}.input-icon.input-icon-after{margin-left:var(--spacing-2);margin-right:var(--spacing-3)}.input-xs{--lc-input-height: var(--size-interactive-xs-height);height:var(--lc-input-height)}.input-xs .input-field{padding:var(--size-interactive-xs-padding);font-size:var(--size-interactive-xs-font-size)}.input-xs .icon{width:1rem;height:1rem}.input-sm{--lc-input-height: var(--size-interactive-sm-height);height:var(--lc-input-height)}.input-sm .input-field{padding:var(--size-interactive-sm-padding);font-size:var(--size-interactive-sm-font-size)}.input-md{--lc-input-height: var(--size-interactive-md-height);height:var(--lc-input-height)}.input-md .input-field{padding:var(--size-interactive-md-padding);font-size:var(--size-interactive-md-font-size)}.input-lg{--lc-input-height: var(--size-interactive-lg-height);height:var(--lc-input-height)}.input-lg .input-field{padding:var(--size-interactive-lg-padding);font-size:var(--size-interactive-lg-font-size)}.input-lg .icon{width:1.5rem;height:1.5rem}.input-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2));min-height:1.25rem}.input-error{flex:1;font-size:var(--font-size-xs);color:var(--color-error-600);margin:0}.input-helper{flex:1;font-size:var(--font-size-xs);color:var(--color-text-secondary);margin:0}.char-count{font-size:var(--font-size-xs);color:var(--color-text-secondary);white-space:nowrap;margin:0}\n"]
|
|
365
386
|
}]
|
|
366
387
|
}],
|
|
367
388
|
propDecorators: {
|
|
@@ -557,6 +578,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
557
578
|
required: false
|
|
558
579
|
}]
|
|
559
580
|
}],
|
|
581
|
+
controlId: [{
|
|
582
|
+
type: i0.Input,
|
|
583
|
+
args: [{
|
|
584
|
+
isSignal: true,
|
|
585
|
+
alias: "controlId",
|
|
586
|
+
required: false
|
|
587
|
+
}]
|
|
588
|
+
}],
|
|
589
|
+
announceError: [{
|
|
590
|
+
type: i0.Input,
|
|
591
|
+
args: [{
|
|
592
|
+
isSignal: true,
|
|
593
|
+
alias: "announceError",
|
|
594
|
+
required: false
|
|
595
|
+
}]
|
|
596
|
+
}],
|
|
560
597
|
value: [{
|
|
561
598
|
type: i0.Input,
|
|
562
599
|
args: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/input/input.component.ts","../../../../libs/angular-ui-kit/src/lib/input/input.component.html"],"sourcesContent":["/* eslint-disable @typescript-eslint/member-ordering */\nimport {\n Component,\n computed,\n input,\n model,\n output,\n signal,\n viewChild,\n ElementRef,\n ChangeDetectionStrategy,\n forwardRef,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\n/** Values of the native `autocapitalize` attribute. */\nexport type InputAutocapitalize = 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';\n\n/** Values of the native `enterkeyhint` attribute (label of the on-screen keyboard's Enter key). */\nexport type InputEnterKeyHint = 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';\n\n/**\n * Input component for single-line text form data entry.\n *\n * Features:\n * - Type variants (text, email, password, number, tel, url)\n * - Size presets (xs, sm, md, lg)\n * - Leading and trailing icon support\n * - Character count display with maxLength (announced only near the limit)\n * - Validation error message display\n * - Helper text support\n * - Disabled and readonly states (read-only values can wrap: `readonlyWrap`)\n * - Input purpose and keyboard hints: `autocomplete`, `name`, `autocapitalize`,\n * `spellcheck`, `enterkeyhint`\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-input label=\"Name\" autocomplete=\"name\" autocapitalize=\"words\" [spellcheck]=\"false\" [(ngModel)]=\"name\" />\n * ```\n */\n@Component({\n selector: 'lc-input',\n imports: [IconComponent],\n templateUrl: './input.component.html',\n styleUrl: './input.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => InputComponent),\n multi: true,\n },\n ],\n})\nexport class InputComponent implements ControlValueAccessor {\n // Input Properties\n /**\n * Input label (displayed above input)\n */\n readonly label = input<string>();\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Input type\n * @default 'text'\n */\n readonly type = input<'text' | 'email' | 'password' | 'number' | 'tel' | 'url'>('text');\n\n /**\n * Size of the input\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the input is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Read-only values wrap onto more lines instead of being clipped (WCAG 1.4.10):\n * the value is shown as text (a read-only textbox named by the label), and a\n * hidden native input keeps the value, `name` and `autocomplete` for forms and\n * password managers. Only applies while `readonly` is set.\n * @default false\n */\n readonly readonlyWrap = input<boolean>(false);\n\n /**\n * Whether the input 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 * Error message to display (if validation fails)\n */\n readonly error = input<string>();\n\n /**\n * Helper text to display below input\n */\n readonly helperText = input<string>();\n\n /**\n * Icon to display before input text (Tabler icon name)\n * @example 'envelope'\n */\n readonly iconBefore = input<string>();\n\n /**\n * Icon to display after input text (Tabler icon name)\n * @example 'eye'\n */\n readonly iconAfter = input<string>();\n\n /**\n * Maximum number of characters\n */\n readonly maxLength = input<number>();\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharCount = input<boolean>(false);\n\n /**\n * Screen-reader announcement of the character count near the limit;\n * `{remaining}` is replaced.\n * @default 'Characters remaining: {remaining}'\n */\n readonly charCountRemainingLabel = input('Characters remaining: {remaining}');\n\n /**\n * Remaining characters from which the count is announced (the visible count\n * is always shown). Unset = the last 10 % of `maxLength` (at least 1).\n */\n readonly charCountAnnounceThreshold = input<number>();\n\n /**\n * ARIA label for accessibility\n */\n readonly ariaLabel = input<string>();\n\n /**\n * Native `autocomplete` — the input purpose (WCAG 1.3.5), so browsers and\n * password managers fill the field: `name`, `email`, `username`,\n * `username webauthn` (offers passkeys in the field), `current-password`,\n * `new-password`, `one-time-code`, `tel`, `off` … Unset = no attribute.\n */\n readonly autocomplete = input<string>();\n\n /** Native `name` of the inner `<input>` (form posts, password managers). Unset = no attribute. */\n readonly name = input<string>();\n\n /**\n * Native `autocapitalize` for on-screen keyboards: `off` for addresses,\n * user names and codes, `words` for names. Unset = no attribute.\n */\n readonly autocapitalize = input<InputAutocapitalize>();\n\n /** Native `spellcheck`: `false` for names, addresses and codes. Unset = no attribute. */\n readonly spellcheck = input<boolean>();\n\n /** Native `enterkeyhint`: the label of the on-screen keyboard's Enter key. Unset = no attribute. */\n readonly enterkeyhint = input<InputEnterKeyHint>();\n\n /** Accessible name of the `*` marker shown for `required` fields. */\n readonly requiredAriaLabel = input('required');\n\n /**\n * Current value (two-way bindable: `[value]` / `[(value)]`). Emits\n * `valueChange` whenever it changes — once per user keystroke, on `clear()`\n * and when a bound form control writes a value.\n */\n readonly value = model<string>('');\n\n // Output Events\n /**\n * Emitted when input receives focus\n */\n readonly focused = output<void>();\n\n /**\n * Emitted when input loses focus\n */\n readonly blurred = output<void>();\n\n /**\n * Emitted when Enter key is pressed\n */\n readonly enterPressed = output<void>();\n\n // Internal state\n readonly inputId = `lc-input-${Math.random().toString(36).substr(2, 9)}`;\n\n // ViewChild reference\n private readonly inputElement = viewChild<ElementRef<HTMLInputElement>>('inputRef');\n private readonly readonlyValueElement = viewChild<ElementRef<HTMLElement>>('readonlyValueRef');\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly _formDisabled = signal<boolean>(false);\n\n // ControlValueAccessor callbacks\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n /** The read-only value is shown as wrapping text (SI-10). */\n protected readonly wrapsReadonlyValue = computed(() => this.readonly() && this.readonlyWrap());\n\n /** `aria-describedby`: only the texts that are rendered (the error replaces the helper text). */\n protected readonly describedBy = computed(() => {\n if (this.error()) return `${this.inputId}-error`;\n if (this.helperText()) return `${this.inputId}-helper`;\n return null;\n });\n\n /** Screen-reader text of the character count: empty until the limit is near. */\n protected readonly charCountAnnouncement = computed(() => {\n const max = this.maxLength();\n if (!this.showCharCount() || !max) return '';\n const remaining = Math.max(0, max - this.value().length);\n const threshold = this.charCountAnnounceThreshold() ?? Math.max(1, Math.ceil(max * 0.1));\n if (remaining > threshold) return '';\n return this.charCountRemainingLabel().replace('{remaining}', String(remaining));\n });\n\n /**\n * Computed disabled state from both input and form control\n */\n protected get isDisabled(): boolean {\n return this.disabled() || this._formDisabled();\n }\n\n /**\n * Gets current character count\n */\n protected get charCount(): number {\n return this.value().length;\n }\n\n /**\n * Determines if error state should be shown\n */\n protected get hasError(): boolean {\n return !!this.error();\n }\n\n /**\n * Programmatically focus the input (the visible value while `readonlyWrap` applies)\n */\n public focus(): void {\n if (this.wrapsReadonlyValue()) {\n this.readonlyValueElement()?.nativeElement.focus();\n return;\n }\n this.inputElement()?.nativeElement.focus();\n }\n\n /**\n * Programmatically select input text\n */\n public select(): void {\n const shown = this.wrapsReadonlyValue() ? this.readonlyValueElement()?.nativeElement : undefined;\n if (shown) {\n const selection = shown.ownerDocument.getSelection();\n const range = shown.ownerDocument.createRange();\n range.selectNodeContents(shown);\n selection?.removeAllRanges();\n selection?.addRange(range);\n return;\n }\n this.inputElement()?.nativeElement.select();\n }\n\n /**\n * Clear the input value\n */\n public clear(): void {\n this.value.set('');\n this.onChange('');\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null): 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 /**\n * Handles input value changes\n */\n protected onInput(event: Event): void {\n const target = event.target as HTMLInputElement;\n const newValue = target.value;\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(newValue);\n this.onChange(newValue);\n }\n\n /**\n * Handles input focus\n */\n protected onFocus(): void {\n this.focused.emit();\n }\n\n /**\n * Handles input blur\n */\n protected onBlur(): void {\n this.onTouched();\n this.blurred.emit();\n }\n\n /**\n * Handles keydown events\n */\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n this.enterPressed.emit();\n }\n }\n}\n","<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}\n @if (required() && requiredIndicator()) {\n <span class=\"required-indicator\" [attr.aria-label]=\"requiredAriaLabel()\">*</span>\n }\n </label>\n }\n\n <!-- Input Container -->\n <div\n class=\"input-container\"\n [class.input-xs]=\"size() === 'xs'\"\n [class.input-sm]=\"size() === 'sm'\"\n [class.input-md]=\"size() === 'md'\"\n [class.input-lg]=\"size() === 'lg'\"\n [class.input-error]=\"hasError\"\n [class.input-disabled]=\"isDisabled\"\n [class.input-readonly]=\"readonly()\"\n [class.input-readonly-wrap]=\"wrapsReadonlyValue()\"\n >\n <!-- Icon Before -->\n @if (iconBefore()) {\n <span class=\"input-icon input-icon-before\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconBefore()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n\n <!-- Read-only value that wraps (SI-10): a read-only textbox named by the label -->\n @if (wrapsReadonlyValue()) {\n <div\n #readonlyValueRef\n class=\"input-field input-readonly-value\"\n role=\"textbox\"\n aria-readonly=\"true\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? inputId + '-label' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n >{{ value() }}</div>\n }\n\n <!-- Input Element (hidden while the read-only value wraps; it keeps value, name and autocomplete) -->\n <input\n #inputRef\n [id]=\"inputId\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [hidden]=\"wrapsReadonlyValue()\"\n [attr.maxlength]=\"maxLength() || null\"\n [attr.autocomplete]=\"autocomplete() ?? null\"\n [attr.name]=\"name() ?? null\"\n [attr.autocapitalize]=\"autocapitalize() ?? null\"\n [attr.spellcheck]=\"spellcheck() === undefined ? null : spellcheck() ? 'true' : 'false'\"\n [attr.enterkeyhint]=\"enterkeyhint() ?? null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-required]=\"required() || undefined\"\n [attr.aria-invalid]=\"hasError || undefined\"\n [attr.aria-describedby]=\"describedBy()\"\n class=\"input-field\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n <!-- Icon After -->\n @if (iconAfter()) {\n <span class=\"input-icon input-icon-after\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconAfter()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n </div>\n\n <!-- Helper Text / Error Message / Character Count -->\n @if (error() || helperText() || (showCharCount() && maxLength())) {\n <div class=\"input-footer\">\n <!-- Error Message -->\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"input-error\" role=\"alert\">\n {{ error() }}\n </p>\n }\n\n <!-- Helper Text -->\n @if (helperText() && !error()) {\n <p [id]=\"inputId + '-helper'\" class=\"input-helper\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Character Count: always visible; spoken only near the limit (live region below) -->\n @if (showCharCount() && maxLength()) {\n <p class=\"char-count\">\n {{ charCount }} / {{ maxLength() }}\n </p>\n }\n </div>\n }\n\n @if (showCharCount() && maxLength()) {\n <span class=\"input-char-count-live\" aria-live=\"polite\">{{ charCountAnnouncement() }}</span>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-input.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/input/input.component.ts","../../../../libs/angular-ui-kit/src/lib/input/input.component.html"],"sourcesContent":["/* eslint-disable @typescript-eslint/member-ordering */\nimport {\n Component,\n computed,\n input,\n model,\n output,\n signal,\n viewChild,\n ElementRef,\n ChangeDetectionStrategy,\n forwardRef,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\n/** Values of the native `autocapitalize` attribute. */\nexport type InputAutocapitalize = 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';\n\n/** Values of the native `enterkeyhint` attribute (label of the on-screen keyboard's Enter key). */\nexport type InputEnterKeyHint = 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Input component for single-line text form data entry.\n *\n * Features:\n * - Type variants (text, email, password, number, tel, url)\n * - Size presets (xs, sm, md, lg)\n * - Leading and trailing icon support\n * - Character count display with maxLength (announced only near the limit)\n * - Validation error message display\n * - Helper text support\n * - Disabled and readonly states (read-only values can wrap: `readonlyWrap`)\n * - Input purpose and keyboard hints: `autocomplete`, `name`, `autocapitalize`,\n * `spellcheck`, `enterkeyhint`\n * - ControlValueAccessor integration for reactive forms\n *\n * @example\n * ```html\n * <lc-input label=\"Name\" autocomplete=\"name\" autocapitalize=\"words\" [spellcheck]=\"false\" [(ngModel)]=\"name\" />\n * ```\n */\n@Component({\n selector: 'lc-input',\n imports: [IconComponent],\n templateUrl: './input.component.html',\n styleUrl: './input.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => InputComponent),\n multi: true,\n },\n ],\n})\nexport class InputComponent implements ControlValueAccessor {\n // Input Properties\n /**\n * Input label (displayed above input)\n */\n readonly label = input<string>();\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Input type\n * @default 'text'\n */\n readonly type = input<'text' | 'email' | 'password' | 'number' | 'tel' | 'url'>('text');\n\n /**\n * Size of the input\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the input is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Read-only values wrap onto more lines instead of being clipped (WCAG 1.4.10):\n * the value is shown as text (a read-only textbox named by the label), and a\n * hidden native input keeps the value, `name` and `autocomplete` for forms and\n * password managers. Only applies while `readonly` is set.\n * @default false\n */\n readonly readonlyWrap = input<boolean>(false);\n\n /**\n * Whether the input 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 * Error message to display (if validation fails)\n */\n readonly error = input<string>();\n\n /**\n * Helper text to display below input\n */\n readonly helperText = input<string>();\n\n /**\n * Icon to display before input text (Tabler icon name)\n * @example 'envelope'\n */\n readonly iconBefore = input<string>();\n\n /**\n * Icon to display after input text (Tabler icon name)\n * @example 'eye'\n */\n readonly iconAfter = input<string>();\n\n /**\n * Maximum number of characters\n */\n readonly maxLength = input<number>();\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharCount = input<boolean>(false);\n\n /**\n * Screen-reader announcement of the character count near the limit;\n * `{remaining}` is replaced.\n * @default 'Characters remaining: {remaining}'\n */\n readonly charCountRemainingLabel = input('Characters remaining: {remaining}');\n\n /**\n * Remaining characters from which the count is announced (the visible count\n * is always shown). Unset = the last 10 % of `maxLength` (at least 1).\n */\n readonly charCountAnnounceThreshold = input<number>();\n\n /**\n * ARIA label for accessibility\n */\n readonly ariaLabel = input<string>();\n\n /**\n * Native `autocomplete` — the input purpose (WCAG 1.3.5), so browsers and\n * password managers fill the field: `name`, `email`, `username`,\n * `username webauthn` (offers passkeys in the field), `current-password`,\n * `new-password`, `one-time-code`, `tel`, `off` … Unset = no attribute.\n */\n readonly autocomplete = input<string>();\n\n /** Native `name` of the inner `<input>` (form posts, password managers). Unset = no attribute. */\n readonly name = input<string>();\n\n /**\n * Native `autocapitalize` for on-screen keyboards: `off` for addresses,\n * user names and codes, `words` for names. Unset = no attribute.\n */\n readonly autocapitalize = input<InputAutocapitalize>();\n\n /** Native `spellcheck`: `false` for names, addresses and codes. Unset = no attribute. */\n readonly spellcheck = input<boolean>();\n\n /** Native `enterkeyhint`: the label of the on-screen keyboard's Enter key. Unset = no attribute. */\n readonly enterkeyhint = input<InputEnterKeyHint>();\n\n /**\n * Deprecated, no effect since 3.12. The `*` is decorative and hidden from\n * assistive technology, because the native `required` state is announced\n * already, in the screen reader's language; a name on the `*` made the field\n * say \"required\" twice. Kept so existing bindings compile.\n * @deprecated No effect since 3.12: the native `required` state is announced instead.\n * @default 'required'\n */\n readonly requiredAriaLabel = input('required');\n\n /**\n * Id of the native `<input>`. The label's `for`, the error (`<id>-error`),\n * the helper text (`<id>-helper`), the label id (`<id>-label`) and\n * `aria-describedby` derive from it, so an error summary can link to the\n * field (`<a href=\"#contact-email\">`) and prerendered HTML is the same in\n * every build. Unique in the document, no spaces. Unset (or empty) = a\n * generated `lc-input-<n>`, which follows creation order (on the server it\n * keeps counting across prerendered routes): set it on prerendered pages.\n * @default generated (lc-input-<n>)\n */\n readonly controlId = input<string | undefined>(undefined);\n\n /**\n * Announce the error message the moment it appears (`role=\"alert\"` on the\n * message). Set `false` when an `lc-alert` error summary above the form\n * already announces the errors (a page re-rendered after a failed submit,\n * e.g. 422), so they aren't read twice. The message stays visible, stays\n * linked through `aria-describedby` (read with the field), and the control\n * keeps `aria-invalid`.\n * @default true\n */\n readonly announceError = input<boolean>(true);\n\n /**\n * Current value (two-way bindable: `[value]` / `[(value)]`). Emits\n * `valueChange` whenever it changes — once per user keystroke, on `clear()`\n * and when a bound form control writes a value.\n */\n readonly value = model<string>('');\n\n // Output Events\n /**\n * Emitted when input receives focus\n */\n readonly focused = output<void>();\n\n /**\n * Emitted when input loses focus\n */\n readonly blurred = output<void>();\n\n /**\n * Emitted when Enter key is pressed\n */\n readonly enterPressed = output<void>();\n\n // Internal state\n /** Generated id, taken once per instance (also with `controlId`), so later ids don't shift. */\n private readonly generatedId = `lc-input-${nextUniqueId++}`;\n\n private readonly resolvedId = computed(() => this.controlId() || this.generatedId);\n\n /**\n * Resolved id of the native control: `controlId`, or the generated\n * `lc-input-<n>`. Read-only, same meaning as before 3.12.\n */\n get inputId(): string {\n return this.resolvedId();\n }\n\n // ViewChild reference\n private readonly inputElement = viewChild<ElementRef<HTMLInputElement>>('inputRef');\n private readonly readonlyValueElement = viewChild<ElementRef<HTMLElement>>('readonlyValueRef');\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly _formDisabled = signal<boolean>(false);\n\n // ControlValueAccessor callbacks\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n /** The read-only value is shown as wrapping text (SI-10). */\n protected readonly wrapsReadonlyValue = computed(() => this.readonly() && this.readonlyWrap());\n\n /** `aria-describedby`: only the texts that are rendered (the error replaces the helper text). */\n protected readonly describedBy = computed(() => {\n if (this.error()) return `${this.inputId}-error`;\n if (this.helperText()) return `${this.inputId}-helper`;\n return null;\n });\n\n /** Screen-reader text of the character count: empty until the limit is near. */\n protected readonly charCountAnnouncement = computed(() => {\n const max = this.maxLength();\n if (!this.showCharCount() || !max) return '';\n const remaining = Math.max(0, max - this.value().length);\n const threshold = this.charCountAnnounceThreshold() ?? Math.max(1, Math.ceil(max * 0.1));\n if (remaining > threshold) return '';\n return this.charCountRemainingLabel().replace('{remaining}', String(remaining));\n });\n\n /**\n * Computed disabled state from both input and form control\n */\n protected get isDisabled(): boolean {\n return this.disabled() || this._formDisabled();\n }\n\n /**\n * Gets current character count\n */\n protected get charCount(): number {\n return this.value().length;\n }\n\n /**\n * Determines if error state should be shown\n */\n protected get hasError(): boolean {\n return !!this.error();\n }\n\n /**\n * Programmatically focus the input (the visible value while `readonlyWrap` applies)\n */\n public focus(): void {\n if (this.wrapsReadonlyValue()) {\n this.readonlyValueElement()?.nativeElement.focus();\n return;\n }\n this.inputElement()?.nativeElement.focus();\n }\n\n /**\n * Programmatically select input text\n */\n public select(): void {\n const shown = this.wrapsReadonlyValue() ? this.readonlyValueElement()?.nativeElement : undefined;\n if (shown) {\n const selection = shown.ownerDocument.getSelection();\n const range = shown.ownerDocument.createRange();\n range.selectNodeContents(shown);\n selection?.removeAllRanges();\n selection?.addRange(range);\n return;\n }\n this.inputElement()?.nativeElement.select();\n }\n\n /**\n * Clear the input value\n */\n public clear(): void {\n this.value.set('');\n this.onChange('');\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null): 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 /**\n * Handles input value changes\n */\n protected onInput(event: Event): void {\n const target = event.target as HTMLInputElement;\n const newValue = target.value;\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(newValue);\n this.onChange(newValue);\n }\n\n /**\n * Handles input focus\n */\n protected onFocus(): void {\n this.focused.emit();\n }\n\n /**\n * Handles input blur\n */\n protected onBlur(): void {\n this.onTouched();\n this.blurred.emit();\n }\n\n /**\n * Handles keydown events\n */\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n this.enterPressed.emit();\n }\n }\n}\n","<div class=\"input-wrapper\">\n <!-- Label -->\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [id]=\"inputId + '-label'\" [attr.for]=\"wrapsReadonlyValue() ? null : inputId\" class=\"input-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <!-- Input Container -->\n <div\n class=\"input-container\"\n [class.input-xs]=\"size() === 'xs'\"\n [class.input-sm]=\"size() === 'sm'\"\n [class.input-md]=\"size() === 'md'\"\n [class.input-lg]=\"size() === 'lg'\"\n [class.input-error]=\"hasError\"\n [class.input-disabled]=\"isDisabled\"\n [class.input-readonly]=\"readonly()\"\n [class.input-readonly-wrap]=\"wrapsReadonlyValue()\"\n >\n <!-- Icon Before -->\n @if (iconBefore()) {\n <span class=\"input-icon input-icon-before\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconBefore()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n\n <!-- Read-only value that wraps (SI-10): a read-only textbox named by the label -->\n @if (wrapsReadonlyValue()) {\n <div\n #readonlyValueRef\n class=\"input-field input-readonly-value\"\n role=\"textbox\"\n aria-readonly=\"true\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? inputId + '-label' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n >{{ value() }}</div>\n }\n\n <!-- Input Element (hidden while the read-only value wraps; it keeps value, name and autocomplete) -->\n <input\n #inputRef\n [id]=\"inputId\"\n [type]=\"type()\"\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [hidden]=\"wrapsReadonlyValue()\"\n [attr.maxlength]=\"maxLength() || null\"\n [attr.autocomplete]=\"autocomplete() ?? null\"\n [attr.name]=\"name() ?? null\"\n [attr.autocapitalize]=\"autocapitalize() ?? null\"\n [attr.spellcheck]=\"spellcheck() === undefined ? null : spellcheck() ? 'true' : 'false'\"\n [attr.enterkeyhint]=\"enterkeyhint() ?? null\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-required]=\"required() || undefined\"\n [attr.aria-invalid]=\"hasError || undefined\"\n [attr.aria-describedby]=\"describedBy()\"\n class=\"input-field\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n <!-- Icon After -->\n @if (iconAfter()) {\n <span class=\"input-icon input-icon-after\" aria-hidden=\"true\">\n <lc-icon [name]=\"iconAfter()!\" size=\"sm\" [decorative]=\"true\" />\n </span>\n }\n </div>\n\n <!-- Helper Text / Error Message / Character Count -->\n @if (error() || helperText() || (showCharCount() && maxLength())) {\n <div class=\"input-footer\">\n <!-- Error Message -->\n @if (error()) {\n <p [id]=\"inputId + '-error'\" class=\"input-error\" [attr.role]=\"announceError() ? 'alert' : null\">\n {{ error() }}\n </p>\n }\n\n <!-- Helper Text -->\n @if (helperText() && !error()) {\n <p [id]=\"inputId + '-helper'\" class=\"input-helper\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Character Count: always visible; spoken only near the limit (live region below) -->\n @if (showCharCount() && maxLength()) {\n <p class=\"char-count\">\n {{ charCount }} / {{ maxLength() }}\n </p>\n }\n </div>\n }\n\n @if (showCharCount() && maxLength()) {\n <span class=\"input-char-count-live\" aria-live=\"polite\">{{ charCountAnnouncement() }}</span>\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function labelWords(s) {
|
|
2
|
+
return s.normalize("NFKD").replace(/\([^()]*\)/g, " ").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
3
|
+
}
|
|
4
|
+
function containsWords(haystack, needle) {
|
|
5
|
+
return needle.length > 0 && haystack.some((_, i) => needle.every((word, k) => haystack[i + k] === word));
|
|
6
|
+
}
|
|
7
|
+
function nameWithText(label, text) {
|
|
8
|
+
const t = text.replace(/\s+/g, " ").trim();
|
|
9
|
+
const l = label.trim();
|
|
10
|
+
if (!/[\p{L}\p{N}]/u.test(t) || Array.from(t).length === 1 && !/\p{N}/u.test(t)) return l;
|
|
11
|
+
const labelW = labelWords(l);
|
|
12
|
+
const textW = labelWords(t);
|
|
13
|
+
if (containsWords(labelW, textW)) return l;
|
|
14
|
+
if (l === "") return t;
|
|
15
|
+
if (containsWords(textW, labelW)) return t;
|
|
16
|
+
return `${l}, ${t}`;
|
|
17
|
+
}
|
|
18
|
+
export { containsWords, labelWords, nameWithText };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=lifecorner-angular-ui-kit-internal-a11y.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-internal-a11y.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/internal/a11y/label-in-name.ts"],"sourcesContent":["/**\n * Accessible names that contain the visible text (WCAG 2.5.3 Label in Name,\n * AS-33). The containment test follows axe-core 4.14's\n * `label-content-name-mismatch`: both strings are compatibility-decomposed,\n * parentheticals are dropped, the rest is lower-cased and split into words of\n * letters and digits, and the visible text's words must occur as one\n * contiguous run in the name's words.\n */\n\n/**\n * The words of `s` as axe-core 4.14 compares them for Label in Name.\n * `toLowerCase`, not `toLocaleLowerCase`: server and browser must agree.\n */\nexport function labelWords(s: string): string[] {\n return s\n .normalize('NFKD')\n .replace(/\\([^()]*\\)/g, ' ')\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/** Whether `needle` occurs in `haystack` as one contiguous run of words. */\nexport function containsWords(haystack: readonly string[], needle: readonly string[]): boolean {\n return needle.length > 0 && haystack.some((_, i) => needle.every((word, k) => haystack[i + k] === word));\n}\n\n/**\n * Accessible name that contains the visible `text` (WCAG 2.5.3): `label`,\n * then `, text`, unless one already contains the other.\n *\n * - Text without letters or digits, or one non-digit character (a symbol or\n * an icon glyph, which axe reports as \"incomplete\"): `label`.\n * - The label already contains the text: `label`, unchanged.\n * - An empty label: `text`.\n * - The text contains the label: `text`, so nothing is read twice.\n * - Otherwise `label, text`.\n */\nexport function nameWithText(label: string, text: string): string {\n const t = text.replace(/\\s+/g, ' ').trim();\n const l = label.trim();\n if (!/[\\p{L}\\p{N}]/u.test(t) || (Array.from(t).length === 1 && !/\\p{N}/u.test(t))) return l;\n const labelW = labelWords(l);\n const textW = labelWords(t);\n if (containsWords(labelW, textW)) return l;\n if (l === '') return t;\n if (containsWords(textW, labelW)) return t;\n return `${l}, ${t}`;\n}\n"],"mappings":"AAaA,SAAgB,WAAW,GAAS;CAClC,OAAO,EACJ,UAAU,MAAM,CAAC,CACjB,QAAQ,eAAe,GAAG,CAAC,CAC3B,YAAW,CAAE,CACb,MAAM,iBAAiB,CAAC,CACxB,OAAO,OAAO;AACnB;AAGA,SAAgB,cAAc,UAA6B,QAAyB;CAClF,OAAO,OAAO,SAAS,KAAK,SAAS,MAAM,GAAG,MAAM,OAAO,OAAO,MAAM,MAAM,SAAS,IAAI,OAAO,IAAI,CAAC;AACzG;AAaA,SAAgB,aAAa,OAAe,MAAY;CACtD,MAAM,IAAI,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAI;CACxC,MAAM,IAAI,MAAM,KAAI;CACpB,IAAI,CAAC,gBAAgB,KAAK,CAAC,KAAM,MAAM,KAAK,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,SAAS,KAAK,CAAC,GAAI,OAAO;CAC1F,MAAM,SAAS,WAAW,CAAC;CAC3B,MAAM,QAAQ,WAAW,CAAC;CAC1B,IAAI,cAAc,QAAQ,KAAK,GAAG,OAAO;CACzC,IAAI,MAAM,IAAI,OAAO;CACrB,IAAI,cAAc,OAAO,MAAM,GAAG,OAAO;CACzC,OAAO,GAAG,EAAC,IAAK;AAClB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, computed, input, output } from "@angular/core";
|
|
3
3
|
import { NgClass } from "@angular/common";
|
|
4
|
+
import { nameWithText } from "@lifecorner/angular-ui-kit/internal/a11y";
|
|
4
5
|
var PaginationComponent = class PaginationComponent {
|
|
5
6
|
currentPage = input(1, ...ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []);
|
|
6
7
|
totalItems = input(0, ...ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []);
|
|
@@ -16,6 +17,8 @@ var PaginationComponent = class PaginationComponent {
|
|
|
16
17
|
pageAriaLabel = input("Page {page}", ...ngDevMode ? [{ debugName: "pageAriaLabel" }] : /* istanbul ignore next */ []);
|
|
17
18
|
infoText = input("Showing {first} to {last} of {total} items", ...ngDevMode ? [{ debugName: "infoText" }] : /* istanbul ignore next */ []);
|
|
18
19
|
pageChange = output();
|
|
20
|
+
previousButtonAriaLabel = computed(() => nameWithText(this.previousAriaLabel(), this.previousLabel()), ...ngDevMode ? [{ debugName: "previousButtonAriaLabel" }] : /* istanbul ignore next */ []);
|
|
21
|
+
nextButtonAriaLabel = computed(() => nameWithText(this.nextAriaLabel(), this.nextLabel()), ...ngDevMode ? [{ debugName: "nextButtonAriaLabel" }] : /* istanbul ignore next */ []);
|
|
19
22
|
totalPages = computed(() => {
|
|
20
23
|
const total = this.totalItems();
|
|
21
24
|
const size = this.pageSize();
|
|
@@ -200,7 +203,7 @@ var PaginationComponent = class PaginationComponent {
|
|
|
200
203
|
},
|
|
201
204
|
outputs: { pageChange: "pageChange" },
|
|
202
205
|
ngImport: i0,
|
|
203
|
-
template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"
|
|
206
|
+
template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n …\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
|
|
204
207
|
styles: [".lc-pagination{display:flex;align-items:center;justify-content:center;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-pagination--sm{font-size:var(--font-size-xs)}.lc-pagination--sm .lc-pagination__button{min-width:1.75rem;height:1.75rem;padding:0 var(--spacing-1)}.lc-pagination--sm .lc-pagination__icon{width:.875rem;height:.875rem}.lc-pagination--sm .lc-pagination__button-text{font-size:var(--font-size-xs)}.lc-pagination--md{font-size:var(--font-size-sm)}.lc-pagination--md .lc-pagination__button{min-width:2.25rem;height:2.25rem;padding:0 var(--spacing-2)}.lc-pagination--md .lc-pagination__icon{width:1rem;height:1rem}.lc-pagination--md .lc-pagination__button-text{font-size:var(--font-size-sm)}.lc-pagination--lg{font-size:var(--font-size-base)}.lc-pagination--lg .lc-pagination__button{min-width:2.75rem;height:2.75rem;padding:0 var(--spacing-3)}.lc-pagination--lg .lc-pagination__icon{width:1.25rem;height:1.25rem}.lc-pagination--lg .lc-pagination__button-text{font-size:var(--font-size-base)}.lc-pagination__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--spacing-1);border:1px solid var(--color-border);background-color:var(--color-surface-2);color:var(--color-text-secondary);border-radius:var(--border-radius-md);font-weight:500;cursor:pointer;transition:color var(--animation-duration-base) var(--animation-timing-ease),background-color var(--animation-duration-base) var(--animation-timing-ease),border-color var(--animation-duration-base) var(--animation-timing-ease),transform var(--animation-duration-base) var(--animation-timing-ease);-webkit-user-select:none;user-select:none}.lc-pagination__button:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-border-strong);color:var(--color-text-primary)}.lc-pagination__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-pagination__button:active:not(:disabled){background-color:var(--color-surface-selected);transform:translateY(1px)}.lc-pagination__button:disabled{opacity:.5;cursor:not-allowed}.lc-pagination__button--active{background-color:var(--color-primary-fill);border-color:var(--color-primary-fill);color:var(--color-on-primary);font-weight:600}.lc-pagination__button--active:hover{background-color:var(--color-primary-fill-hover);border-color:var(--color-primary-fill-hover);color:var(--color-on-primary)}@media(max-width:640px){.lc-pagination__button-text:not(.lc-pagination__button--active .lc-pagination__button-text){display:none}}.lc-pagination__icon{flex-shrink:0}.lc-pagination__pages{display:flex;align-items:center;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.lc-pagination__info{text-align:center;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin-top:var(--spacing-3)}@media(max-width:640px){.lc-pagination{gap:var(--spacing-1)}.lc-pagination__pages{gap:var(--spacing-0-5)}.lc-pagination__button--page:not(.lc-pagination__button--active){display:none}.lc-pagination__button--page:not(.lc-pagination__button--active):first-child,.lc-pagination__button--page:not(.lc-pagination__button--active):last-child{display:inline-flex}.lc-pagination__button--page:not(.lc-pagination__button--active):nth-child(n-1):nth-child(-n+1){display:inline-flex}}@media(prefers-reduced-motion:reduce){.lc-pagination__button{transition:none}}\n"],
|
|
205
208
|
dependencies: [{
|
|
206
209
|
kind: "directive",
|
|
@@ -223,7 +226,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
223
226
|
standalone: true,
|
|
224
227
|
imports: [NgClass],
|
|
225
228
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
226
|
-
template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"
|
|
229
|
+
template: "@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n …\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n",
|
|
227
230
|
styles: [".lc-pagination{display:flex;align-items:center;justify-content:center;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-pagination--sm{font-size:var(--font-size-xs)}.lc-pagination--sm .lc-pagination__button{min-width:1.75rem;height:1.75rem;padding:0 var(--spacing-1)}.lc-pagination--sm .lc-pagination__icon{width:.875rem;height:.875rem}.lc-pagination--sm .lc-pagination__button-text{font-size:var(--font-size-xs)}.lc-pagination--md{font-size:var(--font-size-sm)}.lc-pagination--md .lc-pagination__button{min-width:2.25rem;height:2.25rem;padding:0 var(--spacing-2)}.lc-pagination--md .lc-pagination__icon{width:1rem;height:1rem}.lc-pagination--md .lc-pagination__button-text{font-size:var(--font-size-sm)}.lc-pagination--lg{font-size:var(--font-size-base)}.lc-pagination--lg .lc-pagination__button{min-width:2.75rem;height:2.75rem;padding:0 var(--spacing-3)}.lc-pagination--lg .lc-pagination__icon{width:1.25rem;height:1.25rem}.lc-pagination--lg .lc-pagination__button-text{font-size:var(--font-size-base)}.lc-pagination__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--spacing-1);border:1px solid var(--color-border);background-color:var(--color-surface-2);color:var(--color-text-secondary);border-radius:var(--border-radius-md);font-weight:500;cursor:pointer;transition:color var(--animation-duration-base) var(--animation-timing-ease),background-color var(--animation-duration-base) var(--animation-timing-ease),border-color var(--animation-duration-base) var(--animation-timing-ease),transform var(--animation-duration-base) var(--animation-timing-ease);-webkit-user-select:none;user-select:none}.lc-pagination__button:hover:not(:disabled){background-color:var(--color-surface-hover);border-color:var(--color-border-strong);color:var(--color-text-primary)}.lc-pagination__button:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-pagination__button:active:not(:disabled){background-color:var(--color-surface-selected);transform:translateY(1px)}.lc-pagination__button:disabled{opacity:.5;cursor:not-allowed}.lc-pagination__button--active{background-color:var(--color-primary-fill);border-color:var(--color-primary-fill);color:var(--color-on-primary);font-weight:600}.lc-pagination__button--active:hover{background-color:var(--color-primary-fill-hover);border-color:var(--color-primary-fill-hover);color:var(--color-on-primary)}@media(max-width:640px){.lc-pagination__button-text:not(.lc-pagination__button--active .lc-pagination__button-text){display:none}}.lc-pagination__icon{flex-shrink:0}.lc-pagination__pages{display:flex;align-items:center;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-tertiary);-webkit-user-select:none;user-select:none}.lc-pagination__info{text-align:center;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin-top:var(--spacing-3)}@media(max-width:640px){.lc-pagination{gap:var(--spacing-1)}.lc-pagination__pages{gap:var(--spacing-0-5)}.lc-pagination__button--page:not(.lc-pagination__button--active){display:none}.lc-pagination__button--page:not(.lc-pagination__button--active):first-child,.lc-pagination__button--page:not(.lc-pagination__button--active):last-child{display:inline-flex}.lc-pagination__button--page:not(.lc-pagination__button--active):nth-child(n-1):nth-child(-n+1){display:inline-flex}}@media(prefers-reduced-motion:reduce){.lc-pagination__button{transition:none}}\n"]
|
|
228
231
|
}]
|
|
229
232
|
}],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-pagination.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.ts","../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\n/**\n * Pagination component for navigating through pages of content.\n *\n * Features:\n * - Configurable page size\n * - Previous/Next navigation\n * - Direct page number access\n * - Ellipsis for large page ranges\n * - Size variants (sm, md, lg)\n * - Accessible with ARIA attributes\n * - Optional item count display\n * - All UI strings overridable via inputs (i18n)\n *\n * @example\n * ```html\n * <lc-pagination\n * [currentPage]=\"currentPage\"\n * [totalItems]=\"totalItems\"\n * [pageSize]=\"10\"\n * (pageChange)=\"onPageChange($event)\"\n * ></lc-pagination>\n * ```\n */\n@Component({\n selector: 'lc-pagination',\n standalone: true,\n imports: [NgClass],\n templateUrl: './pagination.component.html',\n styleUrls: ['./pagination.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PaginationComponent {\n readonly currentPage = input(1);\n readonly totalItems = input(0);\n readonly pageSize = input(10);\n readonly size = input<PaginationSize>('md');\n readonly maxVisiblePages = input(7);\n readonly ariaLabel = input('Pagination');\n readonly showInfo = input(false);\n\n // ── UI strings (override for i18n) ──────────────────────────────────────\n /** Visible text of the previous button */\n readonly previousLabel = input('Previous');\n /** Visible text of the next button */\n readonly nextLabel = input('Next');\n /** Accessible name of the previous button (the visible text is hidden on small screens) */\n readonly previousAriaLabel = input('Previous page');\n /** Accessible name of the next button */\n readonly nextAriaLabel = input('Next page');\n /** Accessible name of a page button; `{page}` is replaced with the page number */\n readonly pageAriaLabel = input('Page {page}');\n /**\n * Info line shown when `showInfo` is true; `{first}`, `{last}` and `{total}`\n * are replaced with the item range and total.\n */\n readonly infoText = input('Showing {first} to {last} of {total} items');\n\n readonly pageChange = output<number>();\n\n // Computed properties\n totalPages = computed(() => {\n const total = this.totalItems();\n const size = this.pageSize();\n return total > 0 ? Math.ceil(total / size) : 0;\n });\n\n firstItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n return (page - 1) * size + 1;\n });\n\n lastItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n const total = this.totalItems();\n const lastIndex = page * size;\n return Math.min(lastIndex, total);\n });\n\n isFirstPage = computed(() => this.currentPage() === 1);\n\n isLastPage = computed(() => this.currentPage() === this.totalPages());\n\n paginationClasses = computed(() => {\n const classes = ['lc-pagination'];\n classes.push(`lc-pagination--${this.size()}`);\n return classes.join(' ');\n });\n\n /**\n * Calculate visible page numbers with ellipsis support\n * Returns an array where -1 represents an ellipsis\n */\n visiblePages = computed(() => {\n const current = this.currentPage();\n const total = this.totalPages();\n const maxVisible = this.maxVisiblePages();\n\n if (total <= maxVisible) {\n // Show all pages if total is less than max visible\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: number[] = [];\n const sidePages = Math.floor((maxVisible - 3) / 2); // Reserve spots for first, last, and ellipsis\n\n // Always show first page\n pages.push(1);\n\n if (current <= sidePages + 2) {\n // Near the start: show 1, 2, 3, 4, 5, ..., last\n for (let i = 2; i <= maxVisible - 2; i++) {\n pages.push(i);\n }\n pages.push(-1); // Ellipsis\n pages.push(total);\n } else if (current >= total - sidePages - 1) {\n // Near the end: show 1, ..., n-4, n-3, n-2, n-1, n\n pages.push(-1); // Ellipsis\n for (let i = total - (maxVisible - 3); i <= total; i++) {\n pages.push(i);\n }\n } else {\n // In the middle: show 1, ..., current-1, current, current+1, ..., last\n pages.push(-1); // Left ellipsis\n for (let i = current - sidePages; i <= current + sidePages; i++) {\n if (i > 1 && i < total) {\n pages.push(i);\n }\n }\n pages.push(-1); // Right ellipsis\n pages.push(total);\n }\n\n return pages;\n });\n\n // Navigation methods\n goToPreviousPage(): void {\n if (!this.isFirstPage()) {\n const newPage = this.currentPage() - 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToNextPage(): void {\n if (!this.isLastPage()) {\n const newPage = this.currentPage() + 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToPage(page: number): void {\n if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {\n this.pageChange.emit(page);\n }\n }\n\n /** Resolved info line (`infoText` with placeholders filled in) */\n readonly infoLine = computed(() =>\n this.infoText()\n .replace('{first}', String(this.firstItemIndex()))\n .replace('{last}', String(this.lastItemIndex()))\n .replace('{total}', String(this.totalItems())),\n );\n\n /** Accessible name for the button of `page` */\n getPageAriaLabel(page: number): string {\n return this.pageAriaLabel().replace('{page}', String(page));\n }\n\n // Helper methods\n isCurrentPage(page: number): boolean {\n return page === this.currentPage();\n }\n\n isEllipsis(page: number): boolean {\n return page === -1;\n }\n\n shouldRender(): boolean {\n const total = this.totalPages();\n return total > 1;\n }\n}\n","@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n …\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-pagination.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.ts","../../../../libs/angular-ui-kit/src/lib/pagination/pagination.component.html"],"sourcesContent":["import {\n Component,\n input,\n output,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\n/**\n * Pagination component for navigating through pages of content.\n *\n * Features:\n * - Configurable page size\n * - Previous/Next navigation\n * - Direct page number access\n * - Ellipsis for large page ranges\n * - Size variants (sm, md, lg)\n * - Accessible with ARIA attributes\n * - Optional item count display\n * - All UI strings overridable via inputs (i18n)\n *\n * @example\n * ```html\n * <lc-pagination\n * [currentPage]=\"currentPage\"\n * [totalItems]=\"totalItems\"\n * [pageSize]=\"10\"\n * (pageChange)=\"onPageChange($event)\"\n * ></lc-pagination>\n * ```\n */\n@Component({\n selector: 'lc-pagination',\n standalone: true,\n imports: [NgClass],\n templateUrl: './pagination.component.html',\n styleUrls: ['./pagination.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PaginationComponent {\n readonly currentPage = input(1);\n readonly totalItems = input(0);\n readonly pageSize = input(10);\n readonly size = input<PaginationSize>('md');\n readonly maxVisiblePages = input(7);\n readonly ariaLabel = input('Pagination');\n readonly showInfo = input(false);\n\n // ── UI strings (override for i18n) ──────────────────────────────────────\n /** Visible text of the previous button */\n readonly previousLabel = input('Previous');\n /** Visible text of the next button */\n readonly nextLabel = input('Next');\n /**\n * Accessible name of the previous button (the visible text is hidden on\n * small screens). `previousLabel` is appended when this doesn't contain it\n * (WCAG 2.5.3 Label in Name), e.g. `Vorherige Seite, Zurück`.\n * @default 'Previous page'\n */\n readonly previousAriaLabel = input('Previous page');\n /**\n * Accessible name of the next button. `nextLabel` is appended when this\n * doesn't contain it (WCAG 2.5.3 Label in Name), e.g. `Nächste Seite, Weiter`.\n * @default 'Next page'\n */\n readonly nextAriaLabel = input('Next page');\n /** Accessible name of a page button; `{page}` is replaced with the page number */\n readonly pageAriaLabel = input('Page {page}');\n /**\n * Info line shown when `showInfo` is true; `{first}`, `{last}` and `{total}`\n * are replaced with the item range and total.\n */\n readonly infoText = input('Showing {first} to {last} of {total} items');\n\n readonly pageChange = output<number>();\n\n /** Name of the previous button: its label, then the visible text unless contained (AS-33). */\n protected readonly previousButtonAriaLabel = computed(() =>\n nameWithText(this.previousAriaLabel(), this.previousLabel()),\n );\n\n /** Name of the next button: its label, then the visible text unless contained (AS-33). */\n protected readonly nextButtonAriaLabel = computed(() => nameWithText(this.nextAriaLabel(), this.nextLabel()));\n\n // Computed properties\n totalPages = computed(() => {\n const total = this.totalItems();\n const size = this.pageSize();\n return total > 0 ? Math.ceil(total / size) : 0;\n });\n\n firstItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n return (page - 1) * size + 1;\n });\n\n lastItemIndex = computed(() => {\n const page = this.currentPage();\n const size = this.pageSize();\n const total = this.totalItems();\n const lastIndex = page * size;\n return Math.min(lastIndex, total);\n });\n\n isFirstPage = computed(() => this.currentPage() === 1);\n\n isLastPage = computed(() => this.currentPage() === this.totalPages());\n\n paginationClasses = computed(() => {\n const classes = ['lc-pagination'];\n classes.push(`lc-pagination--${this.size()}`);\n return classes.join(' ');\n });\n\n /**\n * Calculate visible page numbers with ellipsis support\n * Returns an array where -1 represents an ellipsis\n */\n visiblePages = computed(() => {\n const current = this.currentPage();\n const total = this.totalPages();\n const maxVisible = this.maxVisiblePages();\n\n if (total <= maxVisible) {\n // Show all pages if total is less than max visible\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const pages: number[] = [];\n const sidePages = Math.floor((maxVisible - 3) / 2); // Reserve spots for first, last, and ellipsis\n\n // Always show first page\n pages.push(1);\n\n if (current <= sidePages + 2) {\n // Near the start: show 1, 2, 3, 4, 5, ..., last\n for (let i = 2; i <= maxVisible - 2; i++) {\n pages.push(i);\n }\n pages.push(-1); // Ellipsis\n pages.push(total);\n } else if (current >= total - sidePages - 1) {\n // Near the end: show 1, ..., n-4, n-3, n-2, n-1, n\n pages.push(-1); // Ellipsis\n for (let i = total - (maxVisible - 3); i <= total; i++) {\n pages.push(i);\n }\n } else {\n // In the middle: show 1, ..., current-1, current, current+1, ..., last\n pages.push(-1); // Left ellipsis\n for (let i = current - sidePages; i <= current + sidePages; i++) {\n if (i > 1 && i < total) {\n pages.push(i);\n }\n }\n pages.push(-1); // Right ellipsis\n pages.push(total);\n }\n\n return pages;\n });\n\n // Navigation methods\n goToPreviousPage(): void {\n if (!this.isFirstPage()) {\n const newPage = this.currentPage() - 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToNextPage(): void {\n if (!this.isLastPage()) {\n const newPage = this.currentPage() + 1;\n this.pageChange.emit(newPage);\n }\n }\n\n goToPage(page: number): void {\n if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {\n this.pageChange.emit(page);\n }\n }\n\n /** Resolved info line (`infoText` with placeholders filled in) */\n readonly infoLine = computed(() =>\n this.infoText()\n .replace('{first}', String(this.firstItemIndex()))\n .replace('{last}', String(this.lastItemIndex()))\n .replace('{total}', String(this.totalItems())),\n );\n\n /** Accessible name for the button of `page` */\n getPageAriaLabel(page: number): string {\n return this.pageAriaLabel().replace('{page}', String(page));\n }\n\n // Helper methods\n isCurrentPage(page: number): boolean {\n return page === this.currentPage();\n }\n\n isEllipsis(page: number): boolean {\n return page === -1;\n }\n\n shouldRender(): boolean {\n const total = this.totalPages();\n return total > 1;\n }\n}\n","@if (shouldRender()) {\n <nav [attr.aria-label]=\"ariaLabel()\">\n <div [ngClass]=\"paginationClasses()\">\n <!-- Previous Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--prev\"\n [disabled]=\"isFirstPage()\"\n [attr.aria-label]=\"previousButtonAriaLabel()\"\n (click)=\"goToPreviousPage()\"\n >\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" />\n </svg>\n <span class=\"lc-pagination__button-text\">{{ previousLabel() }}</span>\n </button>\n\n <!-- Page Numbers -->\n <div class=\"lc-pagination__pages\">\n @for (page of visiblePages(); track $index) {\n @if (isEllipsis(page)) {\n <span class=\"lc-pagination__ellipsis\" aria-hidden=\"true\">\n …\n </span>\n } @else {\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--page\"\n [class.lc-pagination__button--active]=\"isCurrentPage(page)\"\n [attr.aria-label]=\"getPageAriaLabel(page)\"\n [attr.aria-current]=\"isCurrentPage(page) ? 'page' : null\"\n (click)=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n }\n }\n </div>\n\n <!-- Next Button -->\n <button\n type=\"button\"\n class=\"lc-pagination__button lc-pagination__button--next\"\n [disabled]=\"isLastPage()\"\n [attr.aria-label]=\"nextButtonAriaLabel()\"\n (click)=\"goToNextPage()\"\n >\n <span class=\"lc-pagination__button-text\">{{ nextLabel() }}</span>\n <svg class=\"lc-pagination__icon\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Optional Info Text -->\n @if (showInfo()) {\n <div class=\"lc-pagination__info\">{{ infoLine() }}</div>\n }\n </nav>\n}\n"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { CommonModule } from "@angular/common";
|
|
|
4
4
|
import { NG_VALUE_ACCESSOR } from "@angular/forms";
|
|
5
5
|
import { DomSanitizer } from "@angular/platform-browser";
|
|
6
6
|
import { isSafeImageUrl, isSafeLinkUrl } from "@lifecorner/angular-ui-kit/internal/url";
|
|
7
|
+
import { nameWithText } from "@lifecorner/angular-ui-kit/internal/a11y";
|
|
7
8
|
const HTML_ESCAPES = {
|
|
8
9
|
"&": "&",
|
|
9
10
|
"<": "<",
|
|
@@ -298,6 +299,9 @@ var RichTextEditorComponent = class RichTextEditorComponent {
|
|
|
298
299
|
hr: "Horizontal Rule"
|
|
299
300
|
}[action] || action;
|
|
300
301
|
}
|
|
302
|
+
getActionName(action) {
|
|
303
|
+
return nameWithText(this.getActionTitle(action), this.getActionLabel(action));
|
|
304
|
+
}
|
|
301
305
|
markdownToHtml(md) {
|
|
302
306
|
if (!md) return "";
|
|
303
307
|
let html = escapeHtml(md);
|
|
@@ -523,7 +527,7 @@ var RichTextEditorComponent = class RichTextEditorComponent {
|
|
|
523
527
|
isSignal: true
|
|
524
528
|
}],
|
|
525
529
|
ngImport: i0,
|
|
526
|
-
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]=\"
|
|
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",
|
|
527
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"],
|
|
528
532
|
dependencies: [{
|
|
529
533
|
kind: "ngmodule",
|
|
@@ -549,7 +553,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
549
553
|
useExisting: forwardRef(() => RichTextEditorComponent),
|
|
550
554
|
multi: true
|
|
551
555
|
}],
|
|
552
|
-
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]=\"
|
|
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",
|
|
553
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"]
|
|
554
558
|
}]
|
|
555
559
|
}],
|