@lifecorner/angular-ui-kit 3.32.0 → 3.33.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.
Files changed (48) hide show
  1. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs +2 -2
  2. package/fesm2022/lifecorner-angular-ui-kit-bottom-nav.mjs.map +1 -1
  3. package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +21 -5
  4. package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +65 -19
  6. package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
  7. package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +7 -6
  8. package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
  9. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +21 -4
  10. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  11. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs +21 -1
  12. package/fesm2022/lifecorner-angular-ui-kit-internal-a11y.mjs.map +1 -1
  13. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +2 -2
  14. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  15. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
  16. package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
  17. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +21 -4
  18. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
  19. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
  20. package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
  21. package/fesm2022/lifecorner-angular-ui-kit-select.mjs +5 -4
  22. package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
  23. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +4 -4
  24. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  25. package/fesm2022/lifecorner-angular-ui-kit-table.mjs +2 -2
  26. package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
  27. package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
  28. package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
  29. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
  30. package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
  31. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs +9 -4
  32. package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
  33. package/fesm2022/lifecorner-angular-ui-kit-tooltip.mjs +7 -0
  34. package/fesm2022/lifecorner-angular-ui-kit-tooltip.mjs.map +1 -1
  35. package/package.json +1 -1
  36. package/src/styles/theme-contrast.spec.ts +14 -6
  37. package/types/lifecorner-angular-ui-kit-bottom-nav.d.ts +3 -2
  38. package/types/lifecorner-angular-ui-kit-chat.d.ts +14 -1
  39. package/types/lifecorner-angular-ui-kit-combobox.d.ts +18 -1
  40. package/types/lifecorner-angular-ui-kit-datepicker.d.ts +4 -1
  41. package/types/lifecorner-angular-ui-kit-header.d.ts +26 -0
  42. package/types/lifecorner-angular-ui-kit-internal-a11y.d.ts +12 -1
  43. package/types/lifecorner-angular-ui-kit-list.d.ts +6 -1
  44. package/types/lifecorner-angular-ui-kit-next-step-card.d.ts +15 -0
  45. package/types/lifecorner-angular-ui-kit-select.d.ts +7 -0
  46. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +3 -1
  47. package/types/lifecorner-angular-ui-kit-toast.d.ts +14 -3
  48. package/types/lifecorner-angular-ui-kit-tooltip.d.ts +7 -2
@@ -322,7 +322,7 @@ var TextareaComponent = class TextareaComponent {
322
322
  }],
323
323
  ngImport: i0,
324
324
  template: "<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n",
325
- styles: [".lc-textarea-wrapper{display:flex;flex-direction:column;gap:var(--lc-density-gap-sm, var(--spacing-2));width:100%}.lc-textarea-label{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary)}.lc-textarea{width:100%;font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:var(--line-height-base);color:var(--color-text-primary);background-color:var(--color-surface);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease);resize:vertical;outline:none}.lc-textarea::placeholder{color:var(--color-text-tertiary)}.lc-textarea:focus{border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--xs{padding:var(--spacing-2) var(--spacing-3);font-size:var(--font-size-xs);min-height:4rem}.lc-textarea--sm{padding:var(--spacing-2-5) var(--spacing-3-5);font-size:var(--font-size-sm);min-height:5rem}.lc-textarea--md{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-base);min-height:6rem}.lc-textarea--lg{padding:var(--spacing-3-5) var(--spacing-5);font-size:var(--font-size-lg);min-height:7rem}.lc-textarea--outline{background-color:var(--color-surface);border-color:var(--color-field-border)}.lc-textarea--outline:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){border-color:var(--color-text-secondary)}.lc-textarea--outline:focus{border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--filled{background-color:var(--color-surface-sunken);border-color:var(--color-field-border)}.lc-textarea--filled:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-textarea--filled:focus{background-color:var(--color-surface);border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--disabled{cursor:not-allowed;opacity:.6;background-color:var(--color-surface-muted);color:var(--color-text-tertiary)}.lc-textarea--readonly{cursor:default;background-color:var(--color-surface-muted)}.lc-textarea--error{border-color:var(--color-error-600)}.lc-textarea--error:focus{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.lc-textarea--auto-resize{resize:none;overflow:hidden}.lc-textarea-helper-text{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.lc-textarea-error-message{font-size:var(--font-size-xs);color:var(--color-text-error)}.lc-textarea-char-count,.lc-textarea-character-count{font-size:var(--font-size-xs);color:var(--color-text-tertiary);text-align:right}.lc-textarea-char-count--warning,.lc-textarea-character-count--warning{color:var(--color-warning-600)}.lc-textarea-char-count--error,.lc-textarea-char-count--over-limit,.lc-textarea-character-count--error,.lc-textarea-character-count--over-limit{color:var(--color-text-error)}.lc-textarea-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-textarea-messages{flex:1}.lc-textarea-input-wrapper{position:relative;width:100%}.lc-textarea-required-indicator{color:var(--color-text-error);margin-inline-start:var(--spacing-1)}@media(forced-colors:active){.lc-textarea--error:focus{outline-color:Highlight}}\n"],
325
+ styles: [".lc-textarea-wrapper{display:flex;flex-direction:column;gap:var(--lc-density-gap-sm, var(--spacing-2));width:100%}.lc-textarea-label{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary)}.lc-textarea{width:100%;font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:var(--line-height-base);color:var(--color-text-primary);background-color:var(--color-surface);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease);resize:vertical;outline:none}.lc-textarea::placeholder{color:var(--color-text-tertiary)}.lc-textarea:focus{border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--xs{padding:var(--spacing-2) var(--spacing-3);font-size:var(--font-size-xs);min-height:4rem}.lc-textarea--sm{padding:var(--spacing-2-5) var(--spacing-3-5);font-size:var(--font-size-sm);min-height:5rem}.lc-textarea--md{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-base);min-height:6rem}.lc-textarea--lg{padding:var(--spacing-3-5) var(--spacing-5);font-size:var(--font-size-lg);min-height:7rem}.lc-textarea--outline{background-color:var(--color-surface);border-color:var(--color-field-border)}.lc-textarea--outline:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){border-color:var(--color-text-secondary)}.lc-textarea--outline:focus{border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--filled{background-color:var(--color-surface-sunken);border-color:var(--color-field-border)}.lc-textarea--filled:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-textarea--filled:focus{background-color:var(--color-surface);border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--disabled{cursor:not-allowed;opacity:.6;background-color:var(--color-surface-muted);color:var(--color-text-tertiary)}.lc-textarea--readonly{cursor:default;background-color:var(--color-surface-muted)}.lc-textarea--error{border-color:var(--color-error-600)}.lc-textarea--error:focus{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.lc-textarea--auto-resize{resize:none;overflow:hidden}.lc-textarea-helper-text{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.lc-textarea-error-message{font-size:var(--font-size-xs);color:var(--color-error-600)}.lc-textarea-char-count,.lc-textarea-character-count{font-size:var(--font-size-xs);color:var(--color-text-tertiary);text-align:right}.lc-textarea-char-count--warning,.lc-textarea-character-count--warning{color:var(--color-warning-600)}.lc-textarea-char-count--error,.lc-textarea-char-count--over-limit,.lc-textarea-character-count--error,.lc-textarea-character-count--over-limit{color:var(--color-text-error)}.lc-textarea-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-textarea-messages{flex:1}.lc-textarea-input-wrapper{position:relative;width:100%}.lc-textarea-required-indicator{color:var(--color-text-error);margin-inline-start:var(--spacing-1)}@media(forced-colors:active){.lc-textarea:focus,.lc-textarea--error:focus{outline-color:Highlight}}\n"],
326
326
  changeDetection: i0.ChangeDetectionStrategy.OnPush
327
327
  });
328
328
  };
@@ -344,7 +344,7 @@ i0.ɵɵngDeclareClassMetadata({
344
344
  multi: true
345
345
  }],
346
346
  template: "<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n",
