@lifecorner/angular-ui-kit 3.27.0 → 3.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +4 -4
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_theme-dark.scss +154 -137
- package/src/styles/_theme-light.scss +158 -138
- package/src/styles/focus-indicators.spec.ts +17 -8
- package/src/styles/theme-contrast.spec.ts +116 -2
- package/src/styles/theme-modes.spec.ts +40 -0
- package/src/styles/touch-targets-contrast.spec.ts +289 -0
|
@@ -355,7 +355,7 @@ var NotificationCenterComponent = class NotificationCenterComponent {
|
|
|
355
355
|
},
|
|
356
356
|
ngImport: i0,
|
|
357
357
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
358
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:
|
|
358
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{position:relative;border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:before{content:\"\";position:absolute;inset-inline:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2);top:calc(50% - var(--size-min-touch-height, 2.75rem) / 2);bottom:calc(50% - var(--size-min-touch-height, 2.75rem) / 2)}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:9px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{position:relative;border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:before{content:\"\";position:absolute;inset-inline:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2);top:calc(50% - var(--size-min-touch-height, 2.75rem) / 2);bottom:calc(50% - var(--size-min-touch-height, 2.75rem) / 2)}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}.lc-nc__filter:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible,.lc-nc__action-btn:focus-visible,.lc-nc__filter:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"],
|
|
359
359
|
dependencies: [{
|
|
360
360
|
kind: "component",
|
|
361
361
|
type: IconComponent,
|
|
@@ -418,7 +418,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
418
418
|
])],
|
|
419
419
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
420
420
|
template: "<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n",
|
|
421
|
-
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:
|
|
421
|
+
styles: [":host{display:block}.lc-nc{border:1px solid var(--color-divider);border-radius:var(--border-radius-lg);background-color:var(--color-surface);background-image:var(--gradient-surface);overflow:hidden}.lc-nc__header{display:flex;flex-wrap:wrap;row-gap:var(--spacing-1);align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__header-left{display:flex;align-items:center;gap:8px}.lc-nc__title{margin:0;font-size:var(--typography-font-size-base, 1rem);font-weight:600;color:var(--color-text-primary)}.lc-nc__badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:var(--border-radius-full);background:var(--color-error-fill);color:var(--color-on-error);font-size:.6875rem;font-weight:600}.lc-nc__header-actions{display:flex;gap:8px;margin-inline-start:auto}.lc-nc__action-btn{position:relative;border:none;background:none;padding:4px 8px;border-radius:var(--border-radius-sm);font-size:.75rem;font-weight:500;cursor:pointer;color:var(--color-primary-600);transition:background .15s}.lc-nc__action-btn:before{content:\"\";position:absolute;inset-inline:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2);top:calc(50% - var(--size-min-touch-height, 2.75rem) / 2);bottom:calc(50% - var(--size-min-touch-height, 2.75rem) / 2)}.lc-nc__action-btn:hover{background:var(--color-primary-50)}.lc-nc__action-btn--danger{color:var(--color-error-default)}.lc-nc__action-btn--danger:hover{background:#9d0e0e29}.lc-nc__action-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__search{padding:8px 16px;border-bottom:1px solid var(--color-divider)}.lc-nc__search-field{width:100%;display:block}.lc-nc__filters{display:flex;gap:4px;padding:9px 16px;border-bottom:1px solid var(--color-divider);overflow-x:auto}.lc-nc__filter{position:relative;border:1px solid var(--color-divider);background:transparent;padding:4px 10px;border-radius:var(--border-radius-full);font-size:.6875rem;font-weight:500;cursor:pointer;white-space:nowrap;color:var(--color-text-secondary);transition:all .15s}.lc-nc__filter:before{content:\"\";position:absolute;inset-inline:min(0px,(100% - var(--size-min-touch-height, 2.75rem)) / 2);top:calc(50% - var(--size-min-touch-height, 2.75rem) / 2);bottom:calc(50% - var(--size-min-touch-height, 2.75rem) / 2)}.lc-nc__filter:hover:not(.lc-nc__filter--active){background:var(--color-surface-2)}.lc-nc__filter--active{background:var(--color-primary-fill);color:var(--color-on-primary);border-color:var(--color-primary-500)}.lc-nc__filter:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__filter--active:not(:focus-visible){outline:2px solid Highlight;outline-offset:1px}}.lc-nc__list{overflow-y:auto}.lc-nc__group-header{padding:6px 16px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-tertiary);background:var(--color-surface-2);border-bottom:1px solid var(--color-divider)}.lc-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid var(--color-divider);cursor:pointer;transition:background .15s}.lc-nc__item:hover{background:var(--color-surface-2)}.lc-nc__item:last-child{border-bottom:none}.lc-nc__item--unread,.lc-nc__item--unread:hover{background:var(--color-surface-selected)}.lc-nc__item--urgent{border-left:3px solid var(--color-error)}.lc-nc__item-icon{flex-shrink:0;margin-top:2px}.lc-nc__item-icon--info{color:var(--color-on-info-subtle)}.lc-nc__item-icon--success{color:var(--color-on-success-subtle)}.lc-nc__item-icon--warning{color:var(--color-on-warning-subtle)}.lc-nc__item-icon--error{color:var(--color-on-error-subtle)}.lc-nc__item-body{flex:1;min-width:0}.lc-nc__item-header{display:flex;align-items:center;gap:6px}.lc-nc__item-title{display:block;min-width:0;max-width:100%;padding:0;border:none;background:none;font:inherit;font-size:var(--typography-font-size-sm, .875rem);font-weight:500;text-align:left;color:var(--color-text-primary);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lc-nc__item-title:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-title:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item--unread .lc-nc__item-title{font-weight:600}.lc-nc__item-priority{font-size:.625rem;font-weight:600;padding:1px 5px;border-radius:var(--border-radius-sm);flex-shrink:0;text-transform:uppercase}.lc-nc__item-priority--high{background:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-nc__item-priority--urgent{background:var(--color-error-subtle);color:var(--color-on-error-subtle)}.lc-nc__item-priority--low{background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item-message{margin:2px 0 0;font-size:.75rem;color:var(--color-text-tertiary);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.lc-nc__item-footer{display:flex;align-items:center;gap:8px;margin-top:4px}.lc-nc__item-time{font-size:.6875rem;color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-message,.lc-nc__item--unread .lc-nc__item-time{color:var(--color-text-secondary)}.lc-nc__item-category{font-size:.625rem;padding:1px 5px;border-radius:var(--border-radius-sm);background:var(--color-surface-hover);color:var(--color-text-tertiary)}.lc-nc__item--unread .lc-nc__item-priority,.lc-nc__item--unread .lc-nc__item-category{background-color:var(--color-surface)}.lc-nc__item--unread .lc-nc__item-priority--high{background-image:linear-gradient(var(--color-warning-subtle),var(--color-warning-subtle))}.lc-nc__item--unread .lc-nc__item-priority--urgent{background-image:linear-gradient(var(--color-error-subtle),var(--color-error-subtle))}.lc-nc__item--unread .lc-nc__item-priority--low,.lc-nc__item--unread .lc-nc__item-category{background-image:linear-gradient(var(--color-surface-hover),var(--color-surface-hover))}.lc-nc__item-action{border:none;background:none;padding:0;font-size:.6875rem;font-weight:600;color:var(--color-primary-600);cursor:pointer}.lc-nc__item-action:hover{text-decoration:underline}.lc-nc__item-action:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-nc__item-action:focus-visible{border-radius:var(--border-radius-sm)}.lc-nc__item-dismiss{flex-shrink:0;width:24px;height:24px;border:none;border-radius:var(--border-radius-sm);background:transparent;cursor:pointer;font-size:16px;color:var(--color-text-tertiary);display:flex;align-items:center;justify-content:center;transition:background .1s,color .1s;opacity:0}.lc-nc__item:hover .lc-nc__item-dismiss,.lc-nc__item:focus-within .lc-nc__item-dismiss{opacity:1}.lc-nc__item-dismiss:hover{background:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-nc__item-dismiss:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@media(forced-colors:active){.lc-nc__item-title:focus-visible,.lc-nc__item-action:focus-visible,.lc-nc__item-dismiss:focus-visible,.lc-nc__action-btn:focus-visible,.lc-nc__filter:focus-visible{outline-color:Highlight}}.lc-nc__empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 16px;color:var(--color-text-tertiary)}.lc-nc__empty-icon{font-size:32px;opacity:.5}.lc-nc__empty-text{font-size:var(--typography-font-size-sm, .875rem)}\n"]
|
|
422
422
|
}]
|
|
423
423
|
}],
|
|
424
424
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\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} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-notification-center.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.ts","../../../../libs/angular-ui-kit/src/lib/notification-center/notification-center.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n signal,\n computed,\n output,\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} from '@lifecorner/angular-ui-kit/icons';\nimport { SearchInputComponent } from '@lifecorner/angular-ui-kit/search-input';\n\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\nexport type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';\n\nexport interface Notification {\n id: string;\n title: string;\n message?: string;\n type?: NotificationType;\n priority?: NotificationPriority;\n timestamp: Date;\n read?: boolean;\n icon?: string;\n category?: string;\n actionLabel?: string;\n actionUrl?: string;\n}\n\nconst TYPE_ICONS: Record<NotificationType, string> = {\n info: 'information-circle',\n success: 'check-circle',\n warning: 'exclamation-triangle',\n error: 'x-circle',\n};\n\nconst PRIORITY_LABELS: Record<NotificationPriority, string> = {\n low: 'Low',\n normal: 'Normal',\n high: 'High',\n urgent: 'Urgent',\n};\n\n@Component({\n selector: 'lc-notification-center',\n standalone: true,\n imports: [IconComponent, SearchInputComponent],\n viewProviders: [ɵprovideKitIcons([ICON_INFORMATION_CIRCLE, ICON_CHECK_CIRCLE, ICON_EXCLAMATION_TRIANGLE, ICON_CIRCLE_X])],\n templateUrl: './notification-center.component.html',\n styleUrls: ['./notification-center.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NotificationCenterComponent {\n readonly notifications = input.required<Notification[]>();\n readonly title = input('Notifications');\n readonly showFilter = input(true);\n readonly showTimestamp = input(true);\n readonly showPriority = input(false);\n readonly emptyMessage = input('No notifications');\n readonly maxHeight = input('480px');\n readonly groupByCategory = input(false);\n /** Locale used for absolute dates (older than a week). */\n readonly dateLocale = input('de-DE');\n\n /** Label of the mark-all-read action. */\n readonly markAllReadLabel = input('Mark all read');\n\n /** Label of the clear-all action. */\n readonly clearAllLabel = input('Clear all');\n\n /** Placeholder of the search field. */\n readonly searchPlaceholder = input('Search notifications…');\n\n /** Label of the \"all\" filter (the count follows in parentheses). */\n readonly filterAllLabel = input('All');\n\n /** Label of the error filter. */\n readonly filterErrorLabel = input('Error');\n\n /** Label of the warning filter. */\n readonly filterWarningLabel = input('Warning');\n\n /** Label of the success filter. */\n readonly filterSuccessLabel = input('Success');\n\n /** Label of the info filter. */\n readonly filterInfoLabel = input('Info');\n\n /** Accessible name of a dismiss button, followed by the notification's title. */\n readonly dismissAriaLabel = input('Dismiss notification:');\n\n /** Group header for notifications without `category` (`groupByCategory`). */\n readonly otherCategoryLabel = input('Other');\n\n /** Priority badge for `low` (`showPriority`). */\n readonly lowPriorityLabel = input('Low');\n\n /** Priority badge for `high` (`showPriority`). */\n readonly highPriorityLabel = input('High');\n\n /** Priority badge for `urgent` (`showPriority`). */\n readonly urgentPriorityLabel = input('Urgent');\n\n /** Timestamp of notifications younger than a minute. */\n readonly justNowLabel = input('Just now');\n\n /** Timestamp under an hour; `{n}` is replaced by the minutes. */\n readonly minutesAgoLabel = input('{n}m ago');\n\n /** Timestamp under a day; `{n}` is replaced by the hours. */\n readonly hoursAgoLabel = input('{n}h ago');\n\n /** Timestamp under a week; `{n}` is replaced by the days (older ones show the date in `dateLocale`). */\n readonly daysAgoLabel = input('{n}d ago');\n\n readonly notificationClick = output<Notification>();\n readonly notificationDismiss = output<string>();\n readonly notificationAction = output<Notification>();\n readonly markAllRead = output<void>();\n readonly clearAll = output<void>();\n\n protected activeFilter = signal<NotificationType | 'all'>('all');\n protected searchQuery = signal('');\n\n protected readonly unreadCount = computed(() =>\n this.notifications().filter(n => !n.read).length\n );\n\n protected readonly filteredNotifications = computed(() => {\n let items = this.notifications();\n const filter = this.activeFilter();\n const query = this.searchQuery().toLowerCase().trim();\n\n if (filter !== 'all') {\n items = items.filter(n => n.type === filter);\n }\n\n if (query) {\n items = items.filter(\n n =>\n n.title.toLowerCase().includes(query) ||\n n.message?.toLowerCase().includes(query) ||\n n.category?.toLowerCase().includes(query)\n );\n }\n\n // Copy before sorting: without a filter `items` is the caller's array and\n // `sort` would reorder it in place\n return [...items].sort((a, b) => {\n const prio = this.priorityWeight(b.priority) - this.priorityWeight(a.priority);\n if (prio !== 0) return prio;\n return b.timestamp.getTime() - a.timestamp.getTime();\n });\n });\n\n /**\n * The list as rendered: one group per category, or a single group without a\n * category (`null`, no header) when grouping is off.\n */\n protected readonly groupedNotifications = computed((): { category: string | null; items: Notification[] }[] => {\n const items = this.filteredNotifications();\n if (!this.groupByCategory()) return [{ category: null, items }];\n const map = new Map<string, Notification[]>();\n for (const n of items) {\n const cat = n.category || this.otherCategoryLabel();\n if (!map.has(cat)) map.set(cat, []);\n map.get(cat)!.push(n);\n }\n return Array.from(map.entries()).map(([category, items]) => ({ category, items }));\n });\n\n protected readonly filterCounts = computed(() => {\n const items = this.notifications();\n return {\n all: items.length,\n info: items.filter(n => n.type === 'info').length,\n success: items.filter(n => n.type === 'success').length,\n warning: items.filter(n => n.type === 'warning').length,\n error: items.filter(n => n.type === 'error').length,\n };\n });\n\n protected readonly hasNotifications = computed(() => this.filteredNotifications().length > 0);\n\n protected getTypeIcon(type?: NotificationType): string {\n return type ? TYPE_ICONS[type] : 'information-circle';\n }\n\n protected getPriorityLabel(priority?: NotificationPriority): string {\n switch (priority) {\n case 'low':\n return this.lowPriorityLabel();\n case 'high':\n return this.highPriorityLabel();\n case 'urgent':\n return this.urgentPriorityLabel();\n default:\n return priority ? PRIORITY_LABELS[priority] : '';\n }\n }\n\n protected formatTime(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return this.justNowLabel();\n if (mins < 60) return this.minutesAgoLabel().replace('{n}', String(mins));\n const hours = Math.floor(mins / 60);\n if (hours < 24) return this.hoursAgoLabel().replace('{n}', String(hours));\n const days = Math.floor(hours / 24);\n if (days < 7) return this.daysAgoLabel().replace('{n}', String(days));\n return date.toLocaleDateString(this.dateLocale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n }\n\n protected setFilter(filter: NotificationType | 'all'): void {\n this.activeFilter.set(filter);\n }\n\n protected onSearch(query: string): void {\n this.searchQuery.set(query);\n }\n\n protected onClick(notification: Notification): void {\n this.notificationClick.emit(notification);\n }\n\n protected onDismiss(id: string, event: Event): void {\n event.stopPropagation();\n this.notificationDismiss.emit(id);\n }\n\n protected onAction(notification: Notification, event: Event): void {\n event.stopPropagation();\n this.notificationAction.emit(notification);\n }\n\n protected onMarkAllRead(): void {\n this.markAllRead.emit();\n }\n\n protected onClearAll(): void {\n this.clearAll.emit();\n }\n\n private priorityWeight(p?: NotificationPriority): number {\n switch (p) {\n case 'urgent': return 4;\n case 'high': return 3;\n case 'normal': return 2;\n case 'low': return 1;\n default: return 2;\n }\n }\n}\n","<div class=\"lc-nc\">\n <!-- Header -->\n <div class=\"lc-nc__header\">\n <div class=\"lc-nc__header-left\">\n <h3 class=\"lc-nc__title\">{{ title() }}</h3>\n @if (unreadCount() > 0) {\n <span class=\"lc-nc__badge\">{{ unreadCount() }}</span>\n }\n </div>\n <div class=\"lc-nc__header-actions\">\n @if (unreadCount() > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn\" (click)=\"onMarkAllRead()\">{{ markAllReadLabel() }}</button>\n }\n @if (notifications().length > 0) {\n <button type=\"button\" class=\"lc-nc__action-btn lc-nc__action-btn--danger\" (click)=\"onClearAll()\">{{ clearAllLabel() }}</button>\n }\n </div>\n </div>\n\n <!-- Search -->\n <div class=\"lc-nc__search\">\n <lc-search-input\n class=\"lc-nc__search-field\"\n [placeholder]=\"searchPlaceholder()\"\n [debounceMs]=\"0\"\n (searchChange)=\"onSearch($event)\"\n />\n </div>\n\n <!-- Filters -->\n @if (showFilter()) {\n <div class=\"lc-nc__filters\">\n <button\n type=\"button\"\n class=\"lc-nc__filter\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'all'\"\n [attr.aria-pressed]=\"activeFilter() === 'all'\"\n (click)=\"setFilter('all')\"\n >{{ filterAllLabel() }} ({{ filterCounts().all }})</button>\n @if (filterCounts().error > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--error\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'error'\"\n [attr.aria-pressed]=\"activeFilter() === 'error'\"\n (click)=\"setFilter('error')\"\n >{{ filterErrorLabel() }} ({{ filterCounts().error }})</button>\n }\n @if (filterCounts().warning > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--warning\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'warning'\"\n [attr.aria-pressed]=\"activeFilter() === 'warning'\"\n (click)=\"setFilter('warning')\"\n >{{ filterWarningLabel() }} ({{ filterCounts().warning }})</button>\n }\n @if (filterCounts().success > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--success\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'success'\"\n [attr.aria-pressed]=\"activeFilter() === 'success'\"\n (click)=\"setFilter('success')\"\n >{{ filterSuccessLabel() }} ({{ filterCounts().success }})</button>\n }\n @if (filterCounts().info > 0) {\n <button\n type=\"button\"\n class=\"lc-nc__filter lc-nc__filter--info\"\n [class.lc-nc__filter--active]=\"activeFilter() === 'info'\"\n [attr.aria-pressed]=\"activeFilter() === 'info'\"\n (click)=\"setFilter('info')\"\n >{{ filterInfoLabel() }} ({{ filterCounts().info }})</button>\n }\n </div>\n }\n\n <!-- Notification list -->\n <div class=\"lc-nc__list\" [style.max-height]=\"maxHeight()\">\n @if (!hasNotifications()) {\n <div class=\"lc-nc__empty\">\n <lc-icon name=\"bell\" size=\"lg\" class=\"lc-nc__empty-icon\" [decorative]=\"true\"></lc-icon>\n <span class=\"lc-nc__empty-text\">{{ emptyMessage() }}</span>\n </div>\n } @else {\n @for (group of groupedNotifications(); track group.category ?? '') {\n <div class=\"lc-nc__group\">\n @if (group.category !== null) {\n <div class=\"lc-nc__group-header\">{{ group.category }}</div>\n }\n <!-- The whole row is a mouse target; the title is a real button so\n keyboard and screen-reader users can activate it (its click bubbles\n to the row), and the dismiss/action buttons stop propagation. -->\n @for (n of group.items; track n.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- row is a mouse target; its title is a real button for keyboard users -->\n <div\n class=\"lc-nc__item\"\n [class.lc-nc__item--unread]=\"!n.read\"\n [class.lc-nc__item--urgent]=\"n.priority === 'urgent'\"\n (click)=\"onClick(n)\"\n >\n <lc-icon [name]=\"getTypeIcon(n.type)\" size=\"sm\" class=\"lc-nc__item-icon\" [class]=\"'lc-nc__item-icon lc-nc__item-icon--' + (n.type || 'info')\" [decorative]=\"true\"></lc-icon>\n <div class=\"lc-nc__item-body\">\n <div class=\"lc-nc__item-header\">\n <button type=\"button\" class=\"lc-nc__item-title\">{{ n.title }}</button>\n @if (showPriority() && n.priority && n.priority !== 'normal') {\n <span [class]=\"'lc-nc__item-priority lc-nc__item-priority--' + n.priority\">\n {{ getPriorityLabel(n.priority) }}\n </span>\n }\n </div>\n @if (n.message) {\n <p class=\"lc-nc__item-message\">{{ n.message }}</p>\n }\n <div class=\"lc-nc__item-footer\">\n @if (showTimestamp()) {\n <span class=\"lc-nc__item-time\">{{ formatTime(n.timestamp) }}</span>\n }\n @if (n.category) {\n <span class=\"lc-nc__item-category\">{{ n.category }}</span>\n }\n @if (n.actionLabel) {\n <button type=\"button\" class=\"lc-nc__item-action\" (click)=\"onAction(n, $event)\">{{ n.actionLabel }}</button>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"lc-nc__item-dismiss\"\n (click)=\"onDismiss(n.id, $event)\"\n [attr.aria-label]=\"dismissAriaLabel() + ' ' + n.title\"\n >×</button>\n </div>\n }\n </div>\n }\n }\n </div>\n</div>\n"],"mappings":"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"}
|
|
@@ -527,8 +527,8 @@ var RichTextEditorComponent = class RichTextEditorComponent {
|
|
|
527
527
|
isSignal: true
|
|
528
528
|
}],
|
|
529
529
|
ngImport: i0,
|
|
530
|
-
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
531
|
-
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:
|
|
530
|
+
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n [attr.aria-pressed]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n [attr.aria-pressed]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n [attr.aria-pressed]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
531
|
+
styles: [".lc-rich-text-editor{border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:var(--size-min-touch-height, 2.75rem);height:var(--size-min-touch-height, 2.75rem);display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);min-width:var(--size-min-touch-height, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary-fill);color:var(--color-on-primary)}.lc-rich-text-editor__mode-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}@media(forced-colors:active){.lc-rich-text-editor__toolbar-btn:focus-visible,.lc-rich-text-editor__mode-btn:focus-visible{outline-color:Highlight}.lc-rich-text-editor__mode-btn[aria-pressed=true]{text-decoration-line:underline;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:Highlight}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:Highlight;outline-color:Highlight}}\n"],
|
|
532
532
|
dependencies: [{
|
|
533
533
|
kind: "ngmodule",
|
|
534
534
|
type: CommonModule
|
|
@@ -553,8 +553,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
553
553
|
useExisting: forwardRef(() => RichTextEditorComponent),
|
|
554
554
|
multi: true
|
|
555
555
|
}],
|
|
556
|
-
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
557
|
-
styles: [".lc-rich-text-editor{border:1px solid var(--color-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:
|
|
556
|
+
template: "<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n [attr.aria-pressed]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n [attr.aria-pressed]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n [attr.aria-pressed]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n",
|
|
557
|
+
styles: [".lc-rich-text-editor{border:1px solid var(--color-field-border);border-radius:var(--border-radius-md);background-color:var(--color-surface);overflow:hidden;font-family:var(--typography-font-family)}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:var(--color-primary-500);outline:2px solid var(--color-primary-500);outline-offset:0px}.lc-rich-text-editor--disabled{opacity:.6;pointer-events:none}.lc-rich-text-editor__toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));background-color:var(--color-surface);border-bottom:1px solid var(--color-divider);flex-wrap:wrap;gap:var(--lc-density-gap-xs, var(--spacing-1))}.lc-rich-text-editor__toolbar-actions{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.lc-rich-text-editor__toolbar-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);width:var(--size-min-touch-height, 2.75rem);height:var(--size-min-touch-height, 2.75rem);display:flex;align-items:center;justify-content:center;border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-sm);font-weight:var(--typography-font-weight-semibold);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__toolbar-btn:hover:not(:disabled){background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__toolbar-btn:disabled{cursor:not-allowed;opacity:.4}.lc-rich-text-editor__toolbar-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__toolbar-modes{display:flex;align-items:center;gap:2px;border-left:1px solid var(--color-divider);padding-left:var(--spacing-2);margin-left:var(--spacing-2)}.lc-rich-text-editor__mode-btn{appearance:none;border:none;background:transparent;color:var(--color-text-secondary);min-width:var(--size-min-touch-height, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--typography-font-size-xs);font-weight:var(--typography-font-weight-medium);cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-rich-text-editor__mode-btn:hover{background-color:var(--color-surface-hover);color:var(--color-text-primary)}.lc-rich-text-editor__mode-btn.active{background-color:var(--color-primary-fill);color:var(--color-on-primary)}.lc-rich-text-editor__mode-btn:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-rich-text-editor__content{overflow-y:auto}.lc-rich-text-editor__rich{padding:var(--lc-density-padding-md, var(--spacing-4));min-height:100%;outline:none;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__rich--empty:before{content:attr(data-placeholder);color:var(--color-text-secondary);opacity:.6;pointer-events:none}.lc-rich-text-editor__rich h1,.lc-rich-text-editor__rich h2,.lc-rich-text-editor__rich h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold);color:var(--color-text-primary)}.lc-rich-text-editor__rich h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__rich h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__rich h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__rich p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich ul,.lc-rich-text-editor__rich ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__rich blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__rich code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__rich pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto;margin:var(--spacing-3) 0}.lc-rich-text-editor__rich pre code{background:none;padding:0}.lc-rich-text-editor__rich a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__rich hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__rich img{max-width:100%;border-radius:var(--border-radius-md)}.lc-rich-text-editor__textarea{width:100%;height:100%;min-height:inherit;border:none;outline:none;resize:vertical;padding:var(--lc-density-padding-md, var(--spacing-4));font-family:var(--typography-font-family-mono, monospace);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary);background-color:var(--color-surface);tab-size:2}.lc-rich-text-editor__split{display:grid;grid-template-columns:1fr 1fr;height:100%;min-height:inherit}.lc-rich-text-editor__split-editor{border-right:1px solid var(--color-divider);min-height:inherit;resize:none}.lc-rich-text-editor__split-preview{padding:var(--lc-density-padding-md, var(--spacing-4));overflow-y:auto;font-size:var(--typography-font-size-base);line-height:var(--typography-line-height-relaxed, 1.7);color:var(--color-text-primary)}.lc-rich-text-editor__split-preview h1,.lc-rich-text-editor__split-preview h2,.lc-rich-text-editor__split-preview h3{margin-top:var(--spacing-4);margin-bottom:var(--spacing-2);font-weight:var(--typography-font-weight-bold)}.lc-rich-text-editor__split-preview h1{font-size:var(--typography-font-size-2xl)}.lc-rich-text-editor__split-preview h2{font-size:var(--typography-font-size-xl)}.lc-rich-text-editor__split-preview h3{font-size:var(--typography-font-size-lg)}.lc-rich-text-editor__split-preview p{margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview ul,.lc-rich-text-editor__split-preview ol{padding-left:var(--spacing-6);margin-bottom:var(--spacing-3)}.lc-rich-text-editor__split-preview blockquote{border-left:4px solid var(--color-primary);padding-left:var(--spacing-4);margin:var(--spacing-3) 0;color:var(--color-text-secondary);font-style:italic}.lc-rich-text-editor__split-preview code{background-color:var(--color-surface-hover);padding:2px var(--spacing-1);border-radius:var(--border-radius-sm);font-family:var(--typography-font-family-mono, monospace);font-size:.9em}.lc-rich-text-editor__split-preview pre{background-color:var(--color-surface-hover);padding:var(--spacing-3);border-radius:var(--border-radius-md);overflow-x:auto}.lc-rich-text-editor__split-preview pre code{background:none;padding:0}.lc-rich-text-editor__split-preview a{color:var(--color-primary);text-decoration:underline}.lc-rich-text-editor__split-preview hr{border:none;border-top:1px solid var(--color-divider);margin:var(--spacing-4) 0}.lc-rich-text-editor__footer{display:flex;justify-content:flex-end;gap:var(--lc-density-gap-md, var(--spacing-4));padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider);font-size:var(--typography-font-size-xs);color:var(--color-text-secondary)}@media(forced-colors:active){.lc-rich-text-editor__toolbar-btn:focus-visible,.lc-rich-text-editor__mode-btn:focus-visible{outline-color:Highlight}.lc-rich-text-editor__mode-btn[aria-pressed=true]{text-decoration-line:underline;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:Highlight}.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible,.lc-rich-text-editor__textarea:focus-visible){border-color:Highlight;outline-color:Highlight}}\n"]
|
|
558
558
|
}]
|
|
559
559
|
}],
|
|
560
560
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-rich-text-editor.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.ts","../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n SecurityContext,\n computed,\n effect,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type RichTextEditorMode = 'rich' | 'markdown' | 'split';\nexport type ToolbarAction =\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strikethrough'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'ul'\n | 'ol'\n | 'blockquote'\n | 'code'\n | 'codeblock'\n | 'link'\n | 'image'\n | 'hr';\n\nexport interface ToolbarConfig {\n actions: ToolbarAction[];\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes text for HTML — every character that could open a tag or an attribute. */\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/**\n * The URL as it may go into `href`/`src`, or null if it must not: only relative\n * URLs and the allow-listed schemes pass (see `safe-url.ts`), so a link in\n * user-supplied markdown can never run script when clicked.\n */\nfunction safeUrl(url: string, image = false): string | null {\n const value = url.trim();\n if (!value) return null;\n return (image ? isSafeImageUrl(value) : isSafeLinkUrl(value)) ? value : null;\n}\n\nconst DEFAULT_TOOLBAR: ToolbarConfig = {\n actions: [\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'h1',\n 'h2',\n 'h3',\n 'ul',\n 'ol',\n 'blockquote',\n 'code',\n 'codeblock',\n 'link',\n 'hr',\n ],\n};\n\n/**\n * Rich Text Editor with Markdown support.\n *\n * Supports three modes:\n * - `rich`: WYSIWYG editing with toolbar\n * - `markdown`: Raw markdown editing with syntax highlighting\n * - `split`: Side-by-side markdown and preview\n *\n * Implements ControlValueAccessor for reactive form integration.\n * The value is always stored as Markdown.\n *\n * The built-in markdown → HTML conversion escapes the source before it applies\n * any markup and only lets http(s)/mailto/tel and relative URLs through as\n * links, so markdown that arrives from a server or another user cannot inject\n * markup or script. The result is additionally run through Angular's HTML\n * sanitizer before it reaches the DOM.\n *\n * @example\n * ```html\n * <lc-rich-text-editor\n * [(ngModel)]=\"content\"\n * mode=\"split\"\n * placeholder=\"Write something...\" />\n * ```\n */\n@Component({\n selector: 'lc-rich-text-editor',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './rich-text-editor.component.html',\n styleUrl: './rich-text-editor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n ],\n})\nexport class RichTextEditorComponent implements ControlValueAccessor {\n /** Editor mode */\n mode = input<RichTextEditorMode>('rich');\n\n /** Placeholder text */\n placeholder = input<string>('Start typing...');\n\n /** Minimum height in px */\n minHeight = input<number>(200);\n\n /** Maximum height in px (0 = unlimited) */\n maxHeight = input<number>(0);\n\n /** Whether the editor is disabled */\n disabled = input<boolean>(false);\n\n /** Whether the editor is readonly */\n readonly = input<boolean>(false);\n\n /** Toolbar configuration */\n toolbar = input<ToolbarConfig>(DEFAULT_TOOLBAR);\n\n /** Whether to show word count */\n showWordCount = input<boolean>(true);\n\n /**\n * Tooltips and accessible names of the toolbar actions, by action\n * (`{ bold: 'Fett' }`); missing ones keep the English default. A visible\n * button label with letters or digits that the title doesn't contain is\n * appended (`Heading 1, H1`, WCAG 2.5.3 Label in Name).\n * @default {}\n */\n actionTitles = input<Partial<Record<ToolbarAction, string>>>({});\n\n /** Label of the rich-text mode button. */\n richModeLabel = input('Rich');\n\n /** Tooltip of the rich-text mode button. */\n richModeTitle = input('Rich Text');\n\n /** Label of the Markdown mode button. */\n markdownModeLabel = input('MD');\n\n /** Tooltip of the Markdown mode button. */\n markdownModeTitle = input('Markdown');\n\n /** Label of the split-view mode button. */\n splitModeLabel = input('Split');\n\n /** Tooltip of the split-view mode button. */\n splitModeTitle = input('Split View');\n\n /** Accessible name of the Markdown text area. */\n markdownEditorAriaLabel = input('Markdown editor');\n\n /** Unit after the word count (`showWordCount`). */\n wordsLabel = input('words');\n\n /** Unit after the character count (`showWordCount`). */\n charactersLabel = input('characters');\n\n /** Emitted on every content change */\n readonly contentChange = output<string>();\n\n private readonly editorArea = viewChild<ElementRef<HTMLTextAreaElement>>('editorArea');\n private readonly richArea = viewChild<ElementRef<HTMLDivElement>>('richArea');\n private readonly sanitizer = inject(DomSanitizer);\n\n /** Internal markdown value */\n protected markdown = signal('');\n\n /** Current active mode: follows the `mode` input until the user toggles it. */\n protected activeMode = linkedSignal<RichTextEditorMode>(() => this.mode());\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n /** Whether the content can currently be edited at all. */\n protected readonly editable = computed(() => !this.isDisabled() && !this.readonly());\n\n /** Word count */\n protected wordCount = computed(() => {\n const text = this.markdown().trim();\n if (!text) return 0;\n return text.split(/\\s+/).length;\n });\n\n /** Character count */\n protected charCount = computed(() => this.markdown().length);\n\n /**\n * Rendered HTML from markdown — escaped by the converter and, on top of that,\n * passed through Angular's HTML sanitizer, so it is safe to assign to\n * `innerHTML` directly.\n */\n protected renderedHtml = computed(() => this.sanitizeHtml(this.markdownToHtml(this.markdown())));\n\n // CVA\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n constructor() {\n // The rich area lives inside an `@switch`, so it comes and goes with the\n // mode. Whenever it (re)appears, it starts empty and has to be filled from\n // the markdown — including the very first time, which is after any\n // `writeValue` a form control performs before the view exists. Only the\n // element's presence is tracked: re-rendering on every markdown change would\n // wipe the caret while the user types in it.\n effect(() => {\n const el = this.richArea()?.nativeElement;\n if (el) untracked(() => this.renderRich(el));\n });\n }\n\n // -- ControlValueAccessor --\n writeValue(value: string | null | undefined): void {\n this.markdown.set(value || '');\n const el = this.richArea()?.nativeElement;\n if (el) this.renderRich(el);\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // -- Mode switching --\n protected switchMode(mode: RichTextEditorMode): void {\n if (this.activeMode() === 'rich' && mode !== 'rich') {\n // Sync rich content to markdown before the rich area is torn down.\n const el = this.richArea()?.nativeElement;\n if (el) this.markdown.set(this.htmlToMarkdown(el.innerHTML));\n }\n // Entering rich mode: the effect in the constructor renders the markdown\n // into the freshly created rich area.\n this.activeMode.set(mode);\n }\n\n // -- Toolbar actions --\n protected onToolbarAction(action: ToolbarAction): void {\n if (!this.editable()) return;\n\n if (this.activeMode() === 'rich') {\n this.execRichCommand(action);\n } else {\n this.insertMarkdownSyntax(action);\n }\n }\n\n private execRichCommand(action: ToolbarAction): void {\n switch (action) {\n case 'bold':\n document.execCommand('bold');\n break;\n case 'italic':\n document.execCommand('italic');\n break;\n case 'underline':\n document.execCommand('underline');\n break;\n case 'strikethrough':\n document.execCommand('strikeThrough');\n break;\n case 'h1':\n document.execCommand('formatBlock', false, 'h1');\n break;\n case 'h2':\n document.execCommand('formatBlock', false, 'h2');\n break;\n case 'h3':\n document.execCommand('formatBlock', false, 'h3');\n break;\n case 'ul':\n document.execCommand('insertUnorderedList');\n break;\n case 'ol':\n document.execCommand('insertOrderedList');\n break;\n case 'blockquote':\n document.execCommand('formatBlock', false, 'blockquote');\n break;\n case 'code':\n document.execCommand('insertHTML', false, '<code>' + this.getSelection() + '</code>');\n break;\n case 'codeblock':\n document.execCommand('insertHTML', false, '<pre><code>' + this.getSelection() + '</code></pre>');\n break;\n case 'link': {\n const url = prompt('Enter URL:');\n if (url) document.execCommand('createLink', false, url);\n break;\n }\n case 'hr':\n document.execCommand('insertHorizontalRule');\n break;\n }\n this.syncFromRich();\n }\n\n private insertMarkdownSyntax(action: ToolbarAction): void {\n const textarea = this.editorArea()?.nativeElement;\n if (!textarea) return;\n\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const text = this.markdown();\n const selected = text.substring(start, end);\n\n let insertion = '';\n let cursorOffset = 0;\n\n switch (action) {\n case 'bold':\n insertion = `**${selected || 'bold text'}**`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'italic':\n insertion = `*${selected || 'italic text'}*`;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'underline':\n insertion = `<u>${selected || 'underlined text'}</u>`;\n cursorOffset = selected ? insertion.length : 3;\n break;\n case 'strikethrough':\n insertion = `~~${selected || 'strikethrough'}~~`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'h1':\n insertion = `# ${selected || 'Heading 1'}`;\n cursorOffset = insertion.length;\n break;\n case 'h2':\n insertion = `## ${selected || 'Heading 2'}`;\n cursorOffset = insertion.length;\n break;\n case 'h3':\n insertion = `### ${selected || 'Heading 3'}`;\n cursorOffset = insertion.length;\n break;\n case 'ul':\n insertion = `- ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'ol':\n insertion = `1. ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'blockquote':\n insertion = `> ${selected || 'Quote'}`;\n cursorOffset = insertion.length;\n break;\n case 'code':\n insertion = `\\`${selected || 'code'}\\``;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'codeblock':\n insertion = `\\`\\`\\`\\n${selected || 'code'}\\n\\`\\`\\``;\n cursorOffset = 4;\n break;\n case 'link':\n insertion = `[${selected || 'link text'}](url)`;\n cursorOffset = selected ? insertion.length - 5 : 1;\n break;\n case 'image':\n insertion = ``;\n cursorOffset = selected ? insertion.length - 5 : 2;\n break;\n case 'hr':\n insertion = `\\n---\\n`;\n cursorOffset = insertion.length;\n break;\n }\n\n const newText = text.substring(0, start) + insertion + text.substring(end);\n this.markdown.set(newText);\n this.emitChange(newText);\n\n setTimeout(() => {\n textarea.focus();\n textarea.setSelectionRange(start + cursorOffset, start + cursorOffset);\n });\n }\n\n // -- Content input handlers --\n protected onMarkdownInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n this.markdown.set(value);\n this.emitChange(value);\n }\n\n protected onRichInput(): void {\n this.syncFromRich();\n }\n\n protected onBlur(): void {\n this.onTouched();\n }\n\n private syncFromRich(): void {\n const el = this.richArea()?.nativeElement;\n if (!el) return;\n const md = this.htmlToMarkdown(el.innerHTML);\n this.markdown.set(md);\n this.emitChange(md);\n }\n\n /** Puts the current markdown into the rich area, as sanitised HTML. */\n private renderRich(el: HTMLDivElement): void {\n el.innerHTML = this.renderedHtml();\n }\n\n /**\n * Angular's HTML sanitizer, as the last line of defence behind the escaping\n * `markdownToHtml` does itself: whatever it might miss, this strips.\n */\n private sanitizeHtml(html: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';\n }\n\n private emitChange(value: string): void {\n this.onChange(value);\n this.contentChange.emit(value);\n }\n\n private getSelection(): string {\n return window.getSelection()?.toString() || '';\n }\n\n // -- Toolbar label helpers --\n protected getActionLabel(action: ToolbarAction): string {\n const labels: Record<ToolbarAction, string> = {\n bold: 'B',\n italic: 'I',\n underline: 'U',\n strikethrough: 'S',\n h1: 'H1',\n h2: 'H2',\n h3: 'H3',\n ul: '•',\n ol: '1.',\n blockquote: '\"',\n code: '<>',\n codeblock: '{ }',\n link: '🔗',\n image: '🖼',\n hr: '—',\n };\n return labels[action] || action;\n }\n\n protected getActionTitle(action: ToolbarAction): string {\n const titles: Record<ToolbarAction, string> = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n h1: 'Heading 1',\n h2: 'Heading 2',\n h3: 'Heading 3',\n ul: 'Unordered List',\n ol: 'Ordered List',\n blockquote: 'Blockquote',\n code: 'Inline Code',\n codeblock: 'Code Block',\n link: 'Insert Link',\n image: 'Insert Image',\n hr: 'Horizontal Rule',\n };\n return this.actionTitles()[action] || titles[action] || action;\n }\n\n /**\n * Tooltip and accessible name of a toolbar button (AS-33, WCAG 2.5.3 Label\n * in Name): the title, then the visible label when it has letters or digits\n * the title doesn't contain (`Heading 1, H1`). Symbol labels keep the title.\n */\n protected getActionName(action: ToolbarAction): string {\n return nameWithText(this.getActionTitle(action), this.getActionLabel(action));\n }\n\n // -- Markdown <-> HTML conversion (simple built-in) --\n /**\n * Converts the editor's markdown subset to HTML. The source is HTML-escaped\n * *first*, so nothing in it can open a tag or an attribute; the conversions\n * below then only ever add markup of their own. Raw HTML in the markdown is\n * therefore shown as text — with one exception, `<u>`, which the toolbar's\n * underline action emits (markdown has no underline) and which is let back\n * through in escaped form only.\n */\n protected markdownToHtml(md: string): string {\n if (!md) return '';\n\n let html = escapeHtml(md);\n\n // Code blocks (must be first)\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n\n // Headings\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // Horizontal rule\n html = html.replace(/^---$/gm, '<hr>');\n\n // Bold/Italic\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // Underline: the one raw tag the editor itself writes into the markdown.\n html = html.replace(/<u>(.+?)<\\/u>/g, '<u>$1</u>');\n\n // Inline code\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // Links and images. Only safe URLs become attributes; anything else is\n // shown as its text — a `javascript:` link that just isn't a link is far\n // better than one that runs. Quotes are already escaped (see above), so\n // the URL cannot break out of the attribute either.\n html = html.replace(/!\\[(.+?)\\]\\((.+?)\\)/g, (_m, alt: string, src: string) => {\n const url = safeUrl(src, true);\n return url ? `<img alt=\"${alt}\" src=\"${url}\" />` : alt;\n });\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, (_m, text: string, href: string) => {\n const url = safeUrl(href);\n return url ? `<a href=\"${url}\">${text}</a>` : text;\n });\n\n // Blockquotes — the marker is `>` by now, the source having been escaped.\n html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');\n\n // Unordered lists\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');\n\n // Ordered lists\n html = html.replace(/^\\d+\\. (.+)$/gm, '<li>$1</li>');\n\n // Paragraphs (lines that aren't already wrapped)\n html = html.replace(/^(?!<[a-z])((?!^\\s*$).+)$/gm, '<p>$1</p>');\n\n // Clean up empty paragraphs\n html = html.replace(/<p>\\s*<\\/p>/g, '');\n\n return html.trim();\n }\n\n private htmlToMarkdown(html: string): string {\n if (!html) return '';\n\n let md = html;\n\n // Block elements\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, '# $1\\n');\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, '## $1\\n');\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, '### $1\\n');\n md = md.replace(/<hr\\s*\\/?>/gi, '---\\n');\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gi, '> $1\\n');\n md = md.replace(/<pre><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n');\n\n // Lists\n md = md.replace(/<ul[^>]*>([\\s\\S]*?)<\\/ul>/gi, '$1');\n md = md.replace(/<ol[^>]*>([\\s\\S]*?)<\\/ol>/gi, '$1');\n md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, '- $1\\n');\n\n // Inline\n md = md.replace(/<strong[^>]*>(.*?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.*?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.*?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.*?)<\\/i>/gi, '*$1*');\n md = md.replace(/<del[^>]*>(.*?)<\\/del>/gi, '~~$1~~');\n md = md.replace(/<u[^>]*>(.*?)<\\/u>/gi, '<u>$1</u>');\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, '`$1`');\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, '[$2]($1)');\n md = md.replace(/<img[^>]*alt=\"([^\"]*)\"[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, '');\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>(.*?)<\\/p>/gi, '$1\\n\\n');\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<div[^>]*>(.*?)<\\/div>/gi, '$1\\n');\n\n // Strip remaining HTML tags\n md = md.replace(/<[^>]+>/g, '');\n\n // Decode HTML entities\n md = md.replace(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/ /g, ' ');\n\n // Normalize whitespace\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n }\n}\n","<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-rich-text-editor.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.ts","../../../../libs/angular-ui-kit/src/lib/rich-text-editor/rich-text-editor.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n SecurityContext,\n computed,\n effect,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { isSafeImageUrl, isSafeLinkUrl } from '@lifecorner/angular-ui-kit/internal/url';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\nexport type RichTextEditorMode = 'rich' | 'markdown' | 'split';\nexport type ToolbarAction =\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strikethrough'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'ul'\n | 'ol'\n | 'blockquote'\n | 'code'\n | 'codeblock'\n | 'link'\n | 'image'\n | 'hr';\n\nexport interface ToolbarConfig {\n actions: ToolbarAction[];\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes text for HTML — every character that could open a tag or an attribute. */\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/**\n * The URL as it may go into `href`/`src`, or null if it must not: only relative\n * URLs and the allow-listed schemes pass (see `safe-url.ts`), so a link in\n * user-supplied markdown can never run script when clicked.\n */\nfunction safeUrl(url: string, image = false): string | null {\n const value = url.trim();\n if (!value) return null;\n return (image ? isSafeImageUrl(value) : isSafeLinkUrl(value)) ? value : null;\n}\n\nconst DEFAULT_TOOLBAR: ToolbarConfig = {\n actions: [\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'h1',\n 'h2',\n 'h3',\n 'ul',\n 'ol',\n 'blockquote',\n 'code',\n 'codeblock',\n 'link',\n 'hr',\n ],\n};\n\n/**\n * Rich Text Editor with Markdown support.\n *\n * Supports three modes:\n * - `rich`: WYSIWYG editing with toolbar\n * - `markdown`: Raw markdown editing with syntax highlighting\n * - `split`: Side-by-side markdown and preview\n *\n * Implements ControlValueAccessor for reactive form integration.\n * The value is always stored as Markdown.\n *\n * The built-in markdown → HTML conversion escapes the source before it applies\n * any markup and only lets http(s)/mailto/tel and relative URLs through as\n * links, so markdown that arrives from a server or another user cannot inject\n * markup or script. The result is additionally run through Angular's HTML\n * sanitizer before it reaches the DOM.\n *\n * @example\n * ```html\n * <lc-rich-text-editor\n * [(ngModel)]=\"content\"\n * mode=\"split\"\n * placeholder=\"Write something...\" />\n * ```\n */\n@Component({\n selector: 'lc-rich-text-editor',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './rich-text-editor.component.html',\n styleUrl: './rich-text-editor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n ],\n})\nexport class RichTextEditorComponent implements ControlValueAccessor {\n /** Editor mode */\n mode = input<RichTextEditorMode>('rich');\n\n /** Placeholder text */\n placeholder = input<string>('Start typing...');\n\n /** Minimum height in px */\n minHeight = input<number>(200);\n\n /** Maximum height in px (0 = unlimited) */\n maxHeight = input<number>(0);\n\n /** Whether the editor is disabled */\n disabled = input<boolean>(false);\n\n /** Whether the editor is readonly */\n readonly = input<boolean>(false);\n\n /** Toolbar configuration */\n toolbar = input<ToolbarConfig>(DEFAULT_TOOLBAR);\n\n /** Whether to show word count */\n showWordCount = input<boolean>(true);\n\n /**\n * Tooltips and accessible names of the toolbar actions, by action\n * (`{ bold: 'Fett' }`); missing ones keep the English default. A visible\n * button label with letters or digits that the title doesn't contain is\n * appended (`Heading 1, H1`, WCAG 2.5.3 Label in Name).\n * @default {}\n */\n actionTitles = input<Partial<Record<ToolbarAction, string>>>({});\n\n /** Label of the rich-text mode button. */\n richModeLabel = input('Rich');\n\n /** Tooltip of the rich-text mode button. */\n richModeTitle = input('Rich Text');\n\n /** Label of the Markdown mode button. */\n markdownModeLabel = input('MD');\n\n /** Tooltip of the Markdown mode button. */\n markdownModeTitle = input('Markdown');\n\n /** Label of the split-view mode button. */\n splitModeLabel = input('Split');\n\n /** Tooltip of the split-view mode button. */\n splitModeTitle = input('Split View');\n\n /** Accessible name of the Markdown text area. */\n markdownEditorAriaLabel = input('Markdown editor');\n\n /** Unit after the word count (`showWordCount`). */\n wordsLabel = input('words');\n\n /** Unit after the character count (`showWordCount`). */\n charactersLabel = input('characters');\n\n /** Emitted on every content change */\n readonly contentChange = output<string>();\n\n private readonly editorArea = viewChild<ElementRef<HTMLTextAreaElement>>('editorArea');\n private readonly richArea = viewChild<ElementRef<HTMLDivElement>>('richArea');\n private readonly sanitizer = inject(DomSanitizer);\n\n /** Internal markdown value */\n protected markdown = signal('');\n\n /** Current active mode: follows the `mode` input until the user toggles it. */\n protected activeMode = linkedSignal<RichTextEditorMode>(() => this.mode());\n\n /** Disabled through the form control (`setDisabledState`), as opposed to the input. */\n private readonly formDisabled = signal(false);\n\n /** Disabled by either route — the one flag the template consults. */\n protected readonly isDisabled = computed(() => this.disabled() || this.formDisabled());\n\n /** Whether the content can currently be edited at all. */\n protected readonly editable = computed(() => !this.isDisabled() && !this.readonly());\n\n /** Word count */\n protected wordCount = computed(() => {\n const text = this.markdown().trim();\n if (!text) return 0;\n return text.split(/\\s+/).length;\n });\n\n /** Character count */\n protected charCount = computed(() => this.markdown().length);\n\n /**\n * Rendered HTML from markdown — escaped by the converter and, on top of that,\n * passed through Angular's HTML sanitizer, so it is safe to assign to\n * `innerHTML` directly.\n */\n protected renderedHtml = computed(() => this.sanitizeHtml(this.markdownToHtml(this.markdown())));\n\n // CVA\n private onChange: (value: string) => void = () => { /* set by registerOnChange */ };\n private onTouched: () => void = () => { /* set by registerOnTouched */ };\n\n constructor() {\n // The rich area lives inside an `@switch`, so it comes and goes with the\n // mode. Whenever it (re)appears, it starts empty and has to be filled from\n // the markdown — including the very first time, which is after any\n // `writeValue` a form control performs before the view exists. Only the\n // element's presence is tracked: re-rendering on every markdown change would\n // wipe the caret while the user types in it.\n effect(() => {\n const el = this.richArea()?.nativeElement;\n if (el) untracked(() => this.renderRich(el));\n });\n }\n\n // -- ControlValueAccessor --\n writeValue(value: string | null | undefined): void {\n this.markdown.set(value || '');\n const el = this.richArea()?.nativeElement;\n if (el) this.renderRich(el);\n }\n\n registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n // -- Mode switching --\n protected switchMode(mode: RichTextEditorMode): void {\n if (this.activeMode() === 'rich' && mode !== 'rich') {\n // Sync rich content to markdown before the rich area is torn down.\n const el = this.richArea()?.nativeElement;\n if (el) this.markdown.set(this.htmlToMarkdown(el.innerHTML));\n }\n // Entering rich mode: the effect in the constructor renders the markdown\n // into the freshly created rich area.\n this.activeMode.set(mode);\n }\n\n // -- Toolbar actions --\n protected onToolbarAction(action: ToolbarAction): void {\n if (!this.editable()) return;\n\n if (this.activeMode() === 'rich') {\n this.execRichCommand(action);\n } else {\n this.insertMarkdownSyntax(action);\n }\n }\n\n private execRichCommand(action: ToolbarAction): void {\n switch (action) {\n case 'bold':\n document.execCommand('bold');\n break;\n case 'italic':\n document.execCommand('italic');\n break;\n case 'underline':\n document.execCommand('underline');\n break;\n case 'strikethrough':\n document.execCommand('strikeThrough');\n break;\n case 'h1':\n document.execCommand('formatBlock', false, 'h1');\n break;\n case 'h2':\n document.execCommand('formatBlock', false, 'h2');\n break;\n case 'h3':\n document.execCommand('formatBlock', false, 'h3');\n break;\n case 'ul':\n document.execCommand('insertUnorderedList');\n break;\n case 'ol':\n document.execCommand('insertOrderedList');\n break;\n case 'blockquote':\n document.execCommand('formatBlock', false, 'blockquote');\n break;\n case 'code':\n document.execCommand('insertHTML', false, '<code>' + this.getSelection() + '</code>');\n break;\n case 'codeblock':\n document.execCommand('insertHTML', false, '<pre><code>' + this.getSelection() + '</code></pre>');\n break;\n case 'link': {\n const url = prompt('Enter URL:');\n if (url) document.execCommand('createLink', false, url);\n break;\n }\n case 'hr':\n document.execCommand('insertHorizontalRule');\n break;\n }\n this.syncFromRich();\n }\n\n private insertMarkdownSyntax(action: ToolbarAction): void {\n const textarea = this.editorArea()?.nativeElement;\n if (!textarea) return;\n\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const text = this.markdown();\n const selected = text.substring(start, end);\n\n let insertion = '';\n let cursorOffset = 0;\n\n switch (action) {\n case 'bold':\n insertion = `**${selected || 'bold text'}**`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'italic':\n insertion = `*${selected || 'italic text'}*`;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'underline':\n insertion = `<u>${selected || 'underlined text'}</u>`;\n cursorOffset = selected ? insertion.length : 3;\n break;\n case 'strikethrough':\n insertion = `~~${selected || 'strikethrough'}~~`;\n cursorOffset = selected ? insertion.length : 2;\n break;\n case 'h1':\n insertion = `# ${selected || 'Heading 1'}`;\n cursorOffset = insertion.length;\n break;\n case 'h2':\n insertion = `## ${selected || 'Heading 2'}`;\n cursorOffset = insertion.length;\n break;\n case 'h3':\n insertion = `### ${selected || 'Heading 3'}`;\n cursorOffset = insertion.length;\n break;\n case 'ul':\n insertion = `- ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'ol':\n insertion = `1. ${selected || 'List item'}`;\n cursorOffset = insertion.length;\n break;\n case 'blockquote':\n insertion = `> ${selected || 'Quote'}`;\n cursorOffset = insertion.length;\n break;\n case 'code':\n insertion = `\\`${selected || 'code'}\\``;\n cursorOffset = selected ? insertion.length : 1;\n break;\n case 'codeblock':\n insertion = `\\`\\`\\`\\n${selected || 'code'}\\n\\`\\`\\``;\n cursorOffset = 4;\n break;\n case 'link':\n insertion = `[${selected || 'link text'}](url)`;\n cursorOffset = selected ? insertion.length - 5 : 1;\n break;\n case 'image':\n insertion = ``;\n cursorOffset = selected ? insertion.length - 5 : 2;\n break;\n case 'hr':\n insertion = `\\n---\\n`;\n cursorOffset = insertion.length;\n break;\n }\n\n const newText = text.substring(0, start) + insertion + text.substring(end);\n this.markdown.set(newText);\n this.emitChange(newText);\n\n setTimeout(() => {\n textarea.focus();\n textarea.setSelectionRange(start + cursorOffset, start + cursorOffset);\n });\n }\n\n // -- Content input handlers --\n protected onMarkdownInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n this.markdown.set(value);\n this.emitChange(value);\n }\n\n protected onRichInput(): void {\n this.syncFromRich();\n }\n\n protected onBlur(): void {\n this.onTouched();\n }\n\n private syncFromRich(): void {\n const el = this.richArea()?.nativeElement;\n if (!el) return;\n const md = this.htmlToMarkdown(el.innerHTML);\n this.markdown.set(md);\n this.emitChange(md);\n }\n\n /** Puts the current markdown into the rich area, as sanitised HTML. */\n private renderRich(el: HTMLDivElement): void {\n el.innerHTML = this.renderedHtml();\n }\n\n /**\n * Angular's HTML sanitizer, as the last line of defence behind the escaping\n * `markdownToHtml` does itself: whatever it might miss, this strips.\n */\n private sanitizeHtml(html: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';\n }\n\n private emitChange(value: string): void {\n this.onChange(value);\n this.contentChange.emit(value);\n }\n\n private getSelection(): string {\n return window.getSelection()?.toString() || '';\n }\n\n // -- Toolbar label helpers --\n protected getActionLabel(action: ToolbarAction): string {\n const labels: Record<ToolbarAction, string> = {\n bold: 'B',\n italic: 'I',\n underline: 'U',\n strikethrough: 'S',\n h1: 'H1',\n h2: 'H2',\n h3: 'H3',\n ul: '•',\n ol: '1.',\n blockquote: '\"',\n code: '<>',\n codeblock: '{ }',\n link: '🔗',\n image: '🖼',\n hr: '—',\n };\n return labels[action] || action;\n }\n\n protected getActionTitle(action: ToolbarAction): string {\n const titles: Record<ToolbarAction, string> = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n h1: 'Heading 1',\n h2: 'Heading 2',\n h3: 'Heading 3',\n ul: 'Unordered List',\n ol: 'Ordered List',\n blockquote: 'Blockquote',\n code: 'Inline Code',\n codeblock: 'Code Block',\n link: 'Insert Link',\n image: 'Insert Image',\n hr: 'Horizontal Rule',\n };\n return this.actionTitles()[action] || titles[action] || action;\n }\n\n /**\n * Tooltip and accessible name of a toolbar button (AS-33, WCAG 2.5.3 Label\n * in Name): the title, then the visible label when it has letters or digits\n * the title doesn't contain (`Heading 1, H1`). Symbol labels keep the title.\n */\n protected getActionName(action: ToolbarAction): string {\n return nameWithText(this.getActionTitle(action), this.getActionLabel(action));\n }\n\n // -- Markdown <-> HTML conversion (simple built-in) --\n /**\n * Converts the editor's markdown subset to HTML. The source is HTML-escaped\n * *first*, so nothing in it can open a tag or an attribute; the conversions\n * below then only ever add markup of their own. Raw HTML in the markdown is\n * therefore shown as text — with one exception, `<u>`, which the toolbar's\n * underline action emits (markdown has no underline) and which is let back\n * through in escaped form only.\n */\n protected markdownToHtml(md: string): string {\n if (!md) return '';\n\n let html = escapeHtml(md);\n\n // Code blocks (must be first)\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n\n // Headings\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // Horizontal rule\n html = html.replace(/^---$/gm, '<hr>');\n\n // Bold/Italic\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // Underline: the one raw tag the editor itself writes into the markdown.\n html = html.replace(/<u>(.+?)<\\/u>/g, '<u>$1</u>');\n\n // Inline code\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // Links and images. Only safe URLs become attributes; anything else is\n // shown as its text — a `javascript:` link that just isn't a link is far\n // better than one that runs. Quotes are already escaped (see above), so\n // the URL cannot break out of the attribute either.\n html = html.replace(/!\\[(.+?)\\]\\((.+?)\\)/g, (_m, alt: string, src: string) => {\n const url = safeUrl(src, true);\n return url ? `<img alt=\"${alt}\" src=\"${url}\" />` : alt;\n });\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, (_m, text: string, href: string) => {\n const url = safeUrl(href);\n return url ? `<a href=\"${url}\">${text}</a>` : text;\n });\n\n // Blockquotes — the marker is `>` by now, the source having been escaped.\n html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');\n\n // Unordered lists\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');\n\n // Ordered lists\n html = html.replace(/^\\d+\\. (.+)$/gm, '<li>$1</li>');\n\n // Paragraphs (lines that aren't already wrapped)\n html = html.replace(/^(?!<[a-z])((?!^\\s*$).+)$/gm, '<p>$1</p>');\n\n // Clean up empty paragraphs\n html = html.replace(/<p>\\s*<\\/p>/g, '');\n\n return html.trim();\n }\n\n private htmlToMarkdown(html: string): string {\n if (!html) return '';\n\n let md = html;\n\n // Block elements\n md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, '# $1\\n');\n md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, '## $1\\n');\n md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, '### $1\\n');\n md = md.replace(/<hr\\s*\\/?>/gi, '---\\n');\n md = md.replace(/<blockquote[^>]*>(.*?)<\\/blockquote>/gi, '> $1\\n');\n md = md.replace(/<pre><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n');\n\n // Lists\n md = md.replace(/<ul[^>]*>([\\s\\S]*?)<\\/ul>/gi, '$1');\n md = md.replace(/<ol[^>]*>([\\s\\S]*?)<\\/ol>/gi, '$1');\n md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, '- $1\\n');\n\n // Inline\n md = md.replace(/<strong[^>]*>(.*?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.*?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.*?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.*?)<\\/i>/gi, '*$1*');\n md = md.replace(/<del[^>]*>(.*?)<\\/del>/gi, '~~$1~~');\n md = md.replace(/<u[^>]*>(.*?)<\\/u>/gi, '<u>$1</u>');\n md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, '`$1`');\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.*?)<\\/a>/gi, '[$2]($1)');\n md = md.replace(/<img[^>]*alt=\"([^\"]*)\"[^>]*src=\"([^\"]*)\"[^>]*\\/?>/gi, '');\n\n // Paragraphs and line breaks\n md = md.replace(/<p[^>]*>(.*?)<\\/p>/gi, '$1\\n\\n');\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<div[^>]*>(.*?)<\\/div>/gi, '$1\\n');\n\n // Strip remaining HTML tags\n md = md.replace(/<[^>]+>/g, '');\n\n // Decode HTML entities\n md = md.replace(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/ /g, ' ');\n\n // Normalize whitespace\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n }\n}\n","<div class=\"lc-rich-text-editor\" [class.lc-rich-text-editor--disabled]=\"isDisabled()\">\n <!-- Toolbar -->\n <div class=\"lc-rich-text-editor__toolbar\">\n <div class=\"lc-rich-text-editor__toolbar-actions\">\n @for (action of toolbar().actions; track action) {\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__toolbar-btn\"\n [title]=\"getActionName(action)\"\n [attr.aria-label]=\"getActionName(action)\"\n [disabled]=\"!editable()\"\n (click)=\"onToolbarAction(action)\"\n >\n {{ getActionLabel(action) }}\n </button>\n }\n </div>\n <div class=\"lc-rich-text-editor__toolbar-modes\">\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'rich'\"\n [attr.aria-pressed]=\"activeMode() === 'rich'\"\n (click)=\"switchMode('rich')\"\n [attr.title]=\"richModeTitle()\"\n >{{ richModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'markdown'\"\n [attr.aria-pressed]=\"activeMode() === 'markdown'\"\n (click)=\"switchMode('markdown')\"\n [attr.title]=\"markdownModeTitle()\"\n >{{ markdownModeLabel() }}</button>\n <button\n type=\"button\"\n class=\"lc-rich-text-editor__mode-btn\"\n [class.active]=\"activeMode() === 'split'\"\n [attr.aria-pressed]=\"activeMode() === 'split'\"\n (click)=\"switchMode('split')\"\n [attr.title]=\"splitModeTitle()\"\n >{{ splitModeLabel() }}</button>\n </div>\n </div>\n\n <!-- Editor area -->\n <div\n class=\"lc-rich-text-editor__content\"\n [style.min-height.px]=\"minHeight()\"\n [style.max-height.px]=\"maxHeight() || null\"\n >\n @switch (activeMode()) {\n @case ('rich') {\n <!-- The rich area is filled from the markdown by the component (see the\n constructor effect); it must never receive markup Angular hasn't\n sanitised. `contenteditable` follows the disabled/readonly state —\n a greyed-out toolbar alone would still let the user type. -->\n <div\n #richArea\n class=\"lc-rich-text-editor__rich\"\n [attr.contenteditable]=\"editable() ? 'true' : 'false'\"\n [attr.aria-disabled]=\"isDisabled() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() && !isDisabled() ? 'true' : null\"\n [attr.data-placeholder]=\"placeholder()\"\n [class.lc-rich-text-editor__rich--empty]=\"!markdown()\"\n (input)=\"onRichInput()\"\n (blur)=\"onBlur()\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.aria-label]=\"placeholder()\"\n ></div>\n }\n @case ('markdown') {\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n }\n @case ('split') {\n <div class=\"lc-rich-text-editor__split\">\n <textarea\n #editorArea\n class=\"lc-rich-text-editor__textarea lc-rich-text-editor__split-editor\"\n [placeholder]=\"placeholder()\"\n [value]=\"markdown()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"onMarkdownInput($event)\"\n (blur)=\"onBlur()\"\n spellcheck=\"false\"\n [attr.aria-label]=\"markdownEditorAriaLabel()\"\n ></textarea>\n <div\n class=\"lc-rich-text-editor__split-preview\"\n [innerHTML]=\"renderedHtml()\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n @if (showWordCount()) {\n <div class=\"lc-rich-text-editor__footer\">\n <span>{{ wordCount() }} {{ wordsLabel() }}</span>\n <span>{{ charCount() }} {{ charactersLabel() }}</span>\n </div>\n }\n</div>\n"],"mappings":"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"}
|