@sdcorejs/angular 20.0.1 → 20.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +22 -4
  2. package/assets/scss/core/_inline-edit.scss +105 -0
  3. package/assets/scss/core/form.scss +15 -0
  4. package/components/autoid-inspector/index.d.ts +53 -4
  5. package/components/button/index.d.ts +1 -2
  6. package/components/form-generic/index.d.ts +15 -17
  7. package/components/index.d.ts +1 -0
  8. package/components/modal-resizable/index.d.ts +32 -0
  9. package/components/query-builder/index.d.ts +247 -23
  10. package/components/section/index.d.ts +1 -2
  11. package/components/side-drawer/index.d.ts +1 -2
  12. package/components/stepper/index.d.ts +1 -2
  13. package/components/tab/index.d.ts +1 -2
  14. package/components/table/index.d.ts +100 -12
  15. package/components/upload-file/index.d.ts +2 -0
  16. package/components/view/index.d.ts +33 -0
  17. package/fesm2022/sdcorejs-angular-components-anchor.mjs +9 -9
  18. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +111 -24
  19. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-avatar.mjs +4 -4
  21. package/fesm2022/sdcorejs-angular-components-avatar.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-badge.mjs +3 -3
  23. package/fesm2022/sdcorejs-angular-components-base.mjs +3 -3
  24. package/fesm2022/sdcorejs-angular-components-button.mjs +5 -7
  25. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-chart.mjs +12 -12
  27. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs +3 -3
  28. package/fesm2022/sdcorejs-angular-components-code-editor.mjs +3 -3
  29. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +6 -6
  30. package/fesm2022/sdcorejs-angular-components-editor.mjs +3 -3
  31. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +291 -382
  32. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  33. package/fesm2022/sdcorejs-angular-components-history.mjs +7 -7
  34. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +10 -10
  35. package/fesm2022/sdcorejs-angular-components-inform.mjs +6 -6
  36. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +3 -3
  37. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +158 -0
  38. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -0
  39. package/fesm2022/sdcorejs-angular-components-modal.mjs +3 -3
  40. package/fesm2022/sdcorejs-angular-components-operator.mjs +6 -4
  41. package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-preview.mjs +8 -8
  43. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +60 -106
  45. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +671 -59
  47. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-quick-action.mjs +3 -3
  49. package/fesm2022/sdcorejs-angular-components-section.mjs +7 -9
  50. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +5 -7
  52. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  53. package/fesm2022/sdcorejs-angular-components-splitter.mjs +12 -12
  54. package/fesm2022/sdcorejs-angular-components-stepper.mjs +7 -9
  55. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +18 -18
  57. package/fesm2022/sdcorejs-angular-components-tab.mjs +7 -9
  58. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  59. package/fesm2022/sdcorejs-angular-components-table.mjs +336 -211
  60. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  61. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +29 -18
  62. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  63. package/fesm2022/sdcorejs-angular-components-view.mjs +45 -13
  64. package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
  65. package/fesm2022/sdcorejs-angular-components.mjs +1 -0
  66. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  67. package/fesm2022/sdcorejs-angular-directives.mjs +21 -21
  68. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +35 -10
  69. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +11 -6
  71. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +15 -10
  73. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-chip.mjs +15 -10
  75. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +21 -12
  77. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-date.mjs +29 -17
  79. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +34 -22
  81. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  82. package/fesm2022/sdcorejs-angular-forms-directives.mjs +12 -12
  83. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs +167 -0
  84. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs.map +1 -0
  85. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +7 -5
  86. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  87. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +44 -11
  88. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  89. package/fesm2022/sdcorejs-angular-forms-input.mjs +38 -12
  90. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  91. package/fesm2022/sdcorejs-angular-forms-label.mjs +3 -3
  92. package/fesm2022/sdcorejs-angular-forms-models.mjs +32 -2
  93. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-radio.mjs +11 -6
  95. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-forms-select.mjs +36 -12
  97. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-forms-switch.mjs +11 -6
  99. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  100. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +17 -7
  101. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  102. package/fesm2022/sdcorejs-angular-forms.mjs +4 -4
  103. package/fesm2022/sdcorejs-angular-handlers.mjs +3 -3
  104. package/fesm2022/sdcorejs-angular-i18n.mjs +26 -6
  105. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-interceptors.mjs +6 -6
  107. package/fesm2022/sdcorejs-angular-modules-auth.mjs +9 -9
  108. package/fesm2022/sdcorejs-angular-modules-authom.mjs +7 -7
  109. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +7 -7
  110. package/fesm2022/sdcorejs-angular-modules-layout.mjs +73 -73
  111. package/fesm2022/sdcorejs-angular-modules-permission.mjs +9 -9
  112. package/fesm2022/sdcorejs-angular-pipes.mjs +14 -14
  113. package/fesm2022/sdcorejs-angular-services-api.mjs +10 -10
  114. package/fesm2022/sdcorejs-angular-services-cache.mjs +3 -3
  115. package/fesm2022/sdcorejs-angular-services-confirm.mjs +6 -6
  116. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  117. package/fesm2022/sdcorejs-angular-services-docx.mjs +3 -3
  118. package/fesm2022/sdcorejs-angular-services-excel.mjs +3 -3
  119. package/fesm2022/sdcorejs-angular-services-license.mjs +3 -3
  120. package/fesm2022/sdcorejs-angular-services-loading.mjs +3 -3
  121. package/fesm2022/sdcorejs-angular-services-notify.mjs +47 -16
  122. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  123. package/fesm2022/sdcorejs-angular-services-storage.mjs +3 -3
  124. package/forms/autocomplete/index.d.ts +16 -3
  125. package/forms/checkbox/index.d.ts +5 -1
  126. package/forms/chip/index.d.ts +5 -2
  127. package/forms/chip-calendar/index.d.ts +5 -2
  128. package/forms/date/index.d.ts +14 -5
  129. package/forms/date-range/index.d.ts +12 -5
  130. package/forms/datetime/index.d.ts +14 -5
  131. package/forms/inline-text/index.d.ts +80 -0
  132. package/forms/input/index.d.ts +14 -2
  133. package/forms/input-color/index.d.ts +3 -1
  134. package/forms/input-number/index.d.ts +14 -2
  135. package/forms/models/index.d.ts +40 -2
  136. package/forms/radio/index.d.ts +5 -2
  137. package/forms/select/index.d.ts +23 -5
  138. package/forms/switch/index.d.ts +5 -2
  139. package/forms/textarea/index.d.ts +9 -2
  140. package/i18n/index.d.ts +4 -0
  141. package/package.json +9 -1
  142. package/services/notify/index.d.ts +17 -0