347
- styles: [".lc-textarea-wrapper{display:flex;flex-direction:column;gap:var(--lc-density-gap-sm, var(--spacing-2));width:100%}.lc-textarea-label{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary)}.lc-textarea{width:100%;font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:var(--line-height-base);color:var(--color-text-primary);background-color:var(--color-surface);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease);resize:vertical;outline:none}.lc-textarea::placeholder{color:var(--color-text-tertiary)}.lc-textarea:focus{border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--xs{padding:var(--spacing-2) var(--spacing-3);font-size:var(--font-size-xs);min-height:4rem}.lc-textarea--sm{padding:var(--spacing-2-5) var(--spacing-3-5);font-size:var(--font-size-sm);min-height:5rem}.lc-textarea--md{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-base);min-height:6rem}.lc-textarea--lg{padding:var(--spacing-3-5) var(--spacing-5);font-size:var(--font-size-lg);min-height:7rem}.lc-textarea--outline{background-color:var(--color-surface);border-color:var(--color-field-border)}.lc-textarea--outline:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){border-color:var(--color-text-secondary)}.lc-textarea--outline:focus{border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--filled{background-color:var(--color-surface-sunken);border-color:var(--color-field-border)}.lc-textarea--filled:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-textarea--filled:focus{background-color:var(--color-surface);border-color:var(--color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary-500) 20%,transparent)}.lc-textarea--disabled{cursor:not-allowed;opacity:.6;background-color:var(--color-surface-muted);color:var(--color-text-tertiary)}.lc-textarea--readonly{cursor:default;background-color:var(--color-surface-muted)}.lc-textarea--error{border-color:var(--color-error-600)}.lc-textarea--error:focus{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.lc-textarea--auto-resize{resize:none;overflow:hidden}.lc-textarea-helper-text{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.lc-textarea-error-message{font-size:var(--font-size-xs);color:var(--color-text-error)}.lc-textarea-char-count,.lc-textarea-character-count{font-size:var(--font-size-xs);color:var(--color-text-tertiary);text-align:right}.lc-textarea-char-count--warning,.lc-textarea-character-count--warning{color:var(--color-warning-600)}.lc-textarea-char-count--error,.lc-textarea-char-count--over-limit,.lc-textarea-character-count--error,.lc-textarea-character-count--over-limit{color:var(--color-text-error)}.lc-textarea-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-textarea-messages{flex:1}.lc-textarea-input-wrapper{position:relative;width:100%}.lc-textarea-required-indicator{color:var(--color-text-error);margin-inline-start:var(--spacing-1)}@media(forced-colors:active){.lc-textarea--error:focus{outline-color:Highlight}}\n"]
347
+ styles: [".lc-textarea-wrapper{display:flex;flex-direction:column;gap:var(--lc-density-gap-sm, var(--spacing-2));width:100%}.lc-textarea-label{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary)}.lc-textarea{width:100%;font-family:var(--font-family-base);font-size:var(--font-size-base);line-height:var(--line-height-base);color:var(--color-text-primary);background-color:var(--color-surface);border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);transition:all var(--animation-duration-base) var(--animation-timing-ease);resize:vertical;outline:none}.lc-textarea::placeholder{color:var(--color-text-tertiary)}.lc-textarea:focus{border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--xs{padding:var(--spacing-2) var(--spacing-3);font-size:var(--font-size-xs);min-height:4rem}.lc-textarea--sm{padding:var(--spacing-2-5) var(--spacing-3-5);font-size:var(--font-size-sm);min-height:5rem}.lc-textarea--md{padding:var(--spacing-3) var(--spacing-4);font-size:var(--font-size-base);min-height:6rem}.lc-textarea--lg{padding:var(--spacing-3-5) var(--spacing-5);font-size:var(--font-size-lg);min-height:7rem}.lc-textarea--outline{background-color:var(--color-surface);border-color:var(--color-field-border)}.lc-textarea--outline:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){border-color:var(--color-text-secondary)}.lc-textarea--outline:focus{border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--filled{background-color:var(--color-surface-sunken);border-color:var(--color-field-border)}.lc-textarea--filled:hover:not(:disabled):not(:read-only):not(.lc-textarea--error):not(:focus){background-color:var(--color-surface-hover);border-color:var(--color-text-secondary)}.lc-textarea--filled:focus{background-color:var(--color-surface);border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0;box-shadow:none}.lc-textarea--disabled{cursor:not-allowed;opacity:.6;background-color:var(--color-surface-muted);color:var(--color-text-tertiary)}.lc-textarea--readonly{cursor:default;background-color:var(--color-surface-muted)}.lc-textarea--error{border-color:var(--color-error-600)}.lc-textarea--error:focus{border-color:var(--color-error-600);outline:2px solid var(--color-error-600);outline-offset:0;box-shadow:none}.lc-textarea--auto-resize{resize:none;overflow:hidden}.lc-textarea-helper-text{font-size:var(--font-size-xs);color:var(--color-text-secondary)}.lc-textarea-error-message{font-size:var(--font-size-xs);color:var(--color-error-600)}.lc-textarea-char-count,.lc-textarea-character-count{font-size:var(--font-size-xs);color:var(--color-text-tertiary);text-align:right}.lc-textarea-char-count--warning,.lc-textarea-character-count--warning{color:var(--color-warning-600)}.lc-textarea-char-count--error,.lc-textarea-char-count--over-limit,.lc-textarea-character-count--error,.lc-textarea-character-count--over-limit{color:var(--color-text-error)}.lc-textarea-footer{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-textarea-messages{flex:1}.lc-textarea-input-wrapper{position:relative;width:100%}.lc-textarea-required-indicator{color:var(--color-text-error);margin-inline-start:var(--spacing-1)}@media(forced-colors:active){.lc-textarea:focus,.lc-textarea--error:focus{outline-color:Highlight}}\n"]
348
348
  }]
349
349
  }],
350
350
  ctorParameters: () => [],
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n OutputRef,\n PLATFORM_ID,\n afterRenderEffect,\n computed,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n * - Plain form posts without JavaScript: `name`, and a `value` that is\n * prerendered into the native `<textarea>`\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * <lc-textarea label=\"Message\" name=\"message\" [required]=\"true\" [value]=\"draft\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /**\n * Visual variant of the textarea\n * @default 'outline'\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n * @default false\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Show the `*` after the label of a `required` field. `false` keeps the\n * required semantics (`required`, `aria-required`) without the asterisk —\n * for forms where every field is required.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /**\n * Deprecated, no effect (it never had one in `lc-textarea`). The `*` is\n * decorative and hidden from assistive technology, because the native\n * `required` state is announced already, in the screen reader's language; a\n * name on the `*` would make the field say \"required\" twice. Kept so\n * existing bindings compile.\n * @deprecated No effect: the native `required` state is announced instead.\n * @default 'required'\n */\n readonly requiredAriaLabel = input<string>('required');\n\n /**\n * Id of the native `<textarea>`. The label's `for`, the error\n * (`<id>-error`), the helper text (`<id>-helper`) and `aria-describedby`\n * derive from it, so an error summary can link to the field\n * (`<a href=\"#contact-message\">`) and prerendered HTML is the same in every\n * build. Unique in the document, no spaces. Unset (or empty) = a generated\n * `lc-textarea-<n>`, which follows creation order (on the server it keeps\n * counting across prerendered routes): set it on prerendered pages.\n * @default generated (lc-textarea-<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 * Whether the textarea is readonly\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n * @default ''\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n * @default ''\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n * @default ''\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n * @default undefined\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Native `name` of the inner `<textarea>`, so a form posts the text without\n * JavaScript. Unset = no attribute. A `disabled` textarea is not posted\n * (HTML); use `readonly` to post a fixed text.\n * @default undefined\n */\n readonly name = input<string>();\n\n /**\n * Number of visible text rows\n * @default 3\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n * @default undefined\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n * @default false\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n * @default 3\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n * @default undefined\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Current text, two-way bindable (`[value]` / `[(value)]`). It pre-fills the\n * native textarea, in prerendered HTML too, so a server-rendered page can\n * keep a visitor's text. Emits `valueChange` whenever it changes: once per\n * keystroke that changes the text, and when a bound form control or code\n * sets a different text. With a form control, the control's value wins;\n * don't bind both.\n * @default ''\n */\n readonly value = model<string>('');\n\n /**\n * The model's change output as a class property, so code that subscribes to\n * it (`textarea.valueChange.subscribe(…)`, `outputToObservable(…)`) keeps\n * compiling as in 3.9. Template bindings `(valueChange)` use the model.\n */\n readonly valueChange: OutputRef<string> = this.value;\n\n /** Generated id, taken once per instance (also with `controlId`), so later ids don't shift. */\n private readonly generatedId = `lc-textarea-${nextUniqueId++}`;\n\n private readonly resolvedId = computed(() => this.controlId() || this.generatedId);\n\n /** Id of the native textarea: `controlId`, or the generated `lc-textarea-<n>`. Links label, error and helper text to it. */\n get textareaId(): string {\n return this.resolvedId();\n }\n\n /** Id of the error message: `<textareaId>-error`. */\n get errorId(): string {\n return `${this.resolvedId()}-error`;\n }\n\n /** Id of the helper text: `<textareaId>-helper`. */\n get helperId(): string {\n return `${this.resolvedId()}-helper`;\n }\n\n /**\n * The native textarea's value. On the server a leading line break gets one\n * extra `\\n`: the HTML parser drops the first line break after `<textarea>`,\n * and the server's DOM serialiser doesn't add the one the HTML\n * serialisation rules require, so the prerendered page would lose it.\n */\n protected readonly renderedValue = computed(() => {\n const value = this.value();\n return this.isServer && /^[\\r\\n]/.test(value) ? `\\n${value}` : value;\n });\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-textarea.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.ts","../../../../libs/angular-ui-kit/src/lib/textarea/textarea.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n OutputRef,\n PLATFORM_ID,\n afterRenderEffect,\n computed,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/** Counter for generated ids: they follow creation order, so prerendered HTML is the same in every build. */\nlet nextUniqueId = 0;\n\n/**\n * Textarea component for multi-line text input.\n *\n * Features:\n * - Variant styles (outline, filled)\n * - Size presets (xs, sm, md, lg)\n * - Auto-resize with configurable min/max rows\n * - Character count display with maxLength\n * - Validation error message and helper text\n * - Disabled and readonly states\n * - ControlValueAccessor integration for reactive forms\n * - Plain form posts without JavaScript: `name`, and a `value` that is\n * prerendered into the native `<textarea>`\n *\n * @example\n * ```html\n * <lc-textarea placeholder=\"Enter message\" [autoResize]=\"true\" [(ngModel)]=\"message\" />\n * <lc-textarea label=\"Message\" name=\"message\" [required]=\"true\" [value]=\"draft\" />\n * ```\n */\n@Component({\n selector: 'lc-textarea',\n standalone: true,\n imports: [],\n templateUrl: './textarea.component.html',\n styleUrl: './textarea.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TextareaComponent),\n multi: true,\n },\n ],\n})\nexport class TextareaComponent implements ControlValueAccessor {\n private readonly textareaElement = viewChild<ElementRef<HTMLTextAreaElement>>('textarea');\n\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /**\n * Visual variant of the textarea\n * @default 'outline'\n */\n readonly variant = input<'outline' | 'filled'>('outline');\n\n /**\n * Size of the textarea\n * @default 'md'\n */\n readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the textarea is disabled\n * @default false\n */\n readonly disabled = input<boolean>(false);\n\n /**\n * Whether the textarea is in error state\n * @default false\n */\n readonly error = input<boolean>(false);\n\n /**\n * Whether the textarea is required\n * @default false\n */\n readonly required = input<boolean>(false);\n\n /**\n * Show the `*` after the label of a `required` field. `false` keeps the\n * required semantics (`required`, `aria-required`) without the asterisk —\n * for forms where every field is required.\n * @default true\n */\n readonly requiredIndicator = input<boolean>(true);\n\n /**\n * Deprecated, no effect (it never had one in `lc-textarea`). The `*` is\n * decorative and hidden from assistive technology, because the native\n * `required` state is announced already, in the screen reader's language; a\n * name on the `*` would make the field say \"required\" twice. Kept so\n * existing bindings compile.\n * @deprecated No effect: the native `required` state is announced instead.\n * @default 'required'\n */\n readonly requiredAriaLabel = input<string>('required');\n\n /**\n * Id of the native `<textarea>`. The label's `for`, the error\n * (`<id>-error`), the helper text (`<id>-helper`) and `aria-describedby`\n * derive from it, so an error summary can link to the field\n * (`<a href=\"#contact-message\">`) and prerendered HTML is the same in every\n * build. Unique in the document, no spaces. Unset (or empty) = a generated\n * `lc-textarea-<n>`, which follows creation order (on the server it keeps\n * counting across prerendered routes): set it on prerendered pages.\n * @default generated (lc-textarea-<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 * Whether the textarea is readonly\n * @default false\n */\n readonly readonly = input<boolean>(false);\n\n /**\n * Placeholder text\n * @default ''\n */\n readonly placeholder = input<string>('');\n\n /**\n * Label text displayed above the textarea\n * @default ''\n */\n readonly label = input<string>('');\n\n /**\n * Helper text displayed below the textarea\n * @default ''\n */\n readonly helperText = input<string>('');\n\n /**\n * Error message displayed when error is true\n * @default ''\n */\n readonly errorMessage = input<string>('');\n\n /**\n * ARIA label for accessibility\n * @default undefined\n */\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Native `name` of the inner `<textarea>`, so a form posts the text without\n * JavaScript. Unset = no attribute. A `disabled` textarea is not posted\n * (HTML); use `readonly` to post a fixed text.\n * @default undefined\n */\n readonly name = input<string>();\n\n /**\n * Number of visible text rows\n * @default 3\n */\n readonly rows = input<number>(3);\n\n /**\n * Maximum number of characters allowed\n * @default undefined\n */\n readonly maxLength = input<number | undefined>(undefined);\n\n /**\n * Whether to show character count\n * @default false\n */\n readonly showCharacterCount = input<boolean>(false);\n\n /**\n * Whether to automatically resize based on content\n * @default false\n */\n readonly autoResize = input<boolean>(false);\n\n /**\n * Minimum number of rows for auto-resize\n * @default 3\n */\n readonly minRows = input<number>(3);\n\n /**\n * Maximum number of rows for auto-resize\n * @default undefined\n */\n readonly maxRows = input<number | undefined>(undefined);\n\n /**\n * Current text, two-way bindable (`[value]` / `[(value)]`). It pre-fills the\n * native textarea, in prerendered HTML too, so a server-rendered page can\n * keep a visitor's text. Emits `valueChange` whenever it changes: once per\n * keystroke that changes the text, and when a bound form control or code\n * sets a different text. With a form control, the control's value wins;\n * don't bind both.\n * @default ''\n */\n readonly value = model<string>('');\n\n /**\n * The model's change output as a class property, so code that subscribes to\n * it (`textarea.valueChange.subscribe(…)`, `outputToObservable(…)`) keeps\n * compiling as in 3.9. Template bindings `(valueChange)` use the model.\n */\n readonly valueChange: OutputRef<string> = this.value;\n\n /** Generated id, taken once per instance (also with `controlId`), so later ids don't shift. */\n private readonly generatedId = `lc-textarea-${nextUniqueId++}`;\n\n private readonly resolvedId = computed(() => this.controlId() || this.generatedId);\n\n /** Id of the native textarea: `controlId`, or the generated `lc-textarea-<n>`. Links label, error and helper text to it. */\n get textareaId(): string {\n return this.resolvedId();\n }\n\n /** Id of the error message: `<textareaId>-error`. */\n get errorId(): string {\n return `${this.resolvedId()}-error`;\n }\n\n /** Id of the helper text: `<textareaId>-helper`. */\n get helperId(): string {\n return `${this.resolvedId()}-helper`;\n }\n\n /**\n * The native textarea's value. On the server a leading line break gets one\n * extra `\\n`: the HTML parser drops the first line break after `<textarea>`,\n * and the server's DOM serialiser doesn't add the one the HTML\n * serialisation rules require, so the prerendered page would lose it.\n */\n protected readonly renderedValue = computed(() => {\n const value = this.value();\n return this.isServer && /^[\\r\\n]/.test(value) ? `\\n${value}` : value;\n });\n\n // Internal disabled state (for ControlValueAccessor)\n private readonly formDisabled = signal<boolean>(false);\n\n /**\n * Computed disabled state from both input and form control\n */\n readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n // Computed values\n readonly currentCharacterCount = computed(() => this.value().length);\n\n readonly characterCountText = computed(() => {\n const current = this.currentCharacterCount();\n const max = this.maxLength();\n return max !== undefined ? `${current} / ${max}` : `${current}`;\n });\n\n readonly isOverLimit = computed(() => {\n const max = this.maxLength();\n return max !== undefined && this.currentCharacterCount() > max;\n });\n\n /** Whether an error message is shown (drives aria-describedby) */\n readonly showsError = computed(() => this.error() && !!this.errorMessage());\n\n /** Whether the helper text is shown (error message takes precedence) */\n readonly showsHelper = computed(() => !this.showsError() && !!this.helperText());\n\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.showsError()) ids.push(this.errorId);\n if (this.showsHelper()) ids.push(this.helperId);\n return ids.length ? ids.join(' ') : null;\n });\n\n /**\n * Computed classes for the textarea element\n */\n readonly textareaClasses = computed(() => {\n const classes = ['lc-textarea', `lc-textarea--${this.variant()}`, `lc-textarea--${this.size()}`];\n\n if (this.isDisabled()) {\n classes.push('lc-textarea--disabled');\n }\n\n if (this.error()) {\n classes.push('lc-textarea--error');\n }\n\n if (this.readonly()) {\n classes.push('lc-textarea--readonly');\n }\n\n if (this.autoResize()) {\n classes.push('lc-textarea--auto-resize');\n }\n\n return classes.join(' ');\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onChange: (value: string) => void = () => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n private onTouched: () => void = () => {};\n\n constructor() {\n // Re-measure after every render in which the value or the resize\n // constraints changed — this covers typing, form writes and input changes\n // without timers, and runs once the DOM already holds the new value.\n afterRenderEffect(() => {\n this.value();\n this.minRows();\n this.maxRows();\n if (this.autoResize()) {\n this.adjustHeight();\n }\n });\n }\n\n /**\n * Handle input changes\n */\n onInput(value: string): void {\n if (this.isDisabled() || this.readonly()) {\n return;\n }\n\n // model.set() emits `valueChange` exactly once per actual change\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle blur event\n */\n onBlur(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | null | undefined): void {\n this.value.set(value || '');\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // Private helper methods\n private adjustHeight(): void {\n const textarea = this.textareaElement()?.nativeElement;\n if (!textarea) {\n return;\n }\n\n // Reset height to get accurate scrollHeight\n textarea.style.height = 'auto';\n\n const minHeight = this.rowsToHeight(textarea, this.minRows());\n const maxHeight = this.rowsToHeight(textarea, this.maxRows());\n let newHeight = textarea.scrollHeight;\n\n // Apply constraints\n if (minHeight !== undefined) {\n newHeight = Math.max(newHeight, minHeight);\n }\n if (maxHeight !== undefined) {\n newHeight = Math.min(newHeight, maxHeight);\n }\n\n textarea.style.height = `${newHeight}px`;\n }\n\n /** Pixel height for `rows` lines incl. vertical padding; undefined when it cannot be measured. */\n private rowsToHeight(textarea: HTMLTextAreaElement, rows: number | undefined): number | undefined {\n if (rows === undefined) {\n return undefined;\n }\n\n const style = getComputedStyle(textarea);\n const lineHeight = parseFloat(style.lineHeight);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n\n // 'normal' / unmeasurable line-height (e.g. jsdom) → no constraint\n if (!Number.isFinite(lineHeight)) {\n return undefined;\n }\n\n return lineHeight * rows + paddingTop + paddingBottom;\n }\n\n protected getInputValue(event: Event): string {\n return (event.target as HTMLTextAreaElement).value;\n }\n}\n","<div class=\"lc-textarea-wrapper\">\n @if (label()) {\n <!-- No whitespace before the *: the gap is its margin, and it never wraps onto a line alone -->\n <label [for]=\"textareaId\" class=\"lc-textarea-label\">\n {{ label() }}@if (required() && requiredIndicator()) {<span class=\"lc-textarea-required-indicator\" aria-hidden=\"true\">*</span>}\n </label>\n }\n\n <div class=\"lc-textarea-input-wrapper\">\n <textarea\n #textarea\n [id]=\"textareaId\"\n [class]=\"textareaClasses()\"\n [value]=\"renderedValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [attr.name]=\"name() ?? null\"\n [attr.rows]=\"autoResize() ? null : rows()\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-required]=\"required() || null\"\n [attr.aria-invalid]=\"error() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput(getInputValue($event))\"\n (blur)=\"onBlur()\"\n ></textarea>\n </div>\n\n <div class=\"lc-textarea-footer\">\n <div class=\"lc-textarea-messages\">\n @if (showsError()) {\n <span [id]=\"errorId\" class=\"lc-textarea-error-message\" [attr.role]=\"announceError() ? 'alert' : null\">{{ errorMessage() }}</span>\n } @else if (showsHelper()) {\n <span [id]=\"helperId\" class=\"lc-textarea-helper-text\">{{ helperText() }}</span>\n }\n </div>\n\n @if (showCharacterCount()) {\n <span\n class=\"lc-textarea-character-count\"\n [class.lc-textarea-character-count--over-limit]=\"isOverLimit()\"\n aria-live=\"polite\"\n >\n {{ characterCountText() }}\n </span>\n }\n </div>\n</div>\n"],"mappings":"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"}
@@ -292,6 +292,11 @@ var ToastOutletComponent = class ToastOutletComponent {
292
292
  }