@@ -1,12 +1,14 @@
1
1
  import { CommonModule, DOCUMENT } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { signal, HostListener, Input, Component, createComponent, Inject, Injectable } from '@angular/core';
3
+ import { signal, SecurityContext, HostListener, Input, Component, createComponent, Inject, Injectable } from '@angular/core';
4
4
  import { Utilities } from '@sdcorejs/utils/fns';
5
5
  import { trigger, transition, style, animate } from '@angular/animations';
6
6
  import { TranslatePipe } from '@sdcorejs/angular/i18n';
7
+ import * as i2 from '@angular/platform-browser';
7
8
 
8
9
  class ToastComponent {
9
10
  notifyService;
11
+ sanitizer;
10
12
  data;
11
13
  isExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isExpanded" }] : []));
12
14
  MAX_SHOW = 2;
@@ -14,8 +16,22 @@ class ToastComponent {
14
16
  timer;
15
17
  start;
16
18
  remaining;
17
- constructor(notifyService) {
19
+ constructor(notifyService, sanitizer) {
18
20
  this.notifyService = notifyService;
21
+ this.sanitizer = sanitizer;
22
+ }
23
+ /**
24
+ * Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip
25
+ * `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh
26
+ * `data.html === true`; nhánh mặc định render text (auto-escape) nên không
27
+ * có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.
28
+ */
29
+ sanitizeHtml(value) {
30
+ return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
31
+ }
32
+ /** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */
33
+ get singleMessage() {
34
+ return this.data.message;
19
35
  }
20
36
  ngOnInit() {
21
37
  // Khởi tạo thời gian còn lại bằng duration ban đầu
@@ -73,8 +89,16 @@ class ToastComponent {
73
89
  get hasMore() {
74
90
  return this.isMultiMessage && this.messages.length > this.MAX_SHOW;
75
91
  }
76
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: ToastComponent, deps: [{ token: SdNotifyService }], target: i0.ɵɵFactoryTarget.Component });
77
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.23", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "@toastAnimation": "true", "class": "\"bg-white sd-toast\"" } }, ngImport: i0, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <div [innerHTML]=\"data.message\"></div>\n } \n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n <li [innerHTML]=\"'- ' + msg\"></li>\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], animations: [
92
+ /** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */
93
+ get autoId() {
94
+ return `services-notify-toast-${this.data.type}`;
95
+ }
96
+ /** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */
97
+ get dataMessage() {
98
+ return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;
99
+ }
100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ToastComponent, deps: [{ token: SdNotifyService }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "@toastAnimation": "true", "class": "\"bg-white sd-toast\"", "attr.data-autoid": "autoId", "attr.data-type": "data.type", "attr.data-title": "data.title ?? null", "attr.data-message": "dataMessage" } }, ngImport: i0, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: m\u1EB7c \u0111\u1ECBnh render TEXT (auto-escape) \u2014 an to\u00E0n. Ch\u1EC9 render HTML\n (\u0111\u00E3 sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], animations: [
78
102
  trigger('toastAnimation', [
79
103
  transition(':enter', [
80
104
  style({ transform: 'translateX(100%)', opacity: 0 }),
@@ -86,7 +110,7 @@ class ToastComponent {
86
110
  ])
87
111
  ] });
88
112
  }