293
293
  return groups;
294
294
  }, ...ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []);
295
+ latestLive = computed(() => {
296
+ const latest = /* @__PURE__ */ new Map();
297
+ for (const toast of this.toastService.toasts()) if (toast.position.startsWith("bottom-")) latest.set(toast.position, ToastOutletComponent.liveFor(toast));
298
+ return latest;
299
+ }, ...ngDevMode ? [{ debugName: "latestLive" }] : /* istanbul ignore next */ []);
295
300
  toastsFor(position, live) {
296
301
  return this.groups().get(`${position}:${live}`) ?? [];
297
302
  }
@@ -327,8 +332,8 @@ var ToastOutletComponent = class ToastOutletComponent {
327
332
  "style.--_lc-toast-outlet-frame-bottom": "insets.bottom()"
328
333
  } },
329
334
  ngImport: i0,
330
- template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
331
- styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}\n"],
335
+ template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
336
+ styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}.lc-toast-outlet__stack--bottom-left>.lc-toast-outlet__region,.lc-toast-outlet__stack--bottom-center>.lc-toast-outlet__region,.lc-toast-outlet__stack--bottom-right>.lc-toast-outlet__region{flex-direction:column}.lc-toast-outlet__stack--bottom-left>.lc-toast-outlet__region--latest,.lc-toast-outlet__stack--bottom-center>.lc-toast-outlet__region--latest,.lc-toast-outlet__stack--bottom-right>.lc-toast-outlet__region--latest{order:-1}\n"],
332
337
  dependencies: [{
333
338
  kind: "component",
334
339
  type: ToastComponent,
@@ -359,8 +364,8 @@ i0.ɵɵngDeclareClassMetadata({
359
364
  "[style.--_lc-toast-outlet-frame-top]": "insets.top()",
360
365
  "[style.--_lc-toast-outlet-frame-bottom]": "insets.bottom()"
361
366
  },
362
- template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
363
- styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}\n"]
367
+ template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
368
+ styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}.lc-toast-outlet__stack--bottom-left>.lc-toast-outlet__region,.lc-toast-outlet__stack--bottom-center>.lc-toast-outlet__region,.lc-toast-outlet__stack--bottom-right>.lc-toast-outlet__region{flex-direction:column}.lc-toast-outlet__stack--bottom-left>.lc-toast-outlet__region--latest,.lc-toast-outlet__stack--bottom-center>.lc-toast-outlet__region--latest,.lc-toast-outlet__stack--bottom-right>.lc-toast-outlet__region--latest{order:-1}\n"]
364
369
  }]
365
370
  }],
366
371
  propDecorators: { closeAriaLabel: [{
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable. In the shell, prefer the\n * `bottom-*` positions: a top toast can still cover a page title placed\n * right under the header.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable.\n *\n * Bottom stacks show the newest toast nearest the edge (3.33); top stacks the\n * oldest at the top. A `top-*` stack lies over the page on purpose (a toast\n * stays visible wherever the page is scrolled), so in the shell it can cover\n * a title right under the header (at 360 px a toast at y 72 over an h1 at\n * y 80): prefer `bottom-*` there.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n /**\n * 3.33 (request 052, 045-n2): per `bottom-*` stack, the live level of its\n * newest toast. That region gets `lc-toast-outlet__region--latest` and is\n * laid out at the edge, so the newest toast sits nearest the edge (and, in\n * a scrolling stack, in view at rest). DOM and announcement order stay.\n */\n protected readonly latestLive = computed(() => {\n const latest = new Map<ToastPosition, ToastLive>();\n for (const toast of this.toastService.toasts()) {\n if (toast.position.startsWith('bottom-')) {\n latest.set(toast.position, ToastOutletComponent.liveFor(toast));\n }\n }\n return latest;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div\n class=\"lc-toast-outlet__region\"\n [class.lc-toast-outlet__region--latest]=\"latestLive().get(position) === live\"\n [attr.aria-live]=\"live\"\n aria-atomic=\"false\"\n >\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
@@ -149,6 +149,11 @@ var TooltipDirective = class TooltipDirective {
149
149
  }
150
150
  onFocusIn(event) {
151
151
  if (event.target === this.elementRef.nativeElement) return;
152
+ const target = event.target;
153
+ if (this.tooltipRef && target?.matches?.(CONTROL) && target !== this.describedEl) {
154
+ this.removeDescribedBy();
155
+ this.addDescribedBy(target);
156
+ }
152
157
  this.showWithDelay();
153
158
  }
154
159
  onFocusOut(event) {
@@ -240,6 +245,8 @@ var TooltipDirective = class TooltipDirective {
240
245
  resolveControl() {
241
246
  const host = this.elementRef.nativeElement;
242
247
  if (host.matches(CONTROL)) return host;
248
+ const focused = host.ownerDocument?.activeElement;
249
+ if (focused && focused !== host && host.contains(focused) && focused.matches(CONTROL)) return focused;
243
250
  return host.querySelector(CONTROL) ?? host;
244
251
  }
245
252
  resolveAnchor(control) {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-tooltip.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/tooltip/tooltip.directive.ts"],"sourcesContent":["import {\n Directive,\n input,\n ElementRef,\n OnDestroy,\n Renderer2,\n ViewContainerRef,\n ComponentRef,\n HostListener,\n Component,\n ChangeDetectionStrategy,\n inject,\n} from '@angular/core';\nimport { Overlay, OverlayRef, ConnectedPosition, FlexibleConnectedPositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { Subscription } from 'rxjs';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { closestLang } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Internal tooltip content component\n */\n@Component({\n selector: 'lc-tooltip-content',\n standalone: true,\n imports: [],\n template: `<div class=\"lc-tooltip-inner\">{{ content }}</div>`,\n styles: [\n `\n :host {\n display: block;\n /* Inverted surface: reads as a tooltip in both themes and keeps AAA\n contrast — near-white on the dark theme, near-black on the light one. */\n background-color: var(--color-text-primary);\n color: var(--color-surface);\n padding: 0.5rem 0.75rem;\n border-radius: var(--border-radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n line-height: 1.25rem;\n max-width: 300px;\n word-wrap: break-word;\n box-shadow: var(--elevation-2);\n }\n\n .lc-tooltip-inner {\n position: relative;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-tooltip',\n role: 'tooltip',\n },\n})\nexport class TooltipContentComponent {\n content = '';\n}\n\nconst POSITIONS: Record<TooltipPosition, ConnectedPosition> = {\n top: {\n originX: 'center',\n originY: 'top',\n overlayX: 'center',\n overlayY: 'bottom',\n offsetY: -8,\n panelClass: 'lc-tooltip--top',\n },\n bottom: {\n originX: 'center',\n originY: 'bottom',\n overlayX: 'center',\n overlayY: 'top',\n offsetY: 8,\n panelClass: 'lc-tooltip--bottom',\n },\n left: {\n originX: 'start',\n originY: 'center',\n overlayX: 'end',\n overlayY: 'center',\n offsetX: -8,\n panelClass: 'lc-tooltip--left',\n },\n right: {\n originX: 'end',\n originY: 'center',\n overlayX: 'start',\n overlayY: 'center',\n offsetX: 8,\n panelClass: 'lc-tooltip--right',\n },\n};\n\nconst POSITION_NAMES = Object.keys(POSITIONS) as TooltipPosition[];\n\n/** Fallback order when the preferred side does not fit the viewport. */\nconst FALLBACKS: Record<TooltipPosition, TooltipPosition[]> = {\n top: ['bottom', 'right', 'left'],\n bottom: ['top', 'right', 'left'],\n left: ['right', 'top', 'bottom'],\n right: ['left', 'top', 'bottom'],\n};\n\n/**\n * What the tooltip describes (3.32, request 051): the host when it matches,\n * else the first element inside it that does (`lc-button`'s `<button>`).\n */\nconst CONTROL =\n 'a[href], button, input:not([type=hidden]), select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Tooltip directive for displaying contextual information.\n * Uses Angular CDK Overlay for positioning; flips to the opposite side when\n * the preferred one does not fit the viewport, and can be dismissed with\n * Escape (WCAG 1.4.13).\n *\n * The tooltip takes the `lang` of the host's closest `[lang]` (3.31).\n *\n * Shows on hover and on keyboard focus of the host or of an element inside it\n * (3.32). On a wrapper such as `lc-button`, the tooltip describes and anchors\n * to the wrapped `<button>`.\n *\n * @example\n * ```html\n * <button lcTooltip=\"Click me!\" tooltipPosition=\"top\">\n * Hover or focus\n * </button>\n * ```\n */\n@Directive({\n selector: '[lcTooltip]',\n standalone: true,\n})\nexport class TooltipDirective implements OnDestroy {\n private static nextId = 0;\n\n /**\n * Tooltip content text\n */\n lcTooltip = input.required<string>();\n\n /**\n * Tooltip position\n * @default 'top'\n */\n tooltipPosition = input<TooltipPosition>('top');\n\n /**\n * Show delay in milliseconds\n * @default 0\n */\n tooltipShowDelay = input<number>(0);\n\n /**\n * Hide delay in milliseconds\n * @default 0\n */\n tooltipHideDelay = input<number>(0);\n\n /**\n * Whether tooltip is disabled\n * @default false\n */\n tooltipDisabled = input<boolean>(false);\n\n /**\n * Id of the tooltip element; while shown, the `aria-describedby` of the\n * described element (the host, or the focusable element it wraps, such as\n * `lc-button`'s `<button>`) points at it.\n */\n readonly tooltipId = `lc-tooltip-${++TooltipDirective.nextId}`;\n\n private overlayRef?: OverlayRef;\n private tooltipRef?: ComponentRef<TooltipContentComponent>;\n private positionStrategy?: FlexibleConnectedPositionStrategy;\n /** The element whose `aria-describedby` holds the tooltip id while shown. */\n private describedEl?: HTMLElement;\n private positionSubscription?: Subscription;\n private showTimeout?: number;\n private hideTimeout?: number;\n private escapeListener?: (event: KeyboardEvent) => void;\n\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly overlay = inject(Overlay);\n private readonly viewContainerRef = inject(ViewContainerRef);\n private readonly renderer = inject(Renderer2);\n private readonly overlayStack = inject(OverlayStackService);\n\n ngOnDestroy(): void {\n this.hide();\n this.cleanup();\n }\n\n @HostListener('mouseenter')\n onMouseEnter(): void {\n this.showWithDelay();\n }\n\n @HostListener('mouseleave')\n onMouseLeave(): void {\n this.hideWithDelay();\n }\n\n @HostListener('focus')\n onFocus(): void {\n this.showWithDelay();\n }\n\n @HostListener('blur')\n onBlur(): void {\n this.hideWithDelay();\n }\n\n /**\n * 3.32 (request 051, 050-f3): `focus` does not bubble, so focus on an\n * element inside the host (`lc-button`'s `<button>`) arrives as `focusin`.\n * The host's own focus is handled by `onFocus`, so it is skipped here.\n */\n @HostListener('focusin', ['$event'])\n onFocusIn(event: FocusEvent): void {\n if (event.target === this.elementRef.nativeElement) return;\n this.showWithDelay();\n }\n\n /** Hides when focus leaves the host from an element inside it (not when it moves within). */\n @HostListener('focusout', ['$event'])\n onFocusOut(event: FocusEvent): void {\n const host = this.elementRef.nativeElement;\n if (event.target === host) return;\n const next = event.relatedTarget as Node | null;\n if (next && host.contains(next)) return;\n this.hideWithDelay();\n }\n\n /**\n * Show tooltip programmatically\n */\n show(): void {\n if (this.tooltipDisabled() || !this.lcTooltip()) {\n return;\n }\n\n this.clearTimeouts();\n\n if (!this.overlayRef) {\n this.createOverlay();\n }\n\n if (!this.tooltipRef && this.overlayRef) {\n // Resolved at every show: the wrapped control is described, and it is\n // the anchor when it is the host's only child (3.32, request 051)\n const control = this.resolveControl();\n this.positionStrategy?.setOrigin(this.resolveAnchor(control));\n\n const portal = new ComponentPortal(TooltipContentComponent, this.viewContainerRef);\n this.tooltipRef = this.overlayRef.attach(portal) as ComponentRef<TooltipContentComponent>;\n this.tooltipRef.instance.content = this.lcTooltip();\n\n const tooltipEl = this.tooltipRef.location.nativeElement as HTMLElement;\n // The id is what the described element's aria-describedby points at\n this.renderer.setAttribute(tooltipEl, 'id', this.tooltipId);\n // The overlay renders under <body>, outside the host's lang (WCAG 3.1.2):\n // the tooltip takes the host's closest [lang], read at every show\n const lang = closestLang(this.elementRef.nativeElement);\n if (lang !== null) this.renderer.setAttribute(tooltipEl, 'lang', lang);\n this.addDescribedBy(control);\n this.setPositionClass(this.tooltipPosition());\n\n // Escape dismisses the tooltip (WCAG 1.4.13) — only when it is the\n // top-most overlay, so a modal underneath is not closed by the same key\n this.overlayStack.push(this.tooltipId);\n if (typeof document !== 'undefined') {\n this.escapeListener = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || !this.overlayStack.claim(this.tooltipId, event)) return;\n event.stopPropagation();\n this.hide();\n };\n document.addEventListener('keydown', this.escapeListener);\n }\n }\n }\n\n /**\n * Hide tooltip programmatically\n */\n hide(): void {\n this.clearTimeouts();\n\n if (this.escapeListener) {\n document.removeEventListener('keydown', this.escapeListener);\n this.escapeListener = undefined;\n }\n this.overlayStack.remove(this.tooltipId);\n\n if (this.tooltipRef) {\n this.tooltipRef.destroy();\n this.tooltipRef = undefined;\n }\n\n if (this.overlayRef) {\n this.overlayRef.detach();\n }\n\n this.removeDescribedBy();\n }\n\n /**\n * Toggle tooltip visibility\n */\n toggle(): void {\n if (this.tooltipRef) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n private showWithDelay(): void {\n this.clearTimeouts();\n const delay = this.tooltipShowDelay();\n\n if (delay > 0) {\n this.showTimeout = window.setTimeout(() => this.show(), delay);\n } else {\n this.show();\n }\n }\n\n private hideWithDelay(): void {\n this.clearTimeouts();\n const delay = this.tooltipHideDelay();\n\n if (delay > 0) {\n this.hideTimeout = window.setTimeout(() => this.hide(), delay);\n } else {\n this.hide();\n }\n }\n\n private createOverlay(): void {\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(this.elementRef)\n .withPositions(this.getPositions());\n this.positionStrategy = positionStrategy;\n\n // Keep the tooltip element's position class in sync with the side the\n // overlay actually chose (it flips at viewport edges)\n this.positionSubscription = positionStrategy.positionChanges.subscribe((change) => {\n const applied = POSITION_NAMES.find((p) => POSITIONS[p] === change.connectionPair);\n if (applied) this.setPositionClass(applied);\n });\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.overlay.scrollStrategies.reposition(),\n }) as OverlayRef;\n }\n\n /** Preferred position first, then the fallbacks the overlay may flip to. */\n private getPositions(): ConnectedPosition[] {\n const position = this.tooltipPosition();\n return [position, ...FALLBACKS[position]].map((p) => POSITIONS[p]);\n }\n\n private setPositionClass(position: TooltipPosition): void {\n const tooltipEl = this.tooltipRef?.location.nativeElement as HTMLElement | undefined;\n if (!tooltipEl) return;\n for (const p of POSITION_NAMES) {\n this.renderer.removeClass(tooltipEl, `lc-tooltip--${p}`);\n }\n this.renderer.addClass(tooltipEl, `lc-tooltip--${position}`);\n }\n\n /** The described element: the host if it is a control, else the first control inside it, else the host. */\n private resolveControl(): HTMLElement {\n const host = this.elementRef.nativeElement;\n if (host.matches(CONTROL)) return host;\n return host.querySelector<HTMLElement>(CONTROL) ?? host;\n }\n\n /** The overlay origin: the control when it is the host's only element child (a wrapper), else the host. */\n private resolveAnchor(control: HTMLElement): HTMLElement {\n const host = this.elementRef.nativeElement;\n return control !== host && host.childElementCount === 1 && host.firstElementChild === control ? control : host;\n }\n\n /** Append the tooltip id to the element's aria-describedby without clobbering ids the caller set. */\n private addDescribedBy(el: HTMLElement): void {\n this.describedEl = el;\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean);\n if (!ids.includes(this.tooltipId)) ids.push(this.tooltipId);\n this.renderer.setAttribute(el, 'aria-describedby', ids.join(' '));\n }\n\n /** Removes the tooltip id from the element it was added to, keeping the other ids. */\n private removeDescribedBy(): void {\n const el = this.describedEl ?? this.elementRef.nativeElement;\n this.describedEl = undefined;\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean);\n if (!ids.includes(this.tooltipId)) return;\n const rest = ids.filter((id) => id !== this.tooltipId);\n if (rest.length) {\n this.renderer.setAttribute(el, 'aria-describedby', rest.join(' '));\n } else {\n this.renderer.removeAttribute(el, 'aria-describedby');\n }\n }\n\n private clearTimeouts(): void {\n if (this.showTimeout) {\n window.clearTimeout(this.showTimeout);\n this.showTimeout = undefined;\n }\n if (this.hideTimeout) {\n window.clearTimeout(this.hideTimeout);\n this.hideTimeout = undefined;\n }\n }\n\n private cleanup(): void {\n this.clearTimeouts();\n this.positionSubscription?.unsubscribe();\n this.positionSubscription = undefined;\n this.positionStrategy = undefined;\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = undefined;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-tooltip.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/tooltip/tooltip.directive.ts"],"sourcesContent":["import {\n Directive,\n input,\n ElementRef,\n OnDestroy,\n Renderer2,\n ViewContainerRef,\n ComponentRef,\n HostListener,\n Component,\n ChangeDetectionStrategy,\n inject,\n} from '@angular/core';\nimport { Overlay, OverlayRef, ConnectedPosition, FlexibleConnectedPositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { Subscription } from 'rxjs';\nimport { OverlayStackService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { closestLang } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Internal tooltip content component\n */\n@Component({\n selector: 'lc-tooltip-content',\n standalone: true,\n imports: [],\n template: `<div class=\"lc-tooltip-inner\">{{ content }}</div>`,\n styles: [\n `\n :host {\n display: block;\n /* Inverted surface: reads as a tooltip in both themes and keeps AAA\n contrast — near-white on the dark theme, near-black on the light one. */\n background-color: var(--color-text-primary);\n color: var(--color-surface);\n padding: 0.5rem 0.75rem;\n border-radius: var(--border-radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n line-height: 1.25rem;\n max-width: 300px;\n word-wrap: break-word;\n box-shadow: var(--elevation-2);\n }\n\n .lc-tooltip-inner {\n position: relative;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'lc-tooltip',\n role: 'tooltip',\n },\n})\nexport class TooltipContentComponent {\n content = '';\n}\n\nconst POSITIONS: Record<TooltipPosition, ConnectedPosition> = {\n top: {\n originX: 'center',\n originY: 'top',\n overlayX: 'center',\n overlayY: 'bottom',\n offsetY: -8,\n panelClass: 'lc-tooltip--top',\n },\n bottom: {\n originX: 'center',\n originY: 'bottom',\n overlayX: 'center',\n overlayY: 'top',\n offsetY: 8,\n panelClass: 'lc-tooltip--bottom',\n },\n left: {\n originX: 'start',\n originY: 'center',\n overlayX: 'end',\n overlayY: 'center',\n offsetX: -8,\n panelClass: 'lc-tooltip--left',\n },\n right: {\n originX: 'end',\n originY: 'center',\n overlayX: 'start',\n overlayY: 'center',\n offsetX: 8,\n panelClass: 'lc-tooltip--right',\n },\n};\n\nconst POSITION_NAMES = Object.keys(POSITIONS) as TooltipPosition[];\n\n/** Fallback order when the preferred side does not fit the viewport. */\nconst FALLBACKS: Record<TooltipPosition, TooltipPosition[]> = {\n top: ['bottom', 'right', 'left'],\n bottom: ['top', 'right', 'left'],\n left: ['right', 'top', 'bottom'],\n right: ['left', 'top', 'bottom'],\n};\n\n/**\n * What the tooltip describes (3.32, request 051): the host when it matches,\n * else the first element inside it that does (`lc-button`'s `<button>`).\n */\nconst CONTROL =\n 'a[href], button, input:not([type=hidden]), select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Tooltip directive for displaying contextual information.\n * Uses Angular CDK Overlay for positioning; flips to the opposite side when\n * the preferred one does not fit the viewport, and can be dismissed with\n * Escape (WCAG 1.4.13).\n *\n * The tooltip takes the `lang` of the host's closest `[lang]` (3.31).\n *\n * Shows on hover and on keyboard focus of the host or of an element inside it\n * (3.32). On a wrapper such as `lc-button`, the tooltip describes and anchors\n * to the wrapped `<button>`. On a host with several controls, the one that\n * has focus is described (3.33); on hover, the first.\n *\n * @example\n * ```html\n * <button lcTooltip=\"Click me!\" tooltipPosition=\"top\">\n * Hover or focus\n * </button>\n * ```\n */\n@Directive({\n selector: '[lcTooltip]',\n standalone: true,\n})\nexport class TooltipDirective implements OnDestroy {\n private static nextId = 0;\n\n /**\n * Tooltip content text\n */\n lcTooltip = input.required<string>();\n\n /**\n * Tooltip position\n * @default 'top'\n */\n tooltipPosition = input<TooltipPosition>('top');\n\n /**\n * Show delay in milliseconds\n * @default 0\n */\n tooltipShowDelay = input<number>(0);\n\n /**\n * Hide delay in milliseconds\n * @default 0\n */\n tooltipHideDelay = input<number>(0);\n\n /**\n * Whether tooltip is disabled\n * @default false\n */\n tooltipDisabled = input<boolean>(false);\n\n /**\n * Id of the tooltip element; while shown, the `aria-describedby` of the\n * described element (the host, or the focusable element it wraps, such as\n * `lc-button`'s `<button>`) points at it.\n */\n readonly tooltipId = `lc-tooltip-${++TooltipDirective.nextId}`;\n\n private overlayRef?: OverlayRef;\n private tooltipRef?: ComponentRef<TooltipContentComponent>;\n private positionStrategy?: FlexibleConnectedPositionStrategy;\n /** The element whose `aria-describedby` holds the tooltip id while shown. */\n private describedEl?: HTMLElement;\n private positionSubscription?: Subscription;\n private showTimeout?: number;\n private hideTimeout?: number;\n private escapeListener?: (event: KeyboardEvent) => void;\n\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly overlay = inject(Overlay);\n private readonly viewContainerRef = inject(ViewContainerRef);\n private readonly renderer = inject(Renderer2);\n private readonly overlayStack = inject(OverlayStackService);\n\n ngOnDestroy(): void {\n this.hide();\n this.cleanup();\n }\n\n @HostListener('mouseenter')\n onMouseEnter(): void {\n this.showWithDelay();\n }\n\n @HostListener('mouseleave')\n onMouseLeave(): void {\n this.hideWithDelay();\n }\n\n @HostListener('focus')\n onFocus(): void {\n this.showWithDelay();\n }\n\n @HostListener('blur')\n onBlur(): void {\n this.hideWithDelay();\n }\n\n /**\n * 3.32 (request 051, 050-f3): `focus` does not bubble, so focus on an\n * element inside the host (`lc-button`'s `<button>`) arrives as `focusin`.\n * The host's own focus is handled by `onFocus`, so it is skipped here.\n */\n @HostListener('focusin', ['$event'])\n onFocusIn(event: FocusEvent): void {\n if (event.target === this.elementRef.nativeElement) return;\n // 3.33 (request 052, 051-n1): focus moved to another control inside the\n // host while the tooltip shows: that control is described now.\n const target = event.target as HTMLElement | null;\n if (this.tooltipRef && target?.matches?.(CONTROL) && target !== this.describedEl) {\n this.removeDescribedBy();\n this.addDescribedBy(target);\n }\n this.showWithDelay();\n }\n\n /** Hides when focus leaves the host from an element inside it (not when it moves within). */\n @HostListener('focusout', ['$event'])\n onFocusOut(event: FocusEvent): void {\n const host = this.elementRef.nativeElement;\n if (event.target === host) return;\n const next = event.relatedTarget as Node | null;\n if (next && host.contains(next)) return;\n this.hideWithDelay();\n }\n\n /**\n * Show tooltip programmatically\n */\n show(): void {\n if (this.tooltipDisabled() || !this.lcTooltip()) {\n return;\n }\n\n this.clearTimeouts();\n\n if (!this.overlayRef) {\n this.createOverlay();\n }\n\n if (!this.tooltipRef && this.overlayRef) {\n // Resolved at every show: the wrapped control is described, and it is\n // the anchor when it is the host's only child (3.32, request 051)\n const control = this.resolveControl();\n this.positionStrategy?.setOrigin(this.resolveAnchor(control));\n\n const portal = new ComponentPortal(TooltipContentComponent, this.viewContainerRef);\n this.tooltipRef = this.overlayRef.attach(portal) as ComponentRef<TooltipContentComponent>;\n this.tooltipRef.instance.content = this.lcTooltip();\n\n const tooltipEl = this.tooltipRef.location.nativeElement as HTMLElement;\n // The id is what the described element's aria-describedby points at\n this.renderer.setAttribute(tooltipEl, 'id', this.tooltipId);\n // The overlay renders under <body>, outside the host's lang (WCAG 3.1.2):\n // the tooltip takes the host's closest [lang], read at every show\n const lang = closestLang(this.elementRef.nativeElement);\n if (lang !== null) this.renderer.setAttribute(tooltipEl, 'lang', lang);\n this.addDescribedBy(control);\n this.setPositionClass(this.tooltipPosition());\n\n // Escape dismisses the tooltip (WCAG 1.4.13) — only when it is the\n // top-most overlay, so a modal underneath is not closed by the same key\n this.overlayStack.push(this.tooltipId);\n if (typeof document !== 'undefined') {\n this.escapeListener = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || !this.overlayStack.claim(this.tooltipId, event)) return;\n event.stopPropagation();\n this.hide();\n };\n document.addEventListener('keydown', this.escapeListener);\n }\n }\n }\n\n /**\n * Hide tooltip programmatically\n */\n hide(): void {\n this.clearTimeouts();\n\n if (this.escapeListener) {\n document.removeEventListener('keydown', this.escapeListener);\n this.escapeListener = undefined;\n }\n this.overlayStack.remove(this.tooltipId);\n\n if (this.tooltipRef) {\n this.tooltipRef.destroy();\n this.tooltipRef = undefined;\n }\n\n if (this.overlayRef) {\n this.overlayRef.detach();\n }\n\n this.removeDescribedBy();\n }\n\n /**\n * Toggle tooltip visibility\n */\n toggle(): void {\n if (this.tooltipRef) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n private showWithDelay(): void {\n this.clearTimeouts();\n const delay = this.tooltipShowDelay();\n\n if (delay > 0) {\n this.showTimeout = window.setTimeout(() => this.show(), delay);\n } else {\n this.show();\n }\n }\n\n private hideWithDelay(): void {\n this.clearTimeouts();\n const delay = this.tooltipHideDelay();\n\n if (delay > 0) {\n this.hideTimeout = window.setTimeout(() => this.hide(), delay);\n } else {\n this.hide();\n }\n }\n\n private createOverlay(): void {\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(this.elementRef)\n .withPositions(this.getPositions());\n this.positionStrategy = positionStrategy;\n\n // Keep the tooltip element's position class in sync with the side the\n // overlay actually chose (it flips at viewport edges)\n this.positionSubscription = positionStrategy.positionChanges.subscribe((change) => {\n const applied = POSITION_NAMES.find((p) => POSITIONS[p] === change.connectionPair);\n if (applied) this.setPositionClass(applied);\n });\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.overlay.scrollStrategies.reposition(),\n }) as OverlayRef;\n }\n\n /** Preferred position first, then the fallbacks the overlay may flip to. */\n private getPositions(): ConnectedPosition[] {\n const position = this.tooltipPosition();\n return [position, ...FALLBACKS[position]].map((p) => POSITIONS[p]);\n }\n\n private setPositionClass(position: TooltipPosition): void {\n const tooltipEl = this.tooltipRef?.location.nativeElement as HTMLElement | undefined;\n if (!tooltipEl) return;\n for (const p of POSITION_NAMES) {\n this.renderer.removeClass(tooltipEl, `lc-tooltip--${p}`);\n }\n this.renderer.addClass(tooltipEl, `lc-tooltip--${position}`);\n }\n\n /**\n * The described element: the host if it is a control, else the control\n * inside it that has focus (3.33, request 052, 051-n1: a host with several\n * controls), else the first control inside it, else the host.\n */\n private resolveControl(): HTMLElement {\n const host = this.elementRef.nativeElement;\n if (host.matches(CONTROL)) return host;\n const focused = host.ownerDocument?.activeElement as HTMLElement | null | undefined;\n if (focused && focused !== host && host.contains(focused) && focused.matches(CONTROL)) return focused;\n return host.querySelector<HTMLElement>(CONTROL) ?? host;\n }\n\n /** The overlay origin: the control when it is the host's only element child (a wrapper), else the host. */\n private resolveAnchor(control: HTMLElement): HTMLElement {\n const host = this.elementRef.nativeElement;\n return control !== host && host.childElementCount === 1 && host.firstElementChild === control ? control : host;\n }\n\n /** Append the tooltip id to the element's aria-describedby without clobbering ids the caller set. */\n private addDescribedBy(el: HTMLElement): void {\n this.describedEl = el;\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean);\n if (!ids.includes(this.tooltipId)) ids.push(this.tooltipId);\n this.renderer.setAttribute(el, 'aria-describedby', ids.join(' '));\n }\n\n /** Removes the tooltip id from the element it was added to, keeping the other ids. */\n private removeDescribedBy(): void {\n const el = this.describedEl ?? this.elementRef.nativeElement;\n this.describedEl = undefined;\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean);\n if (!ids.includes(this.tooltipId)) return;\n const rest = ids.filter((id) => id !== this.tooltipId);\n if (rest.length) {\n this.renderer.setAttribute(el, 'aria-describedby', rest.join(' '));\n } else {\n this.renderer.removeAttribute(el, 'aria-describedby');\n }\n }\n\n private clearTimeouts(): void {\n if (this.showTimeout) {\n window.clearTimeout(this.showTimeout);\n this.showTimeout = undefined;\n }\n if (this.hideTimeout) {\n window.clearTimeout(this.hideTimeout);\n this.hideTimeout = undefined;\n }\n }\n\n private cleanup(): void {\n this.clearTimeouts();\n this.positionSubscription?.unsubscribe();\n this.positionSubscription = undefined;\n this.positionStrategy = undefined;\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = undefined;\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifecorner/angular-ui-kit",
3
- "version": "3.32.0",
3
+ "version": "3.33.0",
4
4
  "description": "LifeCorner Design System – Angular UI component library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -457,13 +457,18 @@ describe('Component pairings of 3.19.1 (request 025)', () => {
457
457
  for (const rule of chipRules) expect(rule[4]).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|:\s*white\b/i);
458
458
  });
459
459
 
460
- it('AC-13: in forced colours the pressed chip keeps a Highlight outline unless it is focused', () => {
460
+ // 3.33 (request 052, 044-n3, AC-16): the pressed mark in forced colours is
461
+ // a Highlight underline (the rich-text-editor's), not the focus ring's
462
+ // 2 px outline, and it stays while the chip is focused.
463
+ it('AC-13: in forced colours the pressed chip is underlined in Highlight, also while focused', () => {
461
464
  const at = ncCss.indexOf('@media (forced-colors: active)');
462
465
  expect(at).toBeGreaterThan(-1);
463
466
  const media = ncCss.slice(at, ncCss.indexOf('}\n}', at));
464
- expect(media).toMatch(/\.lc-nc__filter--active:not\(:focus-visible\) \{/);
465
- expect(media).toMatch(/outline: 2px solid Highlight;/);
466
- expect(media).toMatch(/outline-offset: 1px;/);
467
+ expect(media).toMatch(
468
+ /\.lc-nc__filter\[aria-pressed=true\] \{\s*text-decoration-line: underline;\s*text-decoration-thickness: 2px;\s*text-underline-offset: 4px;\s*text-decoration-color: Highlight;/,
469
+ );
470
+ expect(media).not.toMatch(/:not\(:focus-visible\)/);
471
+ expect(media).not.toMatch(/outline: 2px solid Highlight;/);
467
472
  });
468
473
 
469
474
  /** Design 025 §5, per palette [light, dark]: the minimum over both panel ends. */
@@ -735,12 +740,15 @@ describe('Component pairings of 3.21.1 (request 029)', () => {
735
740
  expect(sheets.nc).not.toMatch(/#3b82f6|#22c55e|#f59e0b|#ef4444/i);
736
741
  });
737
742
 
738
- it('D-2: the level pills stay at full opacity; the pressed pill gets an outline in forced colours', () => {
743
+ // 3.33 (request 052, 044-n3, AC-16): the forced-colours mark is the
744
+ // rich-text-editor's Highlight underline (was a 2 px outline, hidden on focus).
745
+ it('D-2: the level pills stay at full opacity; the pressed pill is underlined in forced colours', () => {
739
746
  expect(fills('log', '.lc-log-viewer__level-pill').opacity).toBeUndefined();
740
747
  expect(fills('log', '.lc-log-viewer__level-pill--active').opacity).toBeUndefined();
741
748
  expect(sheets.log).toMatch(
742
- /@media \(forced-colors: active\) \{\s*\.lc-log-viewer__level-pill--active:not\(:focus-visible\) \{\s*outline: 2px solid Highlight;\s*outline-offset: 1px;\s*\}/,
749
+ /@media \(forced-colors: active\) \{\s*\.lc-log-viewer__level-pill\[aria-pressed=true\] \{\s*text-decoration-line: underline;\s*text-decoration-thickness: 2px;\s*text-underline-offset: 4px;\s*text-decoration-color: Highlight;\s*\}/,
743
750
  );
751
+ expect(sheets.log).not.toMatch(/level-pill--active:not\(:focus-visible\)/);
744
752
  });
745
753
 
746
754
  it('AC-10: the progress track is unchanged; primary reads the theme token (request 039), secondary the data mark (036)', () => {
@@ -136,8 +136,9 @@ export declare class BottomNavComponent {
136
136
  * tasks on the pages behind it: drawn like an item badge and read after the
137
137
  * label through `badgeAriaLabel`. The kit doesn't add up the hidden
138
138
  * destinations' badges; pass the value to show. It sits on the bar's last
139
- * entry, so keep it to two characters (`'9+'`). `null` or `undefined`: no
140
- * badge.
139
+ * entry; values up to three characters (`'99+'`) fit: on the last entry a
140
+ * badge that would pass the entry's edge moves left until it ends there
141
+ * (3.33). `null` or `undefined`: no badge.
141
142
  *
142
143
  * @default null
143
144
  */
@@ -128,7 +128,12 @@ export declare class ChatComponent {
128
128
  multiple: import("@angular/core").InputSignal<boolean>;
129
129
  /** Maximum file size in bytes. `0` disables the check. */
130
130
  maxFileSize: import("@angular/core").InputSignal<number>;
131
- /** BCP 47 locale of the message times (hour and minute). */
131
+ /**
132
+ * BCP 47 locale of the message times (hour and minute). Attachment sizes
133
+ * follow the host's language (closest `lang`, 3.33), English without one:
134
+ * `240,0 KB` under `lang="de"`.
135
+ * @default 'de-DE'
136
+ */
132
137
  locale: import("@angular/core").InputSignal<string>;
133
138
  /** Header badge while a message is streaming. */
134
139
  streamingLabel: import("@angular/core").InputSignal<string>;
@@ -211,7 +216,15 @@ export declare class ChatComponent {
211
216
  protected openFilePicker(): void;
212
217
  protected onFilesSelected(event: Event): void;
213
218
  protected removeAttachment(id: string): void;
219
+ private readonly host;
220
+ /**
221
+ * Attachment size with one decimal in the host's language (3.33, request
222
+ * 052, 044-n2): the closest `lang`, English without one (or when it is
223
+ * empty or invalid), so `240.0 KB` by default and `240,0 KB` under
224
+ * `lang="de"`. No digit grouping, as before.
225
+ */
214
226
  protected formatBytes(size: number | undefined): string;
227
+ private decimalFormat;
215
228
  protected isImage(att: ChatAttachment): boolean;
216
229
  private addFiles;
217
230
  private matchesAccept;