89
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: ToastComponent, decorators: [{
113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ToastComponent, decorators: [{
90
114
  type: Component,
91
115
  args: [{ selector: 'toast', standalone: true, imports: [CommonModule, TranslatePipe], animations: [
92
116
  trigger('toastAnimation', [
@@ -100,9 +124,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImpo
100
124
  ])
101
125
  ], host: {
102
126
  '[@toastAnimation]': 'true',
103
- '[class]': '"bg-white sd-toast"'
104
- }, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <div [innerHTML]=\"data.message\"></div>\n } \n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n <li [innerHTML]=\"'- ' + msg\"></li>\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"] }]
105
- }], ctorParameters: () => [{ type: SdNotifyService }], propDecorators: { data: [{
127
+ '[class]': '"bg-white sd-toast"',
128
+ // E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.
129
+ '[attr.data-autoid]': 'autoId',
130
+ '[attr.data-type]': 'data.type',
131
+ '[attr.data-title]': 'data.title ?? null',
132
+ '[attr.data-message]': 'dataMessage'
133
+ }, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: m\u1EB7c \u0111\u1ECBnh render TEXT (auto-escape) \u2014 an to\u00E0n. Ch\u1EC9 render HTML\n (\u0111\u00E3 sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"] }]
134
+ }], ctorParameters: () => [{ type: SdNotifyService }, { type: i2.DomSanitizer }], propDecorators: { data: [{
106
135
  type: Input,
107
136
  args: [{ required: true }]
108
137
  }], pauseTimer: [{
@@ -115,8 +144,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImpo
115
144
 
116
145
  class ToastContainerComponent {
117
146
  toasts = signal([], ...(ngDevMode ? [{ debugName: "toasts" }] : []));
118
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: ToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
119
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.23", type: ToastContainerComponent, isStandalone: true, selector: "toast-container", ngImport: i0, template: `
147
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
148
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: ToastContainerComponent, isStandalone: true, selector: "toast-container", ngImport: i0, template: `
120
149
  <div class="toast-container">
121
150
  @for (toast of toasts(); track toast.id) {
122
151
  <toast [data]="toast"> </toast>
@@ -124,7 +153,7 @@ class ToastContainerComponent {
124
153
  </div>
125
154
  `, isInline: true, styles: [".toast-container{position:fixed;top:20px;right:20px;z-index:9999;display:flex;flex-direction:column;pointer-events:none}sd-toast{pointer-events:auto}\n"], dependencies: [{ kind: "component", type: ToastComponent, selector: "toast", inputs: ["data"] }] });
126
155
  }
127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: ToastContainerComponent, decorators: [{
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ToastContainerComponent, decorators: [{
128
157
  type: Component,
129
158
  args: [{ selector: 'toast-container', standalone: true, imports: [ToastComponent], template: `
130
159
  <div class="toast-container">
@@ -195,7 +224,8 @@ class SdNotifyService {
195
224
  title: option?.title,
196
225
  duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,
197
226
  actionLabel: option?.actionLabel,
198
- onAction: option?.onAction
227
+ onAction: option?.onAction,
228
+ html: option?.html
199
229
  };
200
230
  this.toasts.update((current) => {
201
231
  const updated = [newToast, ...current];
@@ -233,7 +263,8 @@ class SdNotifyService {
233
263
  title: finalTitle,
234
264
  duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,
235
265
  actionLabel: option?.actionLabel,
236
- onAction: option?.onAction
266
+ onAction: option?.onAction,
267
+ html: option?.html
237
268
  };
238
269
  this.toasts.update((current) => {
239
270
  const updated = [newToast, ...current];
@@ -247,10 +278,10 @@ class SdNotifyService {
247
278
  });
248
279
  this.#timers = {};
249
280
  }
250
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdNotifyService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
251
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdNotifyService, providedIn: 'root' });
281
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdNotifyService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
282
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdNotifyService, providedIn: 'root' });
252
283
  }
253
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdNotifyService, decorators: [{
284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdNotifyService, decorators: [{
254
285
  type: Injectable,
255
286
  args: [{
256
287
  providedIn: 'root',
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { Component, Input, signal, HostListener, OnInit, OnDestroy } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { animate, style, transition, trigger } from '@angular/animations';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n animations: [\n trigger('toastAnimation', [\n transition(':enter', [\n style({ transform: 'translateX(100%)', opacity: 0 }),\n animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))\n ]),\n transition(':leave', [\n animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))\n ])\n ])\n ],\n host: {\n '[@toastAnimation]': 'true',\n '[class]': '\"bg-white sd-toast\"'\n }\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n \n isExpanded = signal(false);\n readonly MAX_SHOW = 2;\n\n // --- Logic Timer ---\n private timer: any;\n private start!: number;\n private remaining!: number;\n\n constructor(private notifyService: SdNotifyService) {}\n\n ngOnInit() {\n // Khởi tạo thời gian còn lại bằng duration ban đầu\n this.remaining = this.data.duration;\n // Bắt đầu đếm ngược ngay khi hiện ra\n this.resumeTimer();\n }\n\n ngOnDestroy() {\n clearTimeout(this.timer);\n }\n\n // --- Logic Pause/Resume ---\n\n @HostListener('mouseenter')\n pauseTimer() {\n if (this.timer) {\n // Xóa timer hiện tại\n clearTimeout(this.timer);\n this.timer = null;\n // Tính toán thời gian đã trôi qua để trừ đi\n this.remaining -= Date.now() - this.start;\n }\n }\n\n @HostListener('mouseleave')\n resumeTimer() {\n // Chỉ chạy tiếp nếu còn thời gian và timer chưa chạy\n if (this.remaining > 0 && !this.timer) {\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n }\n\n // --- Logic cũ ---\n\n close() {\n this.notifyService.remove(this.data.id);\n }\n\n onActionClick() {\n this.data.onAction?.();\n }\n\n toggleExpand() {\n this.isExpanded.update(v => !v);\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n}","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">✕</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <div [innerHTML]=\"data.message\"></div>\n } \n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n <li [innerHTML]=\"'- ' + msg\"></li>\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n ApplicationRef,\n createComponent,\n EnvironmentInjector,\n Inject,\n Injectable,\n signal\n} from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n\n constructor(\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n @Inject(DOCUMENT) private document: Document\n ) {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update((current) => current.filter((t) => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update((current) => current.filter((t) => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n \n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n \n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const typeLabel = type === 'error' ? 'Error' : 'Warning';\n const title = option?.title ?? typeLabel;\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages, \n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { Component, Input, signal, HostListener, OnInit, OnDestroy, SecurityContext } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { animate, style, transition, trigger } from '@angular/animations';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n animations: [\n trigger('toastAnimation', [\n transition(':enter', [\n style({ transform: 'translateX(100%)', opacity: 0 }),\n animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))\n ]),\n transition(':leave', [\n animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))\n ])\n ])\n ],\n host: {\n '[@toastAnimation]': 'true',\n '[class]': '\"bg-white sd-toast\"',\n // E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.\n '[attr.data-autoid]': 'autoId',\n '[attr.data-type]': 'data.type',\n '[attr.data-title]': 'data.title ?? null',\n '[attr.data-message]': 'dataMessage'\n }\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n \n isExpanded = signal(false);\n readonly MAX_SHOW = 2;\n\n // --- Logic Timer ---\n private timer: any;\n private start!: number;\n private remaining!: number;\n\n constructor(\n private notifyService: SdNotifyService,\n private sanitizer: DomSanitizer\n ) {}\n\n /**\n * Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip\n * `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh\n * `data.html === true`; nhánh mặc định render text (auto-escape) nên không\n * có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.\n */\n sanitizeHtml(value: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';\n }\n\n /** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */\n get singleMessage(): string {\n return this.data.message as string;\n }\n\n ngOnInit() {\n // Khởi tạo thời gian còn lại bằng duration ban đầu\n this.remaining = this.data.duration;\n // Bắt đầu đếm ngược ngay khi hiện ra\n this.resumeTimer();\n }\n\n ngOnDestroy() {\n clearTimeout(this.timer);\n }\n\n // --- Logic Pause/Resume ---\n\n @HostListener('mouseenter')\n pauseTimer() {\n if (this.timer) {\n // Xóa timer hiện tại\n clearTimeout(this.timer);\n this.timer = null;\n // Tính toán thời gian đã trôi qua để trừ đi\n this.remaining -= Date.now() - this.start;\n }\n }\n\n @HostListener('mouseleave')\n resumeTimer() {\n // Chỉ chạy tiếp nếu còn thời gian và timer chưa chạy\n if (this.remaining > 0 && !this.timer) {\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n }\n\n // --- Logic cũ ---\n\n close() {\n this.notifyService.remove(this.data.id);\n }\n\n onActionClick() {\n this.data.onAction?.();\n }\n\n toggleExpand() {\n this.isExpanded.update(v => !v);\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n\n /** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */\n get autoId(): string {\n return `services-notify-toast-${this.data.type}`;\n }\n\n /** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */\n get dataMessage(): string {\n return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;\n }\n}","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">✕</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: mặc định render TEXT (auto-escape) — an toàn. Chỉ render HTML\n (đã sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n ApplicationRef,\n createComponent,\n EnvironmentInjector,\n Inject,\n Injectable,\n signal\n} from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n\n constructor(\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n @Inject(DOCUMENT) private document: Document\n ) {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update((current) => current.filter((t) => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update((current) => current.filter((t) => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n \n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n \n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const typeLabel = type === 'error' ? 'Error' : 'Warning';\n const title = option?.title ?? typeLabel;\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages, \n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":";;;;;;;;MAmCa,cAAc,CAAA;AAYf,IAAA,aAAA;AACA,IAAA,SAAA;AAZiB,IAAA,IAAI;AAE/B,IAAA,UAAU,GAAG,MAAM,CAAC,KAAK,sDAAC;IACjB,QAAQ,GAAG,CAAC;;AAGb,IAAA,KAAK;AACL,IAAA,KAAK;AACL,IAAA,SAAS;IAEjB,WAAA,CACU,aAA8B,EAC9B,SAAuB,EAAA;QADvB,IAAA,CAAA,aAAa,GAAb,aAAa;QACb,IAAA,CAAA,SAAS,GAAT,SAAS;IAChB;AAEH;;;;;AAKG;AACH,IAAA,YAAY,CAAC,KAAa,EAAA;AACxB,QAAA,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;IACnE;;AAGA,IAAA,IAAI,aAAa,GAAA;AACf,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,OAAiB;IACpC;IAEA,QAAQ,GAAA;;QAEN,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ;;QAEnC,IAAI,CAAC,WAAW,EAAE;IACpB;IAEA,WAAW,GAAA;AACT,QAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;IAC1B;;IAKA,UAAU,GAAA;AACR,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE;;AAEd,YAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;AACxB,YAAA,IAAI,CAAC,KAAK,GAAG,IAAI;;YAEjB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;QAC3C;IACF;IAGA,WAAW,GAAA;;QAET,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;AACrC,YAAA,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE;AACvB,YAAA,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,MAAK;gBAC3B,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;QACpB;IACF;;IAIA,KAAK,GAAA;QACH,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACzC;IAEA,aAAa,GAAA;AACX,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI;IACxB;IAEA,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC;AAEA,IAAA,IAAI,cAAc,GAAA;QAChB,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;IACzC;AAEA,IAAA,IAAI,QAAQ,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,OAAmB;IACtC;AAEA,IAAA,IAAI,eAAe,GAAA;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ;QAC1B,IAAI,IAAI,CAAC,UAAU,EAAE;AAAE,YAAA,OAAO,IAAI;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC;IACrC;AAEA,IAAA,IAAI,SAAS,GAAA;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;IAC7C;AAEA,IAAA,IAAI,OAAO,GAAA;AACT,QAAA,OAAO,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;IACpE;;AAGA,IAAA,IAAI,MAAM,GAAA;AACR,QAAA,OAAO,yBAAyB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;IAClD;;AAGA,IAAA,IAAI,WAAW,GAAA;AACb,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO;IAC7F;wGA9GW,cAAc,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAAA,eAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,YAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,cAAc,wYCnC3B,kiFAkEM,EAAA,MAAA,EAAA,CAAA,80CAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDvDM,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAE,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,UAAA,EAGzB;YACV,OAAO,CAAC,gBAAgB,EAAE;gBACxB,UAAU,CAAC,QAAQ,EAAE;oBACnB,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACpD,oBAAA,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;iBAC5E,CAAC;gBACF,UAAU,CAAC,QAAQ,EAAE;AACnB,oBAAA,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;iBAC9E;aACF;AACF,SAAA,EAAA,CAAA;;4FAWU,cAAc,EAAA,UAAA,EAAA,CAAA;kBA3B1B,SAAS;+BACE,OAAO,EAAA,UAAA,EACL,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,aAAa,CAAC,EAAA,UAAA,EAG1B;wBACV,OAAO,CAAC,gBAAgB,EAAE;4BACxB,UAAU,CAAC,QAAQ,EAAE;gCACnB,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACpD,gCAAA,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;6BAC5E,CAAC;4BACF,UAAU,CAAC,QAAQ,EAAE;AACnB,gCAAA,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;6BAC9E;yBACF;qBACF,EAAA,IAAA,EACK;AACJ,wBAAA,mBAAmB,EAAE,MAAM;AAC3B,wBAAA,SAAS,EAAE,qBAAqB;;AAEhC,wBAAA,oBAAoB,EAAE,QAAQ;AAC9B,wBAAA,kBAAkB,EAAE,WAAW;AAC/B,wBAAA,mBAAmB,EAAE,oBAAoB;AACzC,wBAAA,qBAAqB,EAAE;AACxB,qBAAA,EAAA,QAAA,EAAA,kiFAAA,EAAA,MAAA,EAAA,CAAA,80CAAA,CAAA,EAAA;;sBAGA,KAAK;uBAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;;sBA2CxB,YAAY;uBAAC,YAAY;;sBAWzB,YAAY;uBAAC,YAAY;;;ME1Df,uBAAuB,CAAA;AAClC,IAAA,MAAM,GAAgC,MAAM,CAAC,EAAE,kDAAC;wGADrC,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAvB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAxBxB;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,yJAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAPS,cAAc,EAAA,QAAA,EAAA,OAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAyBb,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBA5BnC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,cACf,IAAI,EAAA,OAAA,EACP,CAAC,cAAc,CAAC,EAAA,QAAA,EACf;;;;;;AAMT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,yJAAA,CAAA,EAAA;;;MCEU,eAAe,CAAA;AAchB,IAAA,MAAA;AACA,IAAA,QAAA;AACkB,IAAA,QAAA;AAfnB,IAAA,MAAM,GAAG,MAAM,CAAc,EAAE,kDAAC;;IAGhC,cAAc,GAAG,GAAG;IACpB,yBAAyB,GAAG,IAAI;IAChC,0BAA0B,GAAG,IAAI;IACjC,WAAW,GAAG,CAAC;;IAGxB,OAAO,GAA6B,EAAE;IACtC,OAAO,GAAyD,EAAE;AAElE,IAAA,WAAA,CACU,MAAsB,EACtB,QAA6B,EACX,QAAkB,EAAA;QAFpC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,QAAQ,GAAR,QAAQ;QACU,IAAA,CAAA,QAAQ,GAAR,QAAQ;QAElC,IAAI,CAAC,cAAc,EAAE;IACvB;IAEA,cAAc,GAAA;AACZ,QAAA,MAAM,YAAY,GAAG,eAAe,CAAC,uBAAuB,EAAE;YAC5D,mBAAmB,EAAE,IAAI,CAAC,QAAQ;AACnC,SAAA,CAAC;QACF,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;QAC1C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC7C,MAAM,OAAO,GAAI,YAAY,CAAC,QAAgB,CAAC,SAAS,CAAC,CAAC,CAAgB;QAC1E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;IACzC;;IAGA,OAAO,CAAC,OAAe,EAAE,MAAqB,EAAA;QAC5C,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC;IAChD;IAEA,IAAI,CAAC,OAAe,EAAE,MAAqB,EAAA;QACzC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;IAC7C;IAEA,OAAO,CAAC,OAA0B,EAAE,MAAqB,EAAA;QACvD,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC;IAC/C;IAEA,KAAK,CAAC,OAA0B,EAAE,MAAqB,EAAA;QACrD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC;IAC7C;AAEA,IAAA,MAAM,CAAC,EAAU,EAAA;QACf,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE;IAEA,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,eAAe,EAAE;IACxB;AAEA,IAAA,WAAW,CAAC,IAAe,EAAA;QACzB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACzE;;AAGA,IAAA,aAAa,CAAC,IAAe,EAAE,OAAe,EAAE,MAAqB,EAAA;AACnE,QAAA,MAAM,QAAQ,GAAc;AAC1B,YAAA,EAAE,EAAE,SAAS,CAAC,YAAY,EAAE;YAC5B,IAAI;YACJ,OAAO;YACP,KAAK,EAAE,MAAM,EAAE,KAAK;AACpB,YAAA,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC,yBAAyB;YAC5D,WAAW,EAAE,MAAM,EAAE,WAAW;YAChC,QAAQ,EAAE,MAAM,EAAE,QAAQ;YAC1B,IAAI,EAAE,MAAM,EAAE;SACf;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAI;YAC7B,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC;YACtC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;AAC3C,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,YAAY,CAAC,IAAe,EAAE,OAA0B,EAAE,MAAqB,EAAA;QAC7E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AACvB,YAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;QACzB;AAEA,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC;QACzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;;AAGhC,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACtB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAE,CAAC;QACnC;QAEA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAK;AACnC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;AACjC,QAAA,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC;IACzB;IAEA,YAAY,CAAC,IAAe,EAAE,MAAqB,EAAA;AACjD,QAAA,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;;AAGjD,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;AACvB,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;AAEzB,QAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE;AAE3B,QAAA,MAAM,SAAS,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,SAAS;AACxD,QAAA,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,SAAS;QACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,KAAK,CAAA,EAAA,EAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,KAAK;AAEhF,QAAA,MAAM,QAAQ,GAAc;AAC1B,YAAA,EAAE,EAAE,SAAS,CAAC,YAAY,EAAE;YAC5B,IAAI;AACJ,YAAA,OAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ;AACvD,YAAA,KAAK,EAAE,UAAU;AACjB,YAAA,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC,0BAA0B;YAC7D,WAAW,EAAE,MAAM,EAAE,WAAW;YAChC,QAAQ,EAAE,MAAM,EAAE,QAAQ;YAC1B,IAAI,EAAE,MAAM,EAAE;SACf;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAI;YAC7B,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC;YACtC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;AAC3C,QAAA,CAAC,CAAC;IACJ;IAEA,eAAe,GAAA;AACb,QAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,IAAG;AAC1C,YAAA,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC;AAChC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,OAAO,GAAG,EAAE;IACnB;AArIW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,mFAgBhB,QAAQ,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAhBP,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,cAFd,MAAM,EAAA,CAAA;;4FAEP,eAAe,EAAA,UAAA,EAAA,CAAA;kBAH3B,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;0BAiBI,MAAM;2BAAC,QAAQ;;;AChCpB;;AAEG;;;;"}
@@ -151,10 +151,10 @@ class SdStorageService {
151
151
  return val;
152
152
  return JSON.parse(JSON.stringify(val));
153
153
  }
154
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdStorageService, deps: [{ token: SD_STORAGE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
155
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdStorageService, providedIn: 'root' });
154
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdStorageService, deps: [{ token: SD_STORAGE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
155
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdStorageService, providedIn: 'root' });
156
156
  }
157
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.23", ngImport: i0, type: SdStorageService, decorators: [{
157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdStorageService, decorators: [{
158
158
  type: Injectable,
159
159
  args: [{
160
160
  providedIn: 'root',
@@ -5,7 +5,7 @@ import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
5
5
  import { ErrorStateMatcher } from '@angular/material/core';
6
6
  import { MatFormFieldAppearance } from '@angular/material/form-field';
7
7
  import { SdItemDefDefDirective, SdViewDefDirective } from '@sdcorejs/angular/forms/directives';
8
- import { SdSearch, SdCustomValidator, SdSelectionData, SdFormControl } from '@sdcorejs/angular/forms/models';
8
+ import { SdSearch, SdViewed, SdViewedInput, SdCustomValidator, SdSelectionData, SdFormControl } from '@sdcorejs/angular/forms/models';
9
9
  import { Size } from '@sdcorejs/utils/models';
10
10
 
11
11
  declare class SdAutocompleteErrotStateMatcher implements ErrorStateMatcher {
@@ -50,7 +50,18 @@ declare class SdAutocomplete<T = any> implements OnInit, OnDestroy, AfterViewIni
50
50
  addable: _angular_core.InputSignalWithTransform<boolean, unknown>;
51
51
  required: _angular_core.InputSignalWithTransform<boolean, unknown>;
52
52
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
53
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
53
+ /** Display mode: `false` edit · `true` static view · `'inline'` view + click-to-edit (autocomplete panel). */
54
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
55
+ /** In `viewed='inline'`, show a hover clear-× on the text face. Set `false` when the host owns removal (chips). */
56
+ clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
57
+ /** `true` when `viewed === 'inline'`. */
58
+ readonly isInline: _angular_core.Signal<boolean>;
59
+ /** `true` when `viewed === true` (static view, no editor). */
60
+ readonly isViewed: _angular_core.Signal<boolean>;
61
+ /** Open the autocomplete panel from the inline text face. No-op unless `viewed='inline'`. */
62
+ enterInlineEdit: () => void;
63
+ /** View display template: `sdViewDef` overrides the projected `#sdValue` (unified). */
64
+ readonly viewTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
54
65
  validator: _angular_core.InputSignal<SdCustomValidator | undefined>;
55
66
  inlineError: _angular_core.InputSignal<string | undefined>;
56
67
  /**
@@ -85,11 +96,13 @@ declare class SdAutocomplete<T = any> implements OnInit, OnDestroy, AfterViewIni
85
96
  onClick: () => void;
86
97
  blur: () => void;
87
98
  focus: () => void;
99
+ /** Open the autocomplete panel programmatically (anchors to the field input). Used by inline mode. */
100
+ open: () => void;
88
101
  clear: ($event?: any) => void;
89
102
  onAdd: ($event: Event) => void;
90
103
  reValidate: () => void;
91
104
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdAutocomplete<any>, never>;
92
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdAutocomplete<any>, "sd-autocomplete", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "valueField": { "alias": "valueField"; "required": false; "isSignal": true; }; "displayField": { "alias": "displayField"; "required": false; "isSignal": true; }; "disabledField": { "alias": "disabledField"; "required": false; "isSignal": true; }; "limit": { "alias": "limit"; "required": false; "isSignal": true; }; "cacheChecksum": { "alias": "cacheChecksum"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "addable": { "alias": "addable"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "validator": { "alias": "validator"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdSelection": "sdSelection"; "sdAdd": "sdAdd"; }, ["sdLabelTemplate", "sdValueTemplate", "itemDef", "sdViewDef"], ["[sdLabel]"], true, never>;
105
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdAutocomplete<any>, "sd-autocomplete", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "valueField": { "alias": "valueField"; "required": false; "isSignal": true; }; "displayField": { "alias": "displayField"; "required": false; "isSignal": true; }; "disabledField": { "alias": "disabledField"; "required": false; "isSignal": true; }; "limit": { "alias": "limit"; "required": false; "isSignal": true; }; "cacheChecksum": { "alias": "cacheChecksum"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "addable": { "alias": "addable"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "validator": { "alias": "validator"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdSelection": "sdSelection"; "sdAdd": "sdAdd"; }, ["sdLabelTemplate", "sdValueTemplate", "itemDef", "sdViewDef"], ["[sdLabel]"], true, never>;
93
106
  }
94
107
 
95
108
  export { SdAutocomplete };
@@ -1,6 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { OnDestroy, AfterViewInit } from '@angular/core';
3
3
  import { FormControl, FormGroup } from '@angular/forms';
4
+ import { SdViewed, SdViewedInput } from '@sdcorejs/angular/forms/models';
4
5
  import { Color } from '@sdcorejs/utils/models';
5
6
 
6
7
  declare class SdCheckbox implements OnDestroy, AfterViewInit {
@@ -13,7 +14,10 @@ declare class SdCheckbox implements OnDestroy, AfterViewInit {
13
14
  readonly label: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
14
15
  readonly color: _angular_core.InputSignalWithTransform<Color, Color | null | undefined>;
15
16
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
16
- readonly viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
17
+ /** Display mode: `false` edit · `true` static view · `'inline'` interactive (disabled `'inline'` → static). */
18
+ readonly viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
19
+ /** `true` when the static read-only view should render. */
20
+ readonly isViewed: _angular_core.Signal<boolean>;
17
21
  readonly inlineError: _angular_core.InputSignalWithTransform<string, string | null | undefined>;
18
22
  readonly model: _angular_core.ModelSignal<unknown>;
19
23
  readonly sdChange: _angular_core.OutputEmitterRef<unknown>;
@@ -6,7 +6,7 @@ import { MatChipInputEvent } from '@angular/material/chips';
6
6
  import { ErrorStateMatcher } from '@angular/material/core';
7
7
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
8
8
  import { SdViewDefDirective, SdLabelDefDirective } from '@sdcorejs/angular/forms/directives';
9
- import { SdFormControl } from '@sdcorejs/angular/forms/models';
9
+ import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
10
10
  import { Size } from '@sdcorejs/utils/models';
11
11
 
12
12
  declare class SdRemovableChipPipe implements PipeTransform {
@@ -45,7 +45,10 @@ declare class SdChip implements AfterViewInit {
45
45
  max: _angular_core.InputSignalWithTransform<number, number | null | undefined>;
46
46
  addable: _angular_core.InputSignalWithTransform<boolean, unknown>;
47
47
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
48
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
48
+ /** Display mode: `false` edit · `true` static view · `'inline'` interactive (disabled `'inline'` → static). */
49
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
50
+ /** `true` when the static view should render (`viewed===true`, or disabled `'inline'`). */
51
+ readonly isViewed: _angular_core.Signal<boolean>;
49
52
  hyperlink: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
50
53
  model: _angular_core.ModelSignal<(string | number)[] | undefined>;
51
54
  sdChange: _angular_core.OutputEmitterRef<any[]>;
@@ -7,7 +7,7 @@ import { MatCalendar } from '@angular/material/datepicker';
7
7
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
8
8
  import { MatMenuTrigger } from '@angular/material/menu';
9
9
  import { SdViewDefDirective, SdLabelDefDirective } from '@sdcorejs/angular/forms/directives';
10
- import { SdFormControl } from '@sdcorejs/angular/forms/models';
10
+ import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
11
11
  import { Size } from '@sdcorejs/utils/models';
12
12
 
13
13
  declare class SdChipCalendarErrorStateMatcher implements ErrorStateMatcher {
@@ -40,7 +40,10 @@ declare class SdChipCalendar implements AfterViewInit {
40
40
  min: _angular_core.InputSignalWithTransform<number, number | null | undefined>;
41
41
  max: _angular_core.InputSignalWithTransform<number, number | null | undefined>;
42
42
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
43
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
43
+ /** Display mode: `false` edit · `true` static view · `'inline'` interactive (disabled `'inline'` → static). */
44
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
45
+ /** `true` when the static view should render (`viewed===true`, or disabled `'inline'`). */
46
+ readonly isViewed: _angular_core.Signal<boolean>;
44
47
  hyperlink: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
45
48
  model: _angular_core.ModelSignal<(string | number)[] | undefined>;
46
49
  sdChange: _angular_core.OutputEmitterRef<any[]>;
@@ -4,7 +4,7 @@ import { FormGroup } from '@angular/forms';
4
4
  import { MatDatepicker, MatDatepickerInputEvent } from '@angular/material/datepicker';
5
5
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
6
6
  import { SdViewDefDirective, SdLabelDefDirective } from '@sdcorejs/angular/forms/directives';
7
- import { SdFormControl } from '@sdcorejs/angular/forms/models';
7
+ import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
8
8
  import { Size } from '@sdcorejs/utils/models';
9
9
 
10
10
  declare class SdDate implements OnDestroy, OnInit {
@@ -35,9 +35,18 @@ declare class SdDate implements OnDestroy, OnInit {
35
35
  hideInlineError: _angular_core.InputSignalWithTransform<boolean, unknown>;
36
36
  required: _angular_core.InputSignalWithTransform<boolean, unknown>;
37
37
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
38
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
39
- /** Flatten the field chrome to a chip-friendly trigger (value + caret only). */
40
- bare: _angular_core.InputSignalWithTransform<boolean, unknown>;
38
+ /** Display mode: `false` edit · `true` static view · `'inline'` view + click-to-edit (calendar). */
39
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
40
+ /** In `viewed='inline'`, show a hover clear-× on the text face. Set `false` when the host owns removal (chips). */
41
+ clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
42
+ /** `true` when `viewed === 'inline'`. */
43
+ readonly isInline: _angular_core.Signal<boolean>;
44
+ /** `true` when `viewed === true` (static view, no editor). */
45
+ readonly isViewed: _angular_core.Signal<boolean>;
46
+ /** Open the calendar from the inline text face. No-op unless `viewed='inline'`. */
47
+ enterInlineEdit: () => void;
48
+ /** View display template: `sdViewDef` overrides the projected `#sdValue` (unified). */
49
+ readonly viewTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
41
50
  inlineError: _angular_core.InputSignal<string | undefined>;
42
51
  /**
43
52
  * Tổng hợp error message để hiển thị trong tooltip khi hideInlineError = true.
@@ -76,7 +85,7 @@ declare class SdDate implements OnDestroy, OnInit {
76
85
  onChange: (event: MatDatepickerInputEvent<Date>) => void;
77
86
  clear: ($event: any) => void;
78
87
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdDate, never>;
79
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDate, "sd-date", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "bare": { "alias": "bare"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "minDateInput": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "maxDateInput": { "alias": "maxDate"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdFocus": "sdFocus"; }, ["sdLabelTemplate", "sdValueTemplate", "sdViewDef", "sdLabelDef"], ["[sdLabel]"], true, never>;
88
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDate, "sd-date", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "minDateInput": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "maxDateInput": { "alias": "maxDate"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdFocus": "sdFocus"; }, ["sdLabelTemplate", "sdValueTemplate", "sdViewDef", "sdLabelDef"], ["[sdLabel]"], true, never>;
80
89
  }
81
90
 
82
91
  export { SdDate };
@@ -4,6 +4,7 @@ import { FormGroup, FormControl } from '@angular/forms';
4
4
  import { MatDateRangePicker, MatDatepickerInputEvent } from '@angular/material/datepicker';
5
5
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
6
6
  import { SdLabelDefDirective } from '@sdcorejs/angular/forms/directives';
7
+ import { SdViewed, SdViewedInput } from '@sdcorejs/angular/forms/models';
7
8
  import { Size } from '@sdcorejs/utils/models';
8
9
 
9
10
  interface Daterange {
@@ -38,10 +39,16 @@ declare class SdDateRange implements OnDestroy, OnInit {
38
39
  readonly errorMessage: _angular_core.Signal<string | undefined>;
39
40
  required: _angular_core.InputSignalWithTransform<boolean, unknown>;
40
41
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
41
- /** Bare mode strip the form-field shell to fit inline in a chip / token. */
42
- bare: _angular_core.InputSignalWithTransform<boolean, unknown>;
43
- /** Viewed mode render a read-only <sd-view> instead of the editable form-field. */
44
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
42
+ /** Display mode: `false` edit · `true` static view · `'inline'` view + click-to-edit (range picker). */
43
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
44
+ /** In `viewed='inline'`, show a hover clear-× on the text face. Set `false` when the host owns removal (chips). */
45
+ clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
46
+ /** `true` when `viewed === 'inline'`. */
47
+ readonly isInline: _angular_core.Signal<boolean>;
48
+ /** `true` when `viewed === true` (static view, no editor). */
49
+ readonly isViewed: _angular_core.Signal<boolean>;
50
+ /** Open the range picker from the inline text face. No-op unless `viewed='inline'`. */
51
+ enterInlineEdit: () => void;
45
52
  /** Optional <ng-template #sdValue> projected by consumer to override the viewed text. */
46
53
  sdValueTemplate: _angular_core.Signal<TemplateRef<unknown> | undefined>;
47
54
  /**
@@ -77,7 +84,7 @@ declare class SdDateRange implements OnDestroy, OnInit {
77
84
  onClosePicker: () => void;
78
85
  onOpenPicker: (event: MouseEvent) => void;
79
86
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdDateRange, never>;
80
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDateRange, "sd-date-range", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "bare": { "alias": "bare"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; }, ["sdLabelDef", "sdValueTemplate"], never, true, never>;
87
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDateRange, "sd-date-range", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; }, ["sdLabelDef", "sdValueTemplate"], never, true, never>;
81
88
  }
82
89
 
83
90
  export { SdDateRange };
@@ -3,7 +3,7 @@ import { OnDestroy, OnInit, ElementRef, TemplateRef } from '@angular/core';
3
3
  import { FormGroup } from '@angular/forms';
4
4
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
5
5
  import { SdViewDefDirective } from '@sdcorejs/angular/forms/directives';
6
- import { SdFormControl } from '@sdcorejs/angular/forms/models';
6
+ import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
7
7
  import { Size } from '@sdcorejs/utils/models';
8
8
 
9
9
  declare class SdDatetime implements OnDestroy, OnInit {
@@ -35,11 +35,20 @@ declare class SdDatetime implements OnDestroy, OnInit {
35
35
  hideInlineError: _angular_core.InputSignalWithTransform<boolean, unknown>;
36
36
  required: _angular_core.InputSignalWithTransform<boolean, unknown>;
37
37
  disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
38
- viewed: _angular_core.InputSignalWithTransform<boolean, unknown>;
38
+ /** Display mode: `false` edit · `true` static view · `'inline'` view + click-to-edit (datetime overlay). */
39
+ viewed: _angular_core.InputSignalWithTransform<SdViewed, SdViewedInput>;
39
40
  /** Hiển thị thêm cột giây trong picker. Mặc định: chỉ HH:MM. */
40
41
  showSeconds: _angular_core.InputSignalWithTransform<boolean, unknown>;
41
- /** Flatten the field chrome to a chip-friendly trigger (value + caret only). */
42
- bare: _angular_core.InputSignalWithTransform<boolean, unknown>;
42
+ /** In `viewed='inline'`, show a hover clear-× on the text face. Set `false` when the host owns removal (chips). */
43
+ clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
44
+ /** `true` when `viewed === 'inline'`. */
45
+ readonly isInline: _angular_core.Signal<boolean>;
46
+ /** `true` when `viewed === true` (static view, no editor). */
47
+ readonly isViewed: _angular_core.Signal<boolean>;
48
+ /** Open the datetime overlay from the inline text face. No-op unless `viewed='inline'`. */
49
+ enterInlineEdit: () => void;
50
+ /** View display template: `sdViewDef` overrides the projected `#sdValue` (unified). */
51
+ readonly viewTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
43
52
  inlineError: _angular_core.InputSignal<string | undefined>;
44
53
  /**
45
54
  * Tổng hợp error message để hiển thị trong tooltip khi hideInlineError = true.
@@ -86,7 +95,7 @@ declare class SdDatetime implements OnDestroy, OnInit {
86
95
  onConfirmInput: (event: any) => void;
87
96
  clear: ($event: any) => void;
88
97
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdDatetime, never>;
89
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDatetime, "sd-datetime", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "showSeconds": { "alias": "showSeconds"; "required": false; "isSignal": true; }; "bare": { "alias": "bare"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "minDateInput": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "maxDateInput": { "alias": "maxDate"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdFocus": "sdFocus"; }, ["sdLabelTemplate", "sdValueTemplate", "sdViewDef"], ["[sdLabel]"], true, never>;
98
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdDatetime, "sd-datetime", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "hideInlineError": { "alias": "hideInlineError"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "showSeconds": { "alias": "showSeconds"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "hyperlink": { "alias": "hyperlink"; "required": false; "isSignal": true; }; "appearanceInput": { "alias": "appearance"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "minInput": { "alias": "min"; "required": false; "isSignal": true; }; "minDateInput": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxInput": { "alias": "max"; "required": false; "isSignal": true; }; "maxDateInput": { "alias": "maxDate"; "required": false; "isSignal": true; }; "valueModel": { "alias": "model"; "required": false; "isSignal": true; }; }, { "valueModel": "modelChange"; "sdChange": "sdChange"; "sdFocus": "sdFocus"; }, ["sdLabelTemplate", "sdValueTemplate", "sdViewDef"], ["[sdLabel]"], true, never>;
90
99
  }
91
100
 
92
101
  export { SdDatetime };