@lifecorner/angular-ui-kit 3.19.2 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-alert.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-bar-chart.mjs +67 -8
- package/fesm2022/lifecorner-angular-ui-kit-bar-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-money.mjs +28 -2
- package/fesm2022/lifecorner-angular-ui-kit-money.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/theme-contrast.spec.ts +81 -0
- package/types/lifecorner-angular-ui-kit-bar-chart.d.ts +105 -10
- package/types/lifecorner-angular-ui-kit-money.d.ts +33 -2
|
@@ -153,7 +153,7 @@ var AlertComponent = class AlertComponent {
|
|
|
153
153
|
} },
|
|
154
154
|
ngImport: i0,
|
|
155
155
|
template: "@if (visible()) {\n <div [class]=\"containerClasses()\">\n @if (showIcon()) {\n <div class=\"lc-alert__icon\">\n <lc-icon\n [name]=\"defaultIcon()\"\n size=\"md\"\n [decorative]=\"!iconAriaLabel()\"\n [ariaLabel]=\"iconAriaLabel()\"\n />\n </div>\n }\n \n <div class=\"lc-alert__body\">\n @if (title()) {\n <div class=\"lc-alert__title\">{{ title() }}</div>\n }\n \n <div class=\"lc-alert__content\">\n @if (message()) {\n {{ message() }}\n } @else {\n <ng-content />\n }\n </div>\n </div>\n\n <div class=\"lc-alert__action\">\n <ng-content select=\"[slot='action']\" />\n </div>\n\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"lc-alert__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n </div>\n}\n",
|
|
156
|
-
styles: [".lc-alert{display:block;width:100%;container:lc-alert/inline-size;border-radius:var(--border-radius-md);border-width:1px;border-style:solid;transition:opacity .15s ease-in-out}.lc-alert--hidden{display:none}.lc-alert__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-alert__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-alert__body{flex:1;min-width:0}.lc-alert__title{font-weight:var(--font-weight-semibold);font-size:var(--font-size-sm);line-height:var(--line-height-tight);margin-bottom:var(--spacing-1)}.lc-alert__content{font-size:var(--font-size-sm);line-height:var(--line-height-normal)}.lc-alert--actions-stacked{container:lc-alert/inline-size}.lc-alert--actions-stacked .lc-alert__container{flex-wrap:wrap}.lc-alert--actions-stacked .lc-alert__body{flex:1 1 0}.lc-alert--actions-stacked .lc-alert__action{order:1;flex:1 1 100%;align-self:auto;display:flex;flex-direction:column;align-items:stretch;gap:var(--spacing-2, .5rem);margin-top:var(--spacing-1, .25rem)}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:100%}.lc-alert--actions-stacked.lc-alert--with-icon .lc-alert__action{padding-inline-start:calc(24px + var(--spacing-3, .75rem))}@container lc-alert (width >= 36rem){.lc-alert--actions-stacked .lc-alert__action{flex-direction:row;flex-wrap:wrap;align-items:center}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:auto}}.lc-alert__action{flex-shrink:0;align-self:center}.lc-alert__action:empty{display:none}.lc-alert__action>a:where(:not(.btn)){display:flex;align-items:center;width:fit-content;max-width:100%;min-width:var(--size-min-touch-width);min-height:var(--size-min-touch-height);font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:var(--line-height-normal);color:var(--color-link);text-align:start;text-decoration-line:underline;text-decoration-thickness:max(1px,.0625em);text-underline-offset:.15em;overflow-wrap:anywhere;border-radius:var(--border-radius-sm)}.lc-alert__action>a:where(:not(.btn)):hover{color:var(--color-link-hover);text-decoration-thickness:max(2px,.125em)}.lc-alert__action>a:where(:not(.btn)):focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-alert:has(>.lc-alert__container>.lc-alert__action>a:where(:not(.btn))){container:lc-alert/inline-size}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){flex-shrink:1;min-width:0;max-width:50%}@container lc-alert (width < 36rem){.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn))){flex-wrap:wrap}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__body{flex:1 1 0}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__action{order:1;flex:1 1 100%;max-width:none;margin-top:var(--spacing-1)}.lc-alert--with-icon:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){padding-inline-start:calc(24px + var(--spacing-3))}}.lc-alert__close{flex-shrink:0;background:transparent;border:none;cursor:pointer;padding:var(--spacing-1);margin:calc(var(--spacing-1) * -1);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-alert__close:hover{background-color:var(--color-surface-hover)}.lc-alert__close:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@container lc-alert (width < 16rem){.lc-alert__container{flex-wrap:wrap;column-gap:var(--spacing-3);row-gap:var(--spacing-1);padding:var(--lc-density-padding-sm, var(--spacing-3))}.lc-alert__body{display:contents}.lc-alert__title,.lc-alert__content{overflow-wrap:break-word;hyphens:auto}.lc-alert__title{flex:1 1 0%;max-width:100%;margin-bottom:0}.lc-alert__content{order:1;flex:1 1 100%;min-width:0}.lc-alert__content:first-child{order:0;flex:1 1 0%;min-width:auto;max-width:100%}.lc-alert.lc-alert .lc-alert__container>.lc-alert__action.lc-alert__action.lc-alert__action{order:1;flex:1 1 100%;min-width:0;max-width:none;align-self:auto;margin-top:var(--spacing-3);padding-inline-start:0;--lc-btn-overflow-wrap: anywhere}.lc-alert__action>a:where(:not(.btn)){min-width:min(var(--size-min-touch-width),100%)}.lc-alert__close{margin-inline-start:auto}}.lc-alert--success{background-color:#8ea4751f;border-color:#8ea47566;color:var(--color-text-primary)}.lc-alert--success .lc-alert__icon{color:var(--color-success)}.lc-alert--success .lc-alert__close:hover{background-color:#8ea4752e}.lc-alert--error{background-color:#9d0e0e24;border-color:#9d0e0e73;color:var(--color-text-primary)}.lc-alert--error .lc-alert__icon{color:var(--color-error)}.lc-alert--error .lc-alert__close:hover{background-color:#9d0e0e33}.lc-alert--warning{background-color:#e1a04024;border-color:#e1a0406b;color:var(--color-text-primary)}.lc-alert--warning .lc-alert__icon{color:var(--color-warning)}.lc-alert--warning .lc-alert__close:hover{background-color:#e1a04033}.lc-alert--info{background-color:#3b658829;border-color:#3b65886b;color:var(--color-text-primary)}.lc-alert--info .lc-alert__icon{color:var(--color-info)}.lc-alert--info .lc-alert__close:hover{background-color:#3b658838}@media(prefers-reduced-motion:no-preference){.lc-alert--hidden{animation:fadeOut .15s ease-in-out}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"],
|
|
156
|
+
styles: [".lc-alert{display:block;width:100%;container:lc-alert/inline-size;border-radius:var(--border-radius-md);border-width:1px;border-style:solid;transition:opacity .15s ease-in-out}.lc-alert--hidden{display:none}.lc-alert__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-alert__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-alert__body{flex:1;min-width:0}.lc-alert__title{font-weight:var(--font-weight-semibold);font-size:var(--font-size-sm);line-height:var(--line-height-tight);margin-bottom:var(--spacing-1)}.lc-alert__content{font-size:var(--font-size-sm);line-height:var(--line-height-normal)}.lc-alert--actions-stacked{container:lc-alert/inline-size}.lc-alert--actions-stacked .lc-alert__container{flex-wrap:wrap}.lc-alert--actions-stacked .lc-alert__body{flex:1 1 0}.lc-alert--actions-stacked .lc-alert__action{order:1;flex:1 1 100%;align-self:auto;display:flex;flex-direction:column;align-items:stretch;gap:var(--spacing-2, .5rem);margin-top:var(--spacing-1, .25rem)}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:100%}.lc-alert--actions-stacked.lc-alert--with-icon .lc-alert__action{padding-inline-start:calc(24px + var(--spacing-3, .75rem))}@container lc-alert (width >= 36rem){.lc-alert--actions-stacked .lc-alert__action{flex-direction:row;flex-wrap:wrap;align-items:center}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:auto}}.lc-alert__action{flex-shrink:0;align-self:center}.lc-alert__action:empty{display:none}.lc-alert__action>a:where(:not(.btn)){display:flex;align-items:center;width:fit-content;max-width:100%;min-width:var(--size-min-touch-width);min-height:var(--size-min-touch-height);font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:var(--line-height-normal);color:var(--color-link);text-align:start;text-decoration-line:underline;text-decoration-thickness:max(1px,.0625em);text-underline-offset:.15em;overflow-wrap:anywhere;border-radius:var(--border-radius-sm)}.lc-alert__action>a:where(:not(.btn)):hover{color:var(--color-link-hover);text-decoration-thickness:max(2px,.125em)}.lc-alert__action>a:where(:not(.btn)):focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-alert:has(>.lc-alert__container>.lc-alert__action>a:where(:not(.btn))){container:lc-alert/inline-size}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){flex-shrink:1;min-width:0;max-width:50%}@container lc-alert (width < 36rem){.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn))){flex-wrap:wrap}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__body{flex:1 1 0}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__action{order:1;flex:1 1 100%;max-width:none;margin-top:var(--spacing-1)}.lc-alert--with-icon:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){padding-inline-start:calc(24px + var(--spacing-3))}}.lc-alert__close{flex-shrink:0;background:transparent;border:none;cursor:pointer;padding:var(--spacing-1);margin:calc(var(--spacing-1) * -1);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-alert__close:hover{background-color:var(--color-surface-hover)}.lc-alert__close:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@container lc-alert (width < 16rem){.lc-alert__container{flex-wrap:wrap;column-gap:var(--spacing-3);row-gap:var(--spacing-1);padding:var(--lc-density-padding-sm, var(--spacing-3))}.lc-alert__body{display:contents}.lc-alert__title,.lc-alert__content{overflow-wrap:break-word;hyphens:auto}.lc-alert__title{flex:1 1 0%;max-width:100%;margin-bottom:0}.lc-alert__content{order:1;flex:1 1 100%;min-width:0}.lc-alert__content:first-child{order:0;flex:1 1 0%;min-width:auto;max-width:100%}.lc-alert.lc-alert .lc-alert__container>.lc-alert__action.lc-alert__action.lc-alert__action{order:1;flex:1 1 100%;min-width:0;max-width:none;align-self:auto;margin-top:var(--spacing-3);padding-inline-start:0;--lc-btn-overflow-wrap: anywhere}.lc-alert__action>a:where(:not(.btn)){min-width:min(var(--size-min-touch-width),100%)}.lc-alert__close{margin-inline-start:auto}}.lc-alert--success{background-color:#8ea4751f;border-color:#8ea47566;color:var(--color-text-primary)}.lc-alert--success .lc-alert__icon{color:var(--color-on-success-subtle)}.lc-alert--success .lc-alert__close:hover{background-color:#8ea4752e}.lc-alert--error{background-color:#9d0e0e24;border-color:#9d0e0e73;color:var(--color-text-primary)}.lc-alert--error .lc-alert__icon{color:var(--color-on-error-subtle)}.lc-alert--error .lc-alert__close:hover{background-color:#9d0e0e33}.lc-alert--warning{background-color:#e1a04024;border-color:#e1a0406b;color:var(--color-text-primary)}.lc-alert--warning .lc-alert__icon{color:var(--color-on-warning-subtle)}.lc-alert--warning .lc-alert__close:hover{background-color:#e1a04033}.lc-alert--info{background-color:#3b658829;border-color:#3b65886b;color:var(--color-text-primary)}.lc-alert--info .lc-alert__icon{color:var(--color-on-info-subtle)}.lc-alert--info .lc-alert__close:hover{background-color:#3b658838}@media(prefers-reduced-motion:no-preference){.lc-alert--hidden{animation:fadeOut .15s ease-in-out}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"],
|
|
157
157
|
dependencies: [{
|
|
158
158
|
kind: "component",
|
|
159
159
|
type: IconComponent,
|
|
@@ -206,7 +206,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
206
206
|
"[attr.aria-label]": "ariaLabel()"
|
|
207
207
|
},
|
|
208
208
|
template: "@if (visible()) {\n <div [class]=\"containerClasses()\">\n @if (showIcon()) {\n <div class=\"lc-alert__icon\">\n <lc-icon\n [name]=\"defaultIcon()\"\n size=\"md\"\n [decorative]=\"!iconAriaLabel()\"\n [ariaLabel]=\"iconAriaLabel()\"\n />\n </div>\n }\n \n <div class=\"lc-alert__body\">\n @if (title()) {\n <div class=\"lc-alert__title\">{{ title() }}</div>\n }\n \n <div class=\"lc-alert__content\">\n @if (message()) {\n {{ message() }}\n } @else {\n <ng-content />\n }\n </div>\n </div>\n\n <div class=\"lc-alert__action\">\n <ng-content select=\"[slot='action']\" />\n </div>\n\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"lc-alert__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n </div>\n}\n",
|
|
209
|
-
styles: [".lc-alert{display:block;width:100%;container:lc-alert/inline-size;border-radius:var(--border-radius-md);border-width:1px;border-style:solid;transition:opacity .15s ease-in-out}.lc-alert--hidden{display:none}.lc-alert__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-alert__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-alert__body{flex:1;min-width:0}.lc-alert__title{font-weight:var(--font-weight-semibold);font-size:var(--font-size-sm);line-height:var(--line-height-tight);margin-bottom:var(--spacing-1)}.lc-alert__content{font-size:var(--font-size-sm);line-height:var(--line-height-normal)}.lc-alert--actions-stacked{container:lc-alert/inline-size}.lc-alert--actions-stacked .lc-alert__container{flex-wrap:wrap}.lc-alert--actions-stacked .lc-alert__body{flex:1 1 0}.lc-alert--actions-stacked .lc-alert__action{order:1;flex:1 1 100%;align-self:auto;display:flex;flex-direction:column;align-items:stretch;gap:var(--spacing-2, .5rem);margin-top:var(--spacing-1, .25rem)}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:100%}.lc-alert--actions-stacked.lc-alert--with-icon .lc-alert__action{padding-inline-start:calc(24px + var(--spacing-3, .75rem))}@container lc-alert (width >= 36rem){.lc-alert--actions-stacked .lc-alert__action{flex-direction:row;flex-wrap:wrap;align-items:center}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:auto}}.lc-alert__action{flex-shrink:0;align-self:center}.lc-alert__action:empty{display:none}.lc-alert__action>a:where(:not(.btn)){display:flex;align-items:center;width:fit-content;max-width:100%;min-width:var(--size-min-touch-width);min-height:var(--size-min-touch-height);font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:var(--line-height-normal);color:var(--color-link);text-align:start;text-decoration-line:underline;text-decoration-thickness:max(1px,.0625em);text-underline-offset:.15em;overflow-wrap:anywhere;border-radius:var(--border-radius-sm)}.lc-alert__action>a:where(:not(.btn)):hover{color:var(--color-link-hover);text-decoration-thickness:max(2px,.125em)}.lc-alert__action>a:where(:not(.btn)):focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-alert:has(>.lc-alert__container>.lc-alert__action>a:where(:not(.btn))){container:lc-alert/inline-size}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){flex-shrink:1;min-width:0;max-width:50%}@container lc-alert (width < 36rem){.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn))){flex-wrap:wrap}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__body{flex:1 1 0}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__action{order:1;flex:1 1 100%;max-width:none;margin-top:var(--spacing-1)}.lc-alert--with-icon:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){padding-inline-start:calc(24px + var(--spacing-3))}}.lc-alert__close{flex-shrink:0;background:transparent;border:none;cursor:pointer;padding:var(--spacing-1);margin:calc(var(--spacing-1) * -1);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-alert__close:hover{background-color:var(--color-surface-hover)}.lc-alert__close:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@container lc-alert (width < 16rem){.lc-alert__container{flex-wrap:wrap;column-gap:var(--spacing-3);row-gap:var(--spacing-1);padding:var(--lc-density-padding-sm, var(--spacing-3))}.lc-alert__body{display:contents}.lc-alert__title,.lc-alert__content{overflow-wrap:break-word;hyphens:auto}.lc-alert__title{flex:1 1 0%;max-width:100%;margin-bottom:0}.lc-alert__content{order:1;flex:1 1 100%;min-width:0}.lc-alert__content:first-child{order:0;flex:1 1 0%;min-width:auto;max-width:100%}.lc-alert.lc-alert .lc-alert__container>.lc-alert__action.lc-alert__action.lc-alert__action{order:1;flex:1 1 100%;min-width:0;max-width:none;align-self:auto;margin-top:var(--spacing-3);padding-inline-start:0;--lc-btn-overflow-wrap: anywhere}.lc-alert__action>a:where(:not(.btn)){min-width:min(var(--size-min-touch-width),100%)}.lc-alert__close{margin-inline-start:auto}}.lc-alert--success{background-color:#8ea4751f;border-color:#8ea47566;color:var(--color-text-primary)}.lc-alert--success .lc-alert__icon{color:var(--color-success)}.lc-alert--success .lc-alert__close:hover{background-color:#8ea4752e}.lc-alert--error{background-color:#9d0e0e24;border-color:#9d0e0e73;color:var(--color-text-primary)}.lc-alert--error .lc-alert__icon{color:var(--color-error)}.lc-alert--error .lc-alert__close:hover{background-color:#9d0e0e33}.lc-alert--warning{background-color:#e1a04024;border-color:#e1a0406b;color:var(--color-text-primary)}.lc-alert--warning .lc-alert__icon{color:var(--color-warning)}.lc-alert--warning .lc-alert__close:hover{background-color:#e1a04033}.lc-alert--info{background-color:#3b658829;border-color:#3b65886b;color:var(--color-text-primary)}.lc-alert--info .lc-alert__icon{color:var(--color-info)}.lc-alert--info .lc-alert__close:hover{background-color:#3b658838}@media(prefers-reduced-motion:no-preference){.lc-alert--hidden{animation:fadeOut .15s ease-in-out}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"]
|
|
209
|
+
styles: [".lc-alert{display:block;width:100%;container:lc-alert/inline-size;border-radius:var(--border-radius-md);border-width:1px;border-style:solid;transition:opacity .15s ease-in-out}.lc-alert--hidden{display:none}.lc-alert__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-alert__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-alert__body{flex:1;min-width:0}.lc-alert__title{font-weight:var(--font-weight-semibold);font-size:var(--font-size-sm);line-height:var(--line-height-tight);margin-bottom:var(--spacing-1)}.lc-alert__content{font-size:var(--font-size-sm);line-height:var(--line-height-normal)}.lc-alert--actions-stacked{container:lc-alert/inline-size}.lc-alert--actions-stacked .lc-alert__container{flex-wrap:wrap}.lc-alert--actions-stacked .lc-alert__body{flex:1 1 0}.lc-alert--actions-stacked .lc-alert__action{order:1;flex:1 1 100%;align-self:auto;display:flex;flex-direction:column;align-items:stretch;gap:var(--spacing-2, .5rem);margin-top:var(--spacing-1, .25rem)}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:100%}.lc-alert--actions-stacked.lc-alert--with-icon .lc-alert__action{padding-inline-start:calc(24px + var(--spacing-3, .75rem))}@container lc-alert (width >= 36rem){.lc-alert--actions-stacked .lc-alert__action{flex-direction:row;flex-wrap:wrap;align-items:center}.lc-alert--actions-stacked .lc-alert__action>lc-button>.btn{width:auto}}.lc-alert__action{flex-shrink:0;align-self:center}.lc-alert__action:empty{display:none}.lc-alert__action>a:where(:not(.btn)){display:flex;align-items:center;width:fit-content;max-width:100%;min-width:var(--size-min-touch-width);min-height:var(--size-min-touch-height);font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:var(--line-height-normal);color:var(--color-link);text-align:start;text-decoration-line:underline;text-decoration-thickness:max(1px,.0625em);text-underline-offset:.15em;overflow-wrap:anywhere;border-radius:var(--border-radius-sm)}.lc-alert__action>a:where(:not(.btn)):hover{color:var(--color-link-hover);text-decoration-thickness:max(2px,.125em)}.lc-alert__action>a:where(:not(.btn)):focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.lc-alert:has(>.lc-alert__container>.lc-alert__action>a:where(:not(.btn))){container:lc-alert/inline-size}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){flex-shrink:1;min-width:0;max-width:50%}@container lc-alert (width < 36rem){.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn))){flex-wrap:wrap}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__body{flex:1 1 0}.lc-alert:not(.lc-alert--actions-stacked) .lc-alert__container:has(>.lc-alert__action>a:where(:not(.btn)))>.lc-alert__action{order:1;flex:1 1 100%;max-width:none;margin-top:var(--spacing-1)}.lc-alert--with-icon:not(.lc-alert--actions-stacked) .lc-alert__action:has(>a:where(:not(.btn))){padding-inline-start:calc(24px + var(--spacing-3))}}.lc-alert__close{flex-shrink:0;background:transparent;border:none;cursor:pointer;padding:var(--spacing-1);margin:calc(var(--spacing-1) * -1);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-alert__close:hover{background-color:var(--color-surface-hover)}.lc-alert__close:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}@container lc-alert (width < 16rem){.lc-alert__container{flex-wrap:wrap;column-gap:var(--spacing-3);row-gap:var(--spacing-1);padding:var(--lc-density-padding-sm, var(--spacing-3))}.lc-alert__body{display:contents}.lc-alert__title,.lc-alert__content{overflow-wrap:break-word;hyphens:auto}.lc-alert__title{flex:1 1 0%;max-width:100%;margin-bottom:0}.lc-alert__content{order:1;flex:1 1 100%;min-width:0}.lc-alert__content:first-child{order:0;flex:1 1 0%;min-width:auto;max-width:100%}.lc-alert.lc-alert .lc-alert__container>.lc-alert__action.lc-alert__action.lc-alert__action{order:1;flex:1 1 100%;min-width:0;max-width:none;align-self:auto;margin-top:var(--spacing-3);padding-inline-start:0;--lc-btn-overflow-wrap: anywhere}.lc-alert__action>a:where(:not(.btn)){min-width:min(var(--size-min-touch-width),100%)}.lc-alert__close{margin-inline-start:auto}}.lc-alert--success{background-color:#8ea4751f;border-color:#8ea47566;color:var(--color-text-primary)}.lc-alert--success .lc-alert__icon{color:var(--color-on-success-subtle)}.lc-alert--success .lc-alert__close:hover{background-color:#8ea4752e}.lc-alert--error{background-color:#9d0e0e24;border-color:#9d0e0e73;color:var(--color-text-primary)}.lc-alert--error .lc-alert__icon{color:var(--color-on-error-subtle)}.lc-alert--error .lc-alert__close:hover{background-color:#9d0e0e33}.lc-alert--warning{background-color:#e1a04024;border-color:#e1a0406b;color:var(--color-text-primary)}.lc-alert--warning .lc-alert__icon{color:var(--color-on-warning-subtle)}.lc-alert--warning .lc-alert__close:hover{background-color:#e1a04033}.lc-alert--info{background-color:#3b658829;border-color:#3b65886b;color:var(--color-text-primary)}.lc-alert--info .lc-alert__icon{color:var(--color-on-info-subtle)}.lc-alert--info .lc-alert__close:hover{background-color:#3b658838}@media(prefers-reduced-motion:no-preference){.lc-alert--hidden{animation:fadeOut .15s ease-in-out}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"]
|
|
210
210
|
}]
|
|
211
211
|
}],
|
|
212
212
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-alert.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/alert/alert.component.ts","../../../../libs/angular-ui-kit/src/lib/alert/alert.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n model,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type AlertVariant = 'success' | 'error' | 'warning' | 'info';\n\n/**\n * Layout of the projected actions (`slot=\"action\"`).\n * - `inline`: one action right of the body, vertically centred (3.2)\n * - `stacked`: the actions in their own row below the title and message;\n * below 576 px of alert width one per line at full width, 8 px apart,\n * from 576 px side by side at their natural width\n *\n * In the narrow layout (below 16rem) the actions start at the alert's inner\n * edge, not under the text.\n */\nexport type AlertActionsLayout = 'inline' | 'stacked';\n\n/**\n * Alert component for inline notifications.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Optional title and icon display\n * - Auto-mapped variant icons\n * - Dismissible with close button; `visible` is two-way bindable so a\n * dismissed alert can be shown again\n * - Content projection for custom body\n * - Action slot for a button rendered right of the body (e.g. retry), or\n * several actions stacked below it (`actionsLayout=\"stacked\"`); 3.12: a text\n * link (`<a slot=\"action\" href=\"…\">`) as the one way forward gets a\n * 44 × 44 px target, link colour, an underline and a focus ring\n * - 3.13.1: narrow layout below 16rem inside the border (alerts under 258 px\n * at 100 % text, 514 px at 200 %): the icon shares the first row with the\n * title, message and actions take the full width below, the padding is one\n * density step smaller (`--lc-density-padding-sm`), and long words\n * hyphenate (`hyphens: auto`, needs the page's `lang`) or break instead of\n * leaving the box\n * - The variant icon is decorative unless `iconAriaLabel` names it (the\n * title and the colour carry the tone; no built-in English word)\n * - Accessible: `role=\"alert\"` for error / warning, `role=\"status\"` otherwise\n *\n * @example\n * ```html\n * <lc-alert variant=\"success\" title=\"Success!\" [dismissible]=\"true\">\n * Your changes have been saved.\n * </lc-alert>\n *\n * <lc-alert variant=\"error\" title=\"Laden fehlgeschlagen\">\n * Die Liste konnte nicht geladen werden.\n * <lc-button slot=\"action\" variant=\"outline\" size=\"sm\">Erneut versuchen</lc-button>\n * </lc-alert>\n *\n * <lc-alert variant=\"error\" title=\"Senden gerade nicht möglich\">\n * Wir können Ihre Nachricht gerade nicht senden.\n * <a slot=\"action\" href=\"mailto:kontakt@beispiel.example\">Schreiben Sie uns an kontakt@beispiel.example</a>\n * </lc-alert>\n * ```\n */\n@Component({\n selector: 'lc-alert',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './alert.component.html',\n styleUrls: ['./alert.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'role()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-label]': 'ariaLabel()',\n },\n})\nexport class AlertComponent {\n /**\n * Visual variant of the alert\n * @default 'info'\n */\n variant = input<AlertVariant>('info');\n\n /**\n * Alert title (optional)\n */\n title = input<string>();\n\n /**\n * Alert message (alternative to content projection)\n */\n message = input<string>();\n\n /**\n * Icon to display (defaults based on variant)\n */\n icon = input<string>();\n\n /**\n * Whether to show icon\n * @default true\n */\n showIcon = input<boolean>(true);\n\n /**\n * Accessible name of the variant icon, e.g. „Warnung“. Leave it unset when\n * the title already says what kind of message this is: the icon is then\n * decorative (`aria-hidden`). The kit sets no default text, so no English\n * word is read on a page in another language.\n */\n iconAriaLabel = input<string | undefined>(undefined);\n\n /**\n * Layout of the projected actions (`slot=\"action\"`): `inline` — one action\n * right of the body (3.2); `stacked` — the actions in their own row below\n * the title and message, one per line at full width and 8 px apart below\n * 576 px of alert width, side by side at their natural width from there.\n * @default 'inline'\n */\n actionsLayout = input<AlertActionsLayout>('inline');\n\n /**\n * Whether the alert can be dismissed\n * @default false\n */\n dismissible = input<boolean>(false);\n\n /**\n * ARIA label for accessibility\n */\n ariaLabel = input<string>();\n\n /** Accessible name of the close button (`dismissible`). */\n closeAriaLabel = input('Close alert');\n\n /**\n * Emitted when alert is dismissed\n */\n readonly dismissed = output<void>();\n\n /**\n * Visibility (two-way bindable). Dismissing sets it to `false`; the parent\n * can set it back to `true` to show the alert again.\n */\n visible = model<boolean>(true);\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const classes = ['lc-alert', `lc-alert--${this.variant()}`];\n if (this.actionsLayout() === 'stacked') {\n classes.push('lc-alert--actions-stacked');\n }\n if (this.showIcon()) {\n classes.push('lc-alert--with-icon');\n }\n if (!this.visible()) {\n classes.push('lc-alert--hidden');\n }\n return classes.join(' ');\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-alert__container';\n });\n\n /** Whether the variant should interrupt the user (error / warning). */\n private readonly isUrgent = computed(() => {\n const variant = this.variant();\n return variant === 'error' || variant === 'warning';\n });\n\n /**\n * Live-region role: `alert` (assertive) only for error / warning,\n * `status` (polite) for info / success.\n */\n protected role = computed(() => (this.isUrgent() ? 'alert' : 'status'));\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => (this.isUrgent() ? 'assertive' : 'polite'));\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<AlertVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return this.icon() || icons[this.variant()];\n });\n\n /**\n * Dismiss the alert\n */\n dismiss(): void {\n this.visible.set(false);\n this.dismissed.emit();\n }\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.dismiss();\n }\n}\n","@if (visible()) {\n <div [class]=\"containerClasses()\">\n @if (showIcon()) {\n <div class=\"lc-alert__icon\">\n <lc-icon\n [name]=\"defaultIcon()\"\n size=\"md\"\n [decorative]=\"!iconAriaLabel()\"\n [ariaLabel]=\"iconAriaLabel()\"\n />\n </div>\n }\n \n <div class=\"lc-alert__body\">\n @if (title()) {\n <div class=\"lc-alert__title\">{{ title() }}</div>\n }\n \n <div class=\"lc-alert__content\">\n @if (message()) {\n {{ message() }}\n } @else {\n <ng-content />\n }\n </div>\n </div>\n\n <div class=\"lc-alert__action\">\n <ng-content select=\"[slot='action']\" />\n </div>\n\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"lc-alert__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-alert.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/alert/alert.component.ts","../../../../libs/angular-ui-kit/src/lib/alert/alert.component.html"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n model,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\n\nexport type AlertVariant = 'success' | 'error' | 'warning' | 'info';\n\n/**\n * Layout of the projected actions (`slot=\"action\"`).\n * - `inline`: one action right of the body, vertically centred (3.2)\n * - `stacked`: the actions in their own row below the title and message;\n * below 576 px of alert width one per line at full width, 8 px apart,\n * from 576 px side by side at their natural width\n *\n * In the narrow layout (below 16rem) the actions start at the alert's inner\n * edge, not under the text.\n */\nexport type AlertActionsLayout = 'inline' | 'stacked';\n\n/**\n * Alert component for inline notifications.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Optional title and icon display\n * - Auto-mapped variant icons\n * - Dismissible with close button; `visible` is two-way bindable so a\n * dismissed alert can be shown again\n * - Content projection for custom body\n * - Action slot for a button rendered right of the body (e.g. retry), or\n * several actions stacked below it (`actionsLayout=\"stacked\"`); 3.12: a text\n * link (`<a slot=\"action\" href=\"…\">`) as the one way forward gets a\n * 44 × 44 px target, link colour, an underline and a focus ring\n * - 3.13.1: narrow layout below 16rem inside the border (alerts under 258 px\n * at 100 % text, 514 px at 200 %): the icon shares the first row with the\n * title, message and actions take the full width below, the padding is one\n * density step smaller (`--lc-density-padding-sm`), and long words\n * hyphenate (`hyphens: auto`, needs the page's `lang`) or break instead of\n * leaving the box\n * - The variant icon is decorative unless `iconAriaLabel` names it (the\n * title and the colour carry the tone; no built-in English word)\n * - Accessible: `role=\"alert\"` for error / warning, `role=\"status\"` otherwise\n *\n * @example\n * ```html\n * <lc-alert variant=\"success\" title=\"Success!\" [dismissible]=\"true\">\n * Your changes have been saved.\n * </lc-alert>\n *\n * <lc-alert variant=\"error\" title=\"Laden fehlgeschlagen\">\n * Die Liste konnte nicht geladen werden.\n * <lc-button slot=\"action\" variant=\"outline\" size=\"sm\">Erneut versuchen</lc-button>\n * </lc-alert>\n *\n * <lc-alert variant=\"error\" title=\"Senden gerade nicht möglich\">\n * Wir können Ihre Nachricht gerade nicht senden.\n * <a slot=\"action\" href=\"mailto:kontakt@beispiel.example\">Schreiben Sie uns an kontakt@beispiel.example</a>\n * </lc-alert>\n * ```\n */\n@Component({\n selector: 'lc-alert',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './alert.component.html',\n styleUrls: ['./alert.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'role()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-label]': 'ariaLabel()',\n },\n})\nexport class AlertComponent {\n /**\n * Visual variant of the alert\n * @default 'info'\n */\n variant = input<AlertVariant>('info');\n\n /**\n * Alert title (optional)\n */\n title = input<string>();\n\n /**\n * Alert message (alternative to content projection)\n */\n message = input<string>();\n\n /**\n * Icon to display (defaults based on variant)\n */\n icon = input<string>();\n\n /**\n * Whether to show icon\n * @default true\n */\n showIcon = input<boolean>(true);\n\n /**\n * Accessible name of the variant icon, e.g. „Warnung“. Leave it unset when\n * the title already says what kind of message this is: the icon is then\n * decorative (`aria-hidden`). The kit sets no default text, so no English\n * word is read on a page in another language.\n */\n iconAriaLabel = input<string | undefined>(undefined);\n\n /**\n * Layout of the projected actions (`slot=\"action\"`): `inline` — one action\n * right of the body (3.2); `stacked` — the actions in their own row below\n * the title and message, one per line at full width and 8 px apart below\n * 576 px of alert width, side by side at their natural width from there.\n * @default 'inline'\n */\n actionsLayout = input<AlertActionsLayout>('inline');\n\n /**\n * Whether the alert can be dismissed\n * @default false\n */\n dismissible = input<boolean>(false);\n\n /**\n * ARIA label for accessibility\n */\n ariaLabel = input<string>();\n\n /** Accessible name of the close button (`dismissible`). */\n closeAriaLabel = input('Close alert');\n\n /**\n * Emitted when alert is dismissed\n */\n readonly dismissed = output<void>();\n\n /**\n * Visibility (two-way bindable). Dismissing sets it to `false`; the parent\n * can set it back to `true` to show the alert again.\n */\n visible = model<boolean>(true);\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const classes = ['lc-alert', `lc-alert--${this.variant()}`];\n if (this.actionsLayout() === 'stacked') {\n classes.push('lc-alert--actions-stacked');\n }\n if (this.showIcon()) {\n classes.push('lc-alert--with-icon');\n }\n if (!this.visible()) {\n classes.push('lc-alert--hidden');\n }\n return classes.join(' ');\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-alert__container';\n });\n\n /** Whether the variant should interrupt the user (error / warning). */\n private readonly isUrgent = computed(() => {\n const variant = this.variant();\n return variant === 'error' || variant === 'warning';\n });\n\n /**\n * Live-region role: `alert` (assertive) only for error / warning,\n * `status` (polite) for info / success.\n */\n protected role = computed(() => (this.isUrgent() ? 'alert' : 'status'));\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => (this.isUrgent() ? 'assertive' : 'polite'));\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<AlertVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return this.icon() || icons[this.variant()];\n });\n\n /**\n * Dismiss the alert\n */\n dismiss(): void {\n this.visible.set(false);\n this.dismissed.emit();\n }\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.dismiss();\n }\n}\n","@if (visible()) {\n <div [class]=\"containerClasses()\">\n @if (showIcon()) {\n <div class=\"lc-alert__icon\">\n <lc-icon\n [name]=\"defaultIcon()\"\n size=\"md\"\n [decorative]=\"!iconAriaLabel()\"\n [ariaLabel]=\"iconAriaLabel()\"\n />\n </div>\n }\n \n <div class=\"lc-alert__body\">\n @if (title()) {\n <div class=\"lc-alert__title\">{{ title() }}</div>\n }\n \n <div class=\"lc-alert__content\">\n @if (message()) {\n {{ message() }}\n } @else {\n <ng-content />\n }\n </div>\n </div>\n\n <div class=\"lc-alert__action\">\n <ng-content select=\"[slot='action']\" />\n </div>\n\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"lc-alert__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ var BarChartComponent = class BarChartComponent {
|
|
|
21
21
|
orientation = input("vertical", ...ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []);
|
|
22
22
|
showValues = input(true, ...ngDevMode ? [{ debugName: "showValues" }] : /* istanbul ignore next */ []);
|
|
23
23
|
showLabels = input(true, ...ngDevMode ? [{ debugName: "showLabels" }] : /* istanbul ignore next */ []);
|
|
24
|
+
labelLayout = input("axis", ...ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []);
|
|
24
25
|
showGrid = input(true, ...ngDevMode ? [{ debugName: "showGrid" }] : /* istanbul ignore next */ []);
|
|
25
26
|
barGap = input(.3, ...ngDevMode ? [{ debugName: "barGap" }] : /* istanbul ignore next */ []);
|
|
26
27
|
color = input("", ...ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []);
|
|
@@ -33,6 +34,13 @@ var BarChartComponent = class BarChartComponent {
|
|
|
33
34
|
effectiveWidth = computed(() => this._containerWidth() || this.width() || DEFAULT_WIDTH, ...ngDevMode ? [{ debugName: "effectiveWidth" }] : /* istanbul ignore next */ []);
|
|
34
35
|
viewBox = computed(() => `0 0 ${this.effectiveWidth()} ${this.height()}`, ...ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []);
|
|
35
36
|
isVertical = computed(() => this.orientation() === "vertical", ...ngDevMode ? [{ debugName: "isVertical" }] : /* istanbul ignore next */ []);
|
|
37
|
+
wrapRows = computed(() => this.labelLayout() === "wrap" && !this.isVertical(), ...ngDevMode ? [{ debugName: "wrapRows" }] : /* istanbul ignore next */ []);
|
|
38
|
+
wrapColumns = computed(() => this.labelLayout() === "wrap" && this.isVertical(), ...ngDevMode ? [{ debugName: "wrapColumns" }] : /* istanbul ignore next */ []);
|
|
39
|
+
paddingBottom = computed(() => {
|
|
40
|
+
if (!this.wrapColumns()) return this.PADDING_BOTTOM;
|
|
41
|
+
const negative = (this.data() ?? []).some((i) => toFinite(i.value) < 0);
|
|
42
|
+
return this.showValues() && negative ? 18 : 2;
|
|
43
|
+
}, ...ngDevMode ? [{ debugName: "paddingBottom" }] : /* istanbul ignore next */ []);
|
|
36
44
|
plot = computed(() => {
|
|
37
45
|
const w = this.effectiveWidth();
|
|
38
46
|
const h = this.height();
|
|
@@ -40,9 +48,12 @@ var BarChartComponent = class BarChartComponent {
|
|
|
40
48
|
x: this.PADDING_LEFT,
|
|
41
49
|
y: this.PADDING_TOP,
|
|
42
50
|
w: w - this.PADDING_LEFT - this.PADDING_RIGHT,
|
|
43
|
-
h: h - this.PADDING_TOP - this.
|
|
51
|
+
h: h - this.PADDING_TOP - this.paddingBottom()
|
|
44
52
|
};
|
|
45
53
|
}, ...ngDevMode ? [{ debugName: "plot" }] : /* istanbul ignore next */ []);
|
|
54
|
+
textOf(item, value, fmt) {
|
|
55
|
+
return item.valueText ?? fmt(value, item);
|
|
56
|
+
}
|
|
46
57
|
scale = computed(() => {
|
|
47
58
|
const values = (this.data() ?? []).map((i) => toFinite(i.value));
|
|
48
59
|
return niceScale(Math.min(0, ...values), Math.max(0, ...values), 4);
|
|
@@ -88,7 +99,7 @@ var BarChartComponent = class BarChartComponent {
|
|
|
88
99
|
color: singleColor || item.color || chartColor(i),
|
|
89
100
|
label: item.label || "",
|
|
90
101
|
value,
|
|
91
|
-
valueText:
|
|
102
|
+
valueText: this.textOf(item, value, fmt),
|
|
92
103
|
labelX: p.x + i * totalBarWidth + totalBarWidth / 2,
|
|
93
104
|
labelY: h - this.PADDING_BOTTOM + 16,
|
|
94
105
|
valueX: p.x + i * totalBarWidth + totalBarWidth / 2,
|
|
@@ -113,7 +124,7 @@ var BarChartComponent = class BarChartComponent {
|
|
|
113
124
|
color: singleColor || item.color || chartColor(i),
|
|
114
125
|
label: item.label || "",
|
|
115
126
|
value,
|
|
116
|
-
valueText:
|
|
127
|
+
valueText: this.textOf(item, value, fmt),
|
|
117
128
|
labelX: p.x - 6,
|
|
118
129
|
labelY: p.y + i * totalBarHeight + totalBarHeight / 2,
|
|
119
130
|
valueX: value >= 0 ? x + barW + 6 : x - 6,
|
|
@@ -139,13 +150,46 @@ var BarChartComponent = class BarChartComponent {
|
|
|
139
150
|
};
|
|
140
151
|
}, ...ngDevMode ? [{ debugName: "axisLine" }] : /* istanbul ignore next */ []);
|
|
141
152
|
plotBottom = computed(() => this.plot().y + this.plot().h, ...ngDevMode ? [{ debugName: "plotBottom" }] : /* istanbul ignore next */ []);
|
|
153
|
+
rowBounds = computed(() => {
|
|
154
|
+
const values = (this.data() ?? []).map((i) => toFinite(i.value));
|
|
155
|
+
const lo = Math.min(0, ...values);
|
|
156
|
+
return {
|
|
157
|
+
values,
|
|
158
|
+
lo,
|
|
159
|
+
range: Math.max(0, ...values) - lo || 1
|
|
160
|
+
};
|
|
161
|
+
}, ...ngDevMode ? [{ debugName: "rowBounds" }] : /* istanbul ignore next */ []);
|
|
162
|
+
rows = computed(() => {
|
|
163
|
+
const d = this.data() ?? [];
|
|
164
|
+
const { values, lo, range } = this.rowBounds();
|
|
165
|
+
const singleColor = this.color();
|
|
166
|
+
const fmt = this.formatValue();
|
|
167
|
+
return d.map((item, i) => {
|
|
168
|
+
const value = values[i];
|
|
169
|
+
return {
|
|
170
|
+
label: item.label || "",
|
|
171
|
+
valueText: this.textOf(item, value, fmt),
|
|
172
|
+
color: singleColor || item.color || chartColor(i),
|
|
173
|
+
x: `${(Math.min(value, 0) - lo) / range * 100}%`,
|
|
174
|
+
width: `${Math.abs(value) / range * 100}%`
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
|
|
178
|
+
rowZero = computed(() => {
|
|
179
|
+
const { lo, range } = this.rowBounds();
|
|
180
|
+
return lo < 0 ? `${-lo / range * 100}%` : null;
|
|
181
|
+
}, ...ngDevMode ? [{ debugName: "rowZero" }] : /* istanbul ignore next */ []);
|
|
142
182
|
effectiveAriaLabel = computed(() => {
|
|
143
183
|
const explicit = this.ariaLabel();
|
|
144
184
|
if (explicit) return explicit;
|
|
145
185
|
const d = this.data() ?? [];
|
|
146
186
|
if (!d.length) return "Bar chart: no data";
|
|
147
187
|
const fmt = this.formatValue();
|
|
148
|
-
return `Bar chart: ${d.map((item, i) =>
|
|
188
|
+
return `Bar chart: ${d.map((item, i) => {
|
|
189
|
+
const label = item.label || `Bar ${i + 1}`;
|
|
190
|
+
const text = this.textOf(item, toFinite(item.value), fmt);
|
|
191
|
+
return text === "" ? label : `${label} ${text}`;
|
|
192
|
+
}).join(", ")}`;
|
|
149
193
|
}, ...ngDevMode ? [{ debugName: "effectiveAriaLabel" }] : /* istanbul ignore next */ []);
|
|
150
194
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
151
195
|
minVersion: "12.0.0",
|
|
@@ -204,6 +248,13 @@ var BarChartComponent = class BarChartComponent {
|
|
|
204
248
|
isRequired: false,
|
|
205
249
|
transformFunction: null
|
|
206
250
|
},
|
|
251
|
+
labelLayout: {
|
|
252
|
+
classPropertyName: "labelLayout",
|
|
253
|
+
publicName: "labelLayout",
|
|
254
|
+
isSignal: true,
|
|
255
|
+
isRequired: false,
|
|
256
|
+
transformFunction: null
|
|
257
|
+
},
|
|
207
258
|
showGrid: {
|
|
208
259
|
classPropertyName: "showGrid",
|
|
209
260
|
publicName: "showGrid",
|
|
@@ -242,8 +293,8 @@ var BarChartComponent = class BarChartComponent {
|
|
|
242
293
|
},
|
|
243
294
|
host: { properties: { "style.max-width.px": "width()" } },
|
|
244
295
|
ngImport: i0,
|
|
245
|
-
template: "<svg\n width=\"100%\"\n
|
|
246
|
-
styles: [":host{display:block;width:100%}.lc-bar-chart{overflow:visible}.lc-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-bar-chart__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-bar-chart__bar{transition:opacity .15s ease}.lc-bar-chart__bar:hover{opacity:.8}.lc-bar-chart__value{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}\n"],
|
|
296
|
+
template: "@if (wrapRows()) {\n <!--\n Horizontal 'wrap' (3.21, request 028): HTML rows, so labels wrap and follow\n the text size. Bar x/width are percentages: final in the server HTML.\n No inline style (CSP). The children of role=\"img\" are presentational.\n -->\n <div\n class=\"lc-bar-chart lc-bar-chart--rows\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n >\n @for (row of rows(); track $index) {\n <div class=\"lc-bar-chart__row\">\n @if ((showLabels() && row.label) || (showValues() && row.valueText)) {\n <div class=\"lc-bar-chart__row-head\">\n @if (showLabels() && row.label) {\n <span class=\"lc-bar-chart__row-label\">{{ row.label }}</span>\n }\n @if (showValues() && row.valueText) {\n <span class=\"lc-bar-chart__row-value\">{{ row.valueText }}</span>\n }\n </div>\n }\n <svg\n width=\"100%\"\n aria-hidden=\"true\"\n focusable=\"false\"\n class=\"lc-bar-chart__row-track\"\n >\n @if (rowZero(); as zero) {\n <line\n [attr.x1]=\"zero\"\n y1=\"-25%\"\n [attr.x2]=\"zero\"\n y2=\"125%\"\n class=\"lc-bar-chart__zero-line\"\n />\n }\n <rect\n [attr.x]=\"row.x\"\n y=\"0\"\n [attr.width]=\"row.width\"\n height=\"100%\"\n [attr.fill]=\"row.color\"\n rx=\"2\"\n class=\"lc-bar-chart__row-bar\"\n />\n </svg>\n </div>\n }\n </div>\n} @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-bar-chart\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid lines -->\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"line.pos\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"line.pos\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"axisLine().x1 - 6\"\n [attr.y]=\"line.pos\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n } @else {\n <line\n [attr.x1]=\"line.pos\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"line.pos\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"line.pos\"\n [attr.y]=\"plotBottom() + 12\"\n text-anchor=\"middle\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n }\n }\n }\n\n <!-- Axes -->\n <line\n [attr.x1]=\"axisLine().x1\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"axisLine().x2\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__axis\"\n />\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"axisLine().by1\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"axisLine().by2\"\n class=\"lc-bar-chart__axis\"\n />\n\n <!-- Bars -->\n @for (bar of bars(); track $index) {\n <rect\n [attr.x]=\"bar.x\"\n [attr.y]=\"bar.y\"\n [attr.width]=\"bar.width\"\n [attr.height]=\"bar.height\"\n [attr.fill]=\"bar.color\"\n rx=\"2\"\n class=\"lc-bar-chart__bar\"\n />\n\n @if (showValues()) {\n <text\n [attr.x]=\"bar.valueX\"\n [attr.y]=\"bar.valueY\"\n [attr.text-anchor]=\"bar.valueAnchor\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__value\"\n >\n {{ bar.valueText }}\n </text>\n }\n\n <!-- One block (not nested), so the SVG's comment anchors stay as before 3.21. -->\n @if (showLabels() && !wrapColumns()) {\n <text\n [attr.x]=\"bar.labelX\"\n [attr.y]=\"bar.labelY\"\n [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__label\"\n >\n {{ bar.label }}\n </text>\n }\n }\n </svg>\n\n <!--\n Vertical 'wrap' (3.21, request 028): the category labels as HTML under the\n plot, one cell per column, wrapping within it. Hidden from assistive\n technology: the names are in the SVG's accessible name.\n -->\n @if (wrapColumns() && showLabels()) {\n <div class=\"lc-bar-chart__category-labels\" aria-hidden=\"true\">\n @for (bar of bars(); track $index) {\n <span class=\"lc-bar-chart__category-label\">{{ bar.label }}</span>\n }\n </div>\n }\n}\n",
|
|
297
|
+
styles: [":host{display:block;width:100%}.lc-bar-chart{overflow:visible}.lc-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-bar-chart__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-bar-chart__bar{transition:opacity .15s ease}.lc-bar-chart__bar:hover{opacity:.8}.lc-bar-chart__value{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-bar-chart--rows{display:flex;flex-direction:column;gap:var(--lc-bar-chart-row-gap, var(--spacing-4))}.lc-bar-chart__row{display:flex;flex-direction:column;gap:var(--spacing-1)}.lc-bar-chart__row-head{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--spacing-2);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-normal)}.lc-bar-chart__row-label{flex:1 1 auto;min-width:0;color:var(--color-text-primary);font-weight:var(--typography-font-weight-normal);overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-bar-chart__row-value{margin-inline-start:auto;min-width:0;text-align:end;color:var(--color-text-primary);font-weight:var(--typography-font-weight-semibold);font-variant-numeric:tabular-nums;overflow-wrap:break-word}.lc-bar-chart__row-track{display:block;width:100%;height:var(--lc-bar-chart-bar-size, .75rem);overflow:visible}.lc-bar-chart__zero-line{stroke:var(--color-text-tertiary);stroke-width:1}.lc-bar-chart__category-labels{display:flex;padding:var(--spacing-1) 10px 0 40px}.lc-bar-chart__category-label{flex:1 1 0%;min-width:0;padding-inline:2px;text-align:center;font-size:.6875rem;line-height:var(--typography-line-height-tight);color:var(--color-text-secondary);overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}@media(forced-colors:active){.lc-bar-chart__row-track{forced-color-adjust:none}.lc-bar-chart__row-bar{fill:Highlight}.lc-bar-chart__zero-line{stroke:CanvasText}}\n"],
|
|
247
298
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
248
299
|
});
|
|
249
300
|
};
|
|
@@ -259,8 +310,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
259
310
|
standalone: true,
|
|
260
311
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
261
312
|
host: { "[style.max-width.px]": "width()" },
|
|
262
|
-
template: "<svg\n width=\"100%\"\n
|
|
263
|
-
styles: [":host{display:block;width:100%}.lc-bar-chart{overflow:visible}.lc-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-bar-chart__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-bar-chart__bar{transition:opacity .15s ease}.lc-bar-chart__bar:hover{opacity:.8}.lc-bar-chart__value{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}\n"]
|
|
313
|
+
template: "@if (wrapRows()) {\n <!--\n Horizontal 'wrap' (3.21, request 028): HTML rows, so labels wrap and follow\n the text size. Bar x/width are percentages: final in the server HTML.\n No inline style (CSP). The children of role=\"img\" are presentational.\n -->\n <div\n class=\"lc-bar-chart lc-bar-chart--rows\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n >\n @for (row of rows(); track $index) {\n <div class=\"lc-bar-chart__row\">\n @if ((showLabels() && row.label) || (showValues() && row.valueText)) {\n <div class=\"lc-bar-chart__row-head\">\n @if (showLabels() && row.label) {\n <span class=\"lc-bar-chart__row-label\">{{ row.label }}</span>\n }\n @if (showValues() && row.valueText) {\n <span class=\"lc-bar-chart__row-value\">{{ row.valueText }}</span>\n }\n </div>\n }\n <svg\n width=\"100%\"\n aria-hidden=\"true\"\n focusable=\"false\"\n class=\"lc-bar-chart__row-track\"\n >\n @if (rowZero(); as zero) {\n <line\n [attr.x1]=\"zero\"\n y1=\"-25%\"\n [attr.x2]=\"zero\"\n y2=\"125%\"\n class=\"lc-bar-chart__zero-line\"\n />\n }\n <rect\n [attr.x]=\"row.x\"\n y=\"0\"\n [attr.width]=\"row.width\"\n height=\"100%\"\n [attr.fill]=\"row.color\"\n rx=\"2\"\n class=\"lc-bar-chart__row-bar\"\n />\n </svg>\n </div>\n }\n </div>\n} @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-bar-chart\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid lines -->\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"line.pos\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"line.pos\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"axisLine().x1 - 6\"\n [attr.y]=\"line.pos\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n } @else {\n <line\n [attr.x1]=\"line.pos\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"line.pos\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"line.pos\"\n [attr.y]=\"plotBottom() + 12\"\n text-anchor=\"middle\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n }\n }\n }\n\n <!-- Axes -->\n <line\n [attr.x1]=\"axisLine().x1\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"axisLine().x2\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__axis\"\n />\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"axisLine().by1\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"axisLine().by2\"\n class=\"lc-bar-chart__axis\"\n />\n\n <!-- Bars -->\n @for (bar of bars(); track $index) {\n <rect\n [attr.x]=\"bar.x\"\n [attr.y]=\"bar.y\"\n [attr.width]=\"bar.width\"\n [attr.height]=\"bar.height\"\n [attr.fill]=\"bar.color\"\n rx=\"2\"\n class=\"lc-bar-chart__bar\"\n />\n\n @if (showValues()) {\n <text\n [attr.x]=\"bar.valueX\"\n [attr.y]=\"bar.valueY\"\n [attr.text-anchor]=\"bar.valueAnchor\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__value\"\n >\n {{ bar.valueText }}\n </text>\n }\n\n <!-- One block (not nested), so the SVG's comment anchors stay as before 3.21. -->\n @if (showLabels() && !wrapColumns()) {\n <text\n [attr.x]=\"bar.labelX\"\n [attr.y]=\"bar.labelY\"\n [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__label\"\n >\n {{ bar.label }}\n </text>\n }\n }\n </svg>\n\n <!--\n Vertical 'wrap' (3.21, request 028): the category labels as HTML under the\n plot, one cell per column, wrapping within it. Hidden from assistive\n technology: the names are in the SVG's accessible name.\n -->\n @if (wrapColumns() && showLabels()) {\n <div class=\"lc-bar-chart__category-labels\" aria-hidden=\"true\">\n @for (bar of bars(); track $index) {\n <span class=\"lc-bar-chart__category-label\">{{ bar.label }}</span>\n }\n </div>\n }\n}\n",
|
|
314
|
+
styles: [":host{display:block;width:100%}.lc-bar-chart{overflow:visible}.lc-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-bar-chart__grid-line{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-bar-chart__bar{transition:opacity .15s ease}.lc-bar-chart__bar:hover{opacity:.8}.lc-bar-chart__value{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-bar-chart--rows{display:flex;flex-direction:column;gap:var(--lc-bar-chart-row-gap, var(--spacing-4))}.lc-bar-chart__row{display:flex;flex-direction:column;gap:var(--spacing-1)}.lc-bar-chart__row-head{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--spacing-2);font-size:var(--typography-font-size-sm);line-height:var(--typography-line-height-normal)}.lc-bar-chart__row-label{flex:1 1 auto;min-width:0;color:var(--color-text-primary);font-weight:var(--typography-font-weight-normal);overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-bar-chart__row-value{margin-inline-start:auto;min-width:0;text-align:end;color:var(--color-text-primary);font-weight:var(--typography-font-weight-semibold);font-variant-numeric:tabular-nums;overflow-wrap:break-word}.lc-bar-chart__row-track{display:block;width:100%;height:var(--lc-bar-chart-bar-size, .75rem);overflow:visible}.lc-bar-chart__zero-line{stroke:var(--color-text-tertiary);stroke-width:1}.lc-bar-chart__category-labels{display:flex;padding:var(--spacing-1) 10px 0 40px}.lc-bar-chart__category-label{flex:1 1 0%;min-width:0;padding-inline:2px;text-align:center;font-size:.6875rem;line-height:var(--typography-line-height-tight);color:var(--color-text-secondary);overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}@media(forced-colors:active){.lc-bar-chart__row-track{forced-color-adjust:none}.lc-bar-chart__row-bar{fill:Highlight}.lc-bar-chart__zero-line{stroke:CanvasText}}\n"]
|
|
264
315
|
}]
|
|
265
316
|
}],
|
|
266
317
|
ctorParameters: () => [],
|
|
@@ -313,6 +364,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
313
364
|
required: false
|
|
314
365
|
}]
|
|
315
366
|
}],
|
|
367
|
+
labelLayout: [{
|
|
368
|
+
type: i0.Input,
|
|
369
|
+
args: [{
|
|
370
|
+
isSignal: true,
|
|
371
|
+
alias: "labelLayout",
|
|
372
|
+
required: false
|
|
373
|
+
}]
|
|
374
|
+
}],
|
|
316
375
|
showGrid: [{
|
|
317
376
|
type: i0.Input,
|
|
318
377
|
args: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/bar-chart/bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/bar-chart/bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n inject,\n input,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n ChartValueFormatter,\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface BarChartItem {\n /** Bar value. Negative values draw downward (left) from the zero baseline. */\n value: number;\n label?: string;\n color?: string;\n}\n\nexport type BarChartOrientation = 'vertical' | 'horizontal';\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n@Component({\n selector: 'lc-bar-chart',\n standalone: true,\n templateUrl: './bar-chart.component.html',\n styleUrls: ['./bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Bar chart component for comparing categorical data.\n *\n * Features:\n * - Vertical and horizontal orientation\n * - Per-bar or uniform color support\n * - Negative values drawn from a zero baseline\n * - Optional value labels on bars\n * - Configurable grid and axis labels on a nice tick scale\n * - Adjustable bar gap spacing\n * - Responsive SVG rendering\n *\n * @example\n * ```html\n * <lc-bar-chart [data]=\"items\" orientation=\"vertical\" [showValues]=\"true\" />\n * ```\n */\nexport class BarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n }\n\n /** Data items. */\n data = input.required<BarChartItem[]>();\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n */\n width = input<number | undefined>(undefined);\n\n /** Chart height in pixels. */\n height = input<number>(200);\n\n /** Bar orientation. */\n orientation = input<BarChartOrientation>('vertical');\n\n /** Show value labels on bars. */\n showValues = input<boolean>(true);\n\n /** Show axis labels. */\n showLabels = input<boolean>(true);\n\n /** Show grid lines. */\n showGrid = input<boolean>(true);\n\n /** Gap between bars as ratio (0-1). */\n barGap = input<number>(0.3);\n\n /** Use a single color for all bars. */\n color = input<string>('');\n\n /** Formats value labels and axis ticks. Defaults to a float-safe `String(value)`. */\n formatValue = input<ChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary that lists\n * every bar's label and value, so assistive technology hears the data and\n * not just \"Bar chart\".\n */\n ariaLabel = input<string>('');\n\n // Layout constants\n private readonly PADDING_LEFT = 40;\n private readonly PADDING_RIGHT = 10;\n private readonly PADDING_TOP = 10;\n private readonly PADDING_BOTTOM = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(\n () => `0 0 ${this.effectiveWidth()} ${this.height()}`\n );\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n private readonly plot = computed(() => {\n const w = this.effectiveWidth();\n const h = this.height();\n return {\n x: this.PADDING_LEFT,\n y: this.PADDING_TOP,\n w: w - this.PADDING_LEFT - this.PADDING_RIGHT,\n h: h - this.PADDING_TOP - this.PADDING_BOTTOM,\n };\n });\n\n /**\n * One scale for grid *and* bars: bounds are nice multiples of the tick step,\n * always include zero, and extend below zero when the data does — so the top\n * tick is the value at the plot edge and negative bars hang off the baseline.\n */\n protected readonly scale = computed(() => {\n const values = (this.data() ?? []).map((i) => toFinite(i.value));\n return niceScale(Math.min(0, ...values), Math.max(0, ...values), 4);\n });\n\n /** Maps a data value to a pixel position along the value axis. */\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.data()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({\n pos: this.valueToPos(tick),\n label: fmt(tick),\n }));\n });\n\n protected readonly bars = computed(() => {\n const d = this.data();\n if (!d || d.length === 0) return [];\n\n const isVertical = this.isVertical();\n const p = this.plot();\n const gap = this.barGap();\n const singleColor = this.color();\n const fmt = this.formatValue();\n const zero = this.valueToPos(0);\n const h = this.height();\n\n if (isVertical) {\n const totalBarWidth = p.w / d.length;\n const barWidth = totalBarWidth * (1 - gap);\n const barOffset = (totalBarWidth - barWidth) / 2;\n\n return d.map((item, i) => {\n const value = toFinite(item.value);\n const end = this.valueToPos(value);\n const y = Math.min(zero, end);\n const barH = Math.abs(end - zero);\n return {\n x: p.x + i * totalBarWidth + barOffset,\n y,\n width: barWidth,\n height: barH,\n color: singleColor || item.color || chartColor(i),\n label: item.label || '',\n value,\n valueText: fmt(value),\n labelX: p.x + i * totalBarWidth + totalBarWidth / 2,\n labelY: h - this.PADDING_BOTTOM + 16,\n valueX: p.x + i * totalBarWidth + totalBarWidth / 2,\n // Above a positive bar, below a negative one.\n valueY: value >= 0 ? y - 6 : y + barH + 12,\n valueAnchor: 'middle',\n };\n });\n } else {\n const totalBarHeight = p.h / d.length;\n const barHeight = totalBarHeight * (1 - gap);\n const barOffset = (totalBarHeight - barHeight) / 2;\n\n return d.map((item, i) => {\n const value = toFinite(item.value);\n const end = this.valueToPos(value);\n const x = Math.min(zero, end);\n const barW = Math.abs(end - zero);\n return {\n x,\n y: p.y + i * totalBarHeight + barOffset,\n width: barW,\n height: barHeight,\n color: singleColor || item.color || chartColor(i),\n label: item.label || '',\n value,\n valueText: fmt(value),\n labelX: p.x - 6,\n labelY: p.y + i * totalBarHeight + totalBarHeight / 2,\n // Right of a positive bar, left of a negative one.\n valueX: value >= 0 ? x + barW + 6 : x - 6,\n valueY: p.y + i * totalBarHeight + totalBarHeight / 2,\n valueAnchor: value >= 0 ? 'start' : 'end',\n };\n });\n }\n });\n\n /**\n * Value axis along the plot edge plus the zero baseline, which sits inside\n * the plot when the data goes negative.\n */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isVertical = this.isVertical();\n const zero = this.data()?.length ? this.valueToPos(0) : (isVertical ? p.y + p.h : p.x);\n return {\n x1: isVertical ? p.x : zero,\n y1: p.y,\n x2: isVertical ? p.x : zero,\n y2: p.y + p.h,\n bx1: p.x,\n by1: isVertical ? zero : p.y + p.h,\n bx2: p.x + p.w,\n by2: isVertical ? zero : p.y + p.h,\n };\n });\n\n /** Bottom edge of the plot — where horizontal-orientation tick labels sit. */\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const d = this.data() ?? [];\n if (!d.length) return 'Bar chart: no data';\n const fmt = this.formatValue();\n return `Bar chart: ${d\n .map((item, i) => `${item.label || `Bar ${i + 1}`} ${fmt(toFinite(item.value))}`)\n .join(', ')}`;\n });\n}\n","<svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-bar-chart\"\n>\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid lines -->\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"line.pos\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"line.pos\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"axisLine().x1 - 6\"\n [attr.y]=\"line.pos\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n } @else {\n <line\n [attr.x1]=\"line.pos\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"line.pos\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"line.pos\"\n [attr.y]=\"plotBottom() + 12\"\n text-anchor=\"middle\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n }\n }\n }\n\n <!-- Axes -->\n <line\n [attr.x1]=\"axisLine().x1\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"axisLine().x2\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__axis\"\n />\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"axisLine().by1\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"axisLine().by2\"\n class=\"lc-bar-chart__axis\"\n />\n\n <!-- Bars -->\n @for (bar of bars(); track $index) {\n <rect\n [attr.x]=\"bar.x\"\n [attr.y]=\"bar.y\"\n [attr.width]=\"bar.width\"\n [attr.height]=\"bar.height\"\n [attr.fill]=\"bar.color\"\n rx=\"2\"\n class=\"lc-bar-chart__bar\"\n />\n\n @if (showValues()) {\n <text\n [attr.x]=\"bar.valueX\"\n [attr.y]=\"bar.valueY\"\n [attr.text-anchor]=\"bar.valueAnchor\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__value\"\n >\n {{ bar.valueText }}\n </text>\n }\n\n @if (showLabels()) {\n <text\n [attr.x]=\"bar.labelX\"\n [attr.y]=\"bar.labelY\"\n [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__label\"\n >\n {{ bar.label }}\n </text>\n }\n }\n</svg>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/bar-chart/bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/bar-chart/bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n inject,\n input,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface BarChartItem {\n /** Bar value. Negative values draw downward (left) from the zero baseline. */\n value: number;\n label?: string;\n color?: string;\n /**\n * The text shown as this bar's value and read in the generated aria label,\n * instead of `formatValue(value, item)`, for example \"313.000,00 · 54,8 %\".\n * The kit shows it as given and never formats or computes it. An empty\n * string shows no value for this bar.\n */\n valueText?: string;\n}\n\nexport type BarChartOrientation = 'vertical' | 'horizontal';\n\n/**\n * How the category labels are set: `'axis'` (one line of SVG text on the\n * category axis) or `'wrap'` (HTML text that wraps and follows the text size).\n */\nexport type BarChartLabelLayout = 'axis' | 'wrap';\n\n/**\n * Signature of `lc-bar-chart`'s `formatValue`. A bar's value label passes the\n * bar's item; an axis tick passes the tick only (`item` is `undefined`).\n * One-argument formatters keep working.\n */\nexport type BarChartValueFormatter = (value: number, item?: BarChartItem) => string;\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n@Component({\n selector: 'lc-bar-chart',\n standalone: true,\n templateUrl: './bar-chart.component.html',\n styleUrls: ['./bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Bar chart component for comparing categorical data.\n *\n * Features:\n * - Vertical and horizontal orientation\n * - Per-bar or uniform color support\n * - Negative values drawn from a zero baseline\n * - Optional value labels on bars\n * - Configurable grid and axis labels on a nice tick scale\n * - Adjustable bar gap spacing\n * - Responsive SVG rendering\n *\n * @example\n * ```html\n * <lc-bar-chart [data]=\"items\" orientation=\"vertical\" [showValues]=\"true\" />\n * ```\n */\nexport class BarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n }\n\n /** Data items. */\n data = input.required<BarChartItem[]>();\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n * @default undefined\n */\n width = input<number | undefined>(undefined);\n\n /**\n * Chart height in pixels. A horizontal `'wrap'` chart takes the height of\n * its rows; a vertical `'wrap'` chart adds its label row below this height.\n * @default 200\n */\n height = input<number>(200);\n\n /**\n * Bar orientation.\n * @default 'vertical'\n */\n orientation = input<BarChartOrientation>('vertical');\n\n /**\n * Show value labels on bars. In horizontal `'wrap'` rows the value sits at\n * the end of the label line.\n * @default true\n */\n showValues = input<boolean>(true);\n\n /**\n * Show axis labels.\n * @default true\n */\n showLabels = input<boolean>(true);\n\n /**\n * How the category labels are set. `'axis'`: one line of SVG text on the\n * category axis, in a fixed 40 px band left of a horizontal bar or centred\n * under a vertical column. Long labels can run out of the chart. `'wrap'`:\n * HTML text that wraps to the room it needs and follows the text size. A\n * horizontal chart becomes a list of rows: the label and the value on one\n * line (the value moves under the label when both don't fit), and below\n * them the bar across the full width. The longest bar fills the width, and\n * `height`, `barGap` and `showGrid` don't apply. A vertical chart keeps its\n * plot and wraps each label within its column, under the chart.\n * @default 'axis'\n */\n labelLayout = input<BarChartLabelLayout>('axis');\n\n /**\n * Show grid lines. Not used by horizontal `'wrap'` rows.\n * @default true\n */\n showGrid = input<boolean>(true);\n\n /**\n * Gap between bars as ratio (0-1). Not used by horizontal `'wrap'` rows.\n * @default 0.3\n */\n barGap = input<number>(0.3);\n\n /**\n * Use a single color for all bars.\n * @default ''\n */\n color = input<string>('');\n\n /**\n * Formats the value labels and the axis ticks. For a bar it gets the bar's\n * item as the second argument; for an axis tick it gets the tick only\n * (`item` is `undefined`). An item's `valueText` takes precedence.\n * @default float-safe `String(value)`\n */\n formatValue = input<BarChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary that lists\n * every bar's label and value, so assistive technology hears the data and\n * not just \"Bar chart\".\n * @default ''\n */\n ariaLabel = input<string>('');\n\n // Layout constants\n private readonly PADDING_LEFT = 40;\n private readonly PADDING_RIGHT = 10;\n private readonly PADDING_TOP = 10;\n private readonly PADDING_BOTTOM = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(\n () => `0 0 ${this.effectiveWidth()} ${this.height()}`\n );\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n /** Horizontal `'wrap'`: HTML rows instead of the SVG plot. */\n protected readonly wrapRows = computed(\n () => this.labelLayout() === 'wrap' && !this.isVertical()\n );\n\n /** Vertical `'wrap'`: the SVG plot, with the category labels as an HTML row under it. */\n protected readonly wrapColumns = computed(\n () => this.labelLayout() === 'wrap' && this.isVertical()\n );\n\n /**\n * Room under the plot. `'axis'` keeps 30 for the SVG labels; vertical\n * `'wrap'` labels sit under the SVG, so only 2 are left, or 18 when a\n * negative bar's value label hangs below it.\n */\n private readonly paddingBottom = computed(() => {\n if (!this.wrapColumns()) return this.PADDING_BOTTOM;\n const negative = (this.data() ?? []).some((i) => toFinite(i.value) < 0);\n return this.showValues() && negative ? 18 : 2;\n });\n\n private readonly plot = computed(() => {\n const w = this.effectiveWidth();\n const h = this.height();\n return {\n x: this.PADDING_LEFT,\n y: this.PADDING_TOP,\n w: w - this.PADDING_LEFT - this.PADDING_RIGHT,\n h: h - this.PADDING_TOP - this.paddingBottom(),\n };\n });\n\n /** A bar's text: `valueText`, else `formatValue(value, item)`. */\n private textOf(item: BarChartItem, value: number, fmt: BarChartValueFormatter): string {\n return item.valueText ?? fmt(value, item);\n }\n\n /**\n * One scale for grid *and* bars: bounds are nice multiples of the tick step,\n * always include zero, and extend below zero when the data does — so the top\n * tick is the value at the plot edge and negative bars hang off the baseline.\n */\n protected readonly scale = computed(() => {\n const values = (this.data() ?? []).map((i) => toFinite(i.value));\n return niceScale(Math.min(0, ...values), Math.max(0, ...values), 4);\n });\n\n /** Maps a data value to a pixel position along the value axis. */\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.data()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({\n pos: this.valueToPos(tick),\n label: fmt(tick),\n }));\n });\n\n protected readonly bars = computed(() => {\n const d = this.data();\n if (!d || d.length === 0) return [];\n\n const isVertical = this.isVertical();\n const p = this.plot();\n const gap = this.barGap();\n const singleColor = this.color();\n const fmt = this.formatValue();\n const zero = this.valueToPos(0);\n const h = this.height();\n\n if (isVertical) {\n const totalBarWidth = p.w / d.length;\n const barWidth = totalBarWidth * (1 - gap);\n const barOffset = (totalBarWidth - barWidth) / 2;\n\n return d.map((item, i) => {\n const value = toFinite(item.value);\n const end = this.valueToPos(value);\n const y = Math.min(zero, end);\n const barH = Math.abs(end - zero);\n return {\n x: p.x + i * totalBarWidth + barOffset,\n y,\n width: barWidth,\n height: barH,\n color: singleColor || item.color || chartColor(i),\n label: item.label || '',\n value,\n valueText: this.textOf(item, value, fmt),\n labelX: p.x + i * totalBarWidth + totalBarWidth / 2,\n labelY: h - this.PADDING_BOTTOM + 16,\n valueX: p.x + i * totalBarWidth + totalBarWidth / 2,\n // Above a positive bar, below a negative one.\n valueY: value >= 0 ? y - 6 : y + barH + 12,\n valueAnchor: 'middle',\n };\n });\n } else {\n const totalBarHeight = p.h / d.length;\n const barHeight = totalBarHeight * (1 - gap);\n const barOffset = (totalBarHeight - barHeight) / 2;\n\n return d.map((item, i) => {\n const value = toFinite(item.value);\n const end = this.valueToPos(value);\n const x = Math.min(zero, end);\n const barW = Math.abs(end - zero);\n return {\n x,\n y: p.y + i * totalBarHeight + barOffset,\n width: barW,\n height: barHeight,\n color: singleColor || item.color || chartColor(i),\n label: item.label || '',\n value,\n valueText: this.textOf(item, value, fmt),\n labelX: p.x - 6,\n labelY: p.y + i * totalBarHeight + totalBarHeight / 2,\n // Right of a positive bar, left of a negative one.\n valueX: value >= 0 ? x + barW + 6 : x - 6,\n valueY: p.y + i * totalBarHeight + totalBarHeight / 2,\n valueAnchor: value >= 0 ? 'start' : 'end',\n };\n });\n }\n });\n\n /**\n * Value axis along the plot edge plus the zero baseline, which sits inside\n * the plot when the data goes negative.\n */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isVertical = this.isVertical();\n const zero = this.data()?.length ? this.valueToPos(0) : (isVertical ? p.y + p.h : p.x);\n return {\n x1: isVertical ? p.x : zero,\n y1: p.y,\n x2: isVertical ? p.x : zero,\n y2: p.y + p.h,\n bx1: p.x,\n by1: isVertical ? zero : p.y + p.h,\n bx2: p.x + p.w,\n by2: isVertical ? zero : p.y + p.h,\n };\n });\n\n /** Bottom edge of the plot — where horizontal-orientation tick labels sit. */\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n /**\n * Bounds of the horizontal `'wrap'` rows: zero is always inside, and the\n * longest bar fills the track.\n */\n private readonly rowBounds = computed(() => {\n const values = (this.data() ?? []).map((i) => toFinite(i.value));\n const lo = Math.min(0, ...values);\n const hi = Math.max(0, ...values);\n return { values, lo, range: hi - lo || 1 };\n });\n\n /**\n * Horizontal `'wrap'` rows. Bar `x` and `width` are percentages of the\n * track, so the server HTML is final before the container is measured.\n */\n protected readonly rows = computed(() => {\n const d = this.data() ?? [];\n const { values, lo, range } = this.rowBounds();\n const singleColor = this.color();\n const fmt = this.formatValue();\n return d.map((item, i) => {\n const value = values[i];\n return {\n label: item.label || '',\n valueText: this.textOf(item, value, fmt),\n color: singleColor || item.color || chartColor(i),\n x: `${((Math.min(value, 0) - lo) / range) * 100}%`,\n width: `${(Math.abs(value) / range) * 100}%`,\n };\n });\n });\n\n /** Position of the zero line in the `'wrap'` rows; `null` without negative values. */\n protected readonly rowZero = computed(() => {\n const { lo, range } = this.rowBounds();\n return lo < 0 ? `${(-lo / range) * 100}%` : null;\n });\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const d = this.data() ?? [];\n if (!d.length) return 'Bar chart: no data';\n const fmt = this.formatValue();\n return `Bar chart: ${d\n .map((item, i) => {\n const label = item.label || `Bar ${i + 1}`;\n const text = this.textOf(item, toFinite(item.value), fmt);\n return text === '' ? label : `${label} ${text}`;\n })\n .join(', ')}`;\n });\n}\n","@if (wrapRows()) {\n <!--\n Horizontal 'wrap' (3.21, request 028): HTML rows, so labels wrap and follow\n the text size. Bar x/width are percentages: final in the server HTML.\n No inline style (CSP). The children of role=\"img\" are presentational.\n -->\n <div\n class=\"lc-bar-chart lc-bar-chart--rows\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n >\n @for (row of rows(); track $index) {\n <div class=\"lc-bar-chart__row\">\n @if ((showLabels() && row.label) || (showValues() && row.valueText)) {\n <div class=\"lc-bar-chart__row-head\">\n @if (showLabels() && row.label) {\n <span class=\"lc-bar-chart__row-label\">{{ row.label }}</span>\n }\n @if (showValues() && row.valueText) {\n <span class=\"lc-bar-chart__row-value\">{{ row.valueText }}</span>\n }\n </div>\n }\n <svg\n width=\"100%\"\n aria-hidden=\"true\"\n focusable=\"false\"\n class=\"lc-bar-chart__row-track\"\n >\n @if (rowZero(); as zero) {\n <line\n [attr.x1]=\"zero\"\n y1=\"-25%\"\n [attr.x2]=\"zero\"\n y2=\"125%\"\n class=\"lc-bar-chart__zero-line\"\n />\n }\n <rect\n [attr.x]=\"row.x\"\n y=\"0\"\n [attr.width]=\"row.width\"\n height=\"100%\"\n [attr.fill]=\"row.color\"\n rx=\"2\"\n class=\"lc-bar-chart__row-bar\"\n />\n </svg>\n </div>\n }\n </div>\n} @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-bar-chart\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n <!-- Grid lines -->\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"line.pos\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"line.pos\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"axisLine().x1 - 6\"\n [attr.y]=\"line.pos\"\n text-anchor=\"end\"\n dominant-baseline=\"central\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n } @else {\n <line\n [attr.x1]=\"line.pos\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"line.pos\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__grid-line\"\n />\n <text\n [attr.x]=\"line.pos\"\n [attr.y]=\"plotBottom() + 12\"\n text-anchor=\"middle\"\n class=\"lc-bar-chart__grid-label\"\n >\n {{ line.label }}\n </text>\n }\n }\n }\n\n <!-- Axes -->\n <line\n [attr.x1]=\"axisLine().x1\"\n [attr.y1]=\"axisLine().y1\"\n [attr.x2]=\"axisLine().x2\"\n [attr.y2]=\"axisLine().y2\"\n class=\"lc-bar-chart__axis\"\n />\n <line\n [attr.x1]=\"axisLine().bx1\"\n [attr.y1]=\"axisLine().by1\"\n [attr.x2]=\"axisLine().bx2\"\n [attr.y2]=\"axisLine().by2\"\n class=\"lc-bar-chart__axis\"\n />\n\n <!-- Bars -->\n @for (bar of bars(); track $index) {\n <rect\n [attr.x]=\"bar.x\"\n [attr.y]=\"bar.y\"\n [attr.width]=\"bar.width\"\n [attr.height]=\"bar.height\"\n [attr.fill]=\"bar.color\"\n rx=\"2\"\n class=\"lc-bar-chart__bar\"\n />\n\n @if (showValues()) {\n <text\n [attr.x]=\"bar.valueX\"\n [attr.y]=\"bar.valueY\"\n [attr.text-anchor]=\"bar.valueAnchor\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__value\"\n >\n {{ bar.valueText }}\n </text>\n }\n\n <!-- One block (not nested), so the SVG's comment anchors stay as before 3.21. -->\n @if (showLabels() && !wrapColumns()) {\n <text\n [attr.x]=\"bar.labelX\"\n [attr.y]=\"bar.labelY\"\n [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\"\n [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\"\n class=\"lc-bar-chart__label\"\n >\n {{ bar.label }}\n </text>\n }\n }\n </svg>\n\n <!--\n Vertical 'wrap' (3.21, request 028): the category labels as HTML under the\n plot, one cell per column, wrapping within it. Hidden from assistive\n technology: the names are in the SVG's accessible name.\n -->\n @if (wrapColumns() && showLabels()) {\n <div class=\"lc-bar-chart__category-labels\" aria-hidden=\"true\">\n @for (bar of bars(); track $index) {\n <span class=\"lc-bar-chart__category-label\">{{ bar.label }}</span>\n }\n </div>\n }\n}\n"],"mappings":"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"}
|
|
@@ -60,9 +60,17 @@ function numberFormat(locale, options) {
|
|
|
60
60
|
return new Intl.NumberFormat("en-US", options);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
function withoutCurrency(parts) {
|
|
64
|
+
const at = parts.findIndex((p) => p.type === "currency");
|
|
65
|
+
if (at < 0) return parts;
|
|
66
|
+
return parts.map((p, i) => p.type === "literal" && Math.abs(i - at) === 1 ? {
|
|
67
|
+
...p,
|
|
68
|
+
value: p.value.replace(/\s/g, "")
|
|
69
|
+
} : p).filter((p, i) => i !== at && p.value !== "");
|
|
70
|
+
}
|
|
63
71
|
function formatMoney(amountMinor, currency, options = {}) {
|
|
64
72
|
if (!Number.isSafeInteger(amountMinor) || !/^[A-Za-z]{3}$/.test(currency ?? "")) return null;
|
|
65
|
-
const { locale = "en-US", signDisplay = "auto", minusLabel = "minus", plusLabel = "plus" } = options;
|
|
73
|
+
const { locale = "en-US", signDisplay = "auto", minusLabel = "minus", plusLabel = "plus", showCurrency = true } = options;
|
|
66
74
|
const digits = moneyMinorUnits(currency);
|
|
67
75
|
const value = decimalString(amountMinor, digits);
|
|
68
76
|
const base = {
|
|
@@ -83,6 +91,7 @@ function formatMoney(amountMinor, currency, options = {}) {
|
|
|
83
91
|
} catch {
|
|
84
92
|
return null;
|
|
85
93
|
}
|
|
94
|
+
if (showCurrency === false) visible = withoutCurrency(visible);
|
|
86
95
|
return {
|
|
87
96
|
text: visible.map((p) => p.type === "minusSign" ? p.value.replace(/-/g, "−") : p.value).join(""),
|
|
88
97
|
spoken: named.map((p) => p.type === "minusSign" ? `${minusLabel} ` : p.type === "plusSign" ? `${plusLabel} ` : p.value).join("").replace(/\s+/g, " ").trim()
|
|
@@ -96,12 +105,14 @@ var MoneyComponent = class MoneyComponent {
|
|
|
96
105
|
signDisplay = input("auto", ...ngDevMode ? [{ debugName: "signDisplay" }] : /* istanbul ignore next */ []);
|
|
97
106
|
minusLabel = input("minus", ...ngDevMode ? [{ debugName: "minusLabel" }] : /* istanbul ignore next */ []);
|
|
98
107
|
plusLabel = input("plus", ...ngDevMode ? [{ debugName: "plusLabel" }] : /* istanbul ignore next */ []);
|
|
108
|
+
showCurrency = input(true, ...ngDevMode ? [{ debugName: "showCurrency" }] : /* istanbul ignore next */ []);
|
|
99
109
|
effectiveLocale = computed(() => this.locale() || this.appLocale || "en-US", ...ngDevMode ? [{ debugName: "effectiveLocale" }] : /* istanbul ignore next */ []);
|
|
100
110
|
formatted = computed(() => formatMoney(this.amountMinor(), this.currency(), {
|
|
101
111
|
locale: this.effectiveLocale(),
|
|
102
112
|
signDisplay: this.signDisplay(),
|
|
103
113
|
minusLabel: this.minusLabel(),
|
|
104
|
-
plusLabel: this.plusLabel()
|
|
114
|
+
plusLabel: this.plusLabel(),
|
|
115
|
+
showCurrency: this.showCurrency()
|
|
105
116
|
}), ...ngDevMode ? [{ debugName: "formatted" }] : /* istanbul ignore next */ []);
|
|
106
117
|
dir = computed(() => moneyDirection(this.effectiveLocale()), ...ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []);
|
|
107
118
|
constructor() {
|
|
@@ -167,6 +178,13 @@ var MoneyComponent = class MoneyComponent {
|
|
|
167
178
|
isSignal: true,
|
|
168
179
|
isRequired: false,
|
|
169
180
|
transformFunction: null
|
|
181
|
+
},
|
|
182
|
+
showCurrency: {
|
|
183
|
+
classPropertyName: "showCurrency",
|
|
184
|
+
publicName: "showCurrency",
|
|
185
|
+
isSignal: true,
|
|
186
|
+
isRequired: false,
|
|
187
|
+
transformFunction: null
|
|
170
188
|
}
|
|
171
189
|
},
|
|
172
190
|
host: { classAttribute: "lc-money" },
|
|
@@ -242,6 +260,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
242
260
|
alias: "plusLabel",
|
|
243
261
|
required: false
|
|
244
262
|
}]
|
|
263
|
+
}],
|
|
264
|
+
showCurrency: [{
|
|
265
|
+
type: i0.Input,
|
|
266
|
+
args: [{
|
|
267
|
+
isSignal: true,
|
|
268
|
+
alias: "showCurrency",
|
|
269
|
+
required: false
|
|
270
|
+
}]
|
|
245
271
|
}]
|
|
246
272
|
}
|
|
247
273
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-money.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/money/money-format.ts","../../../../libs/angular-ui-kit/src/lib/money/money.component.ts"],"sourcesContent":["/**\n * How the sign of an amount shows, as `Intl.NumberFormat`'s `signDisplay`:\n * - `auto`: negative amounts only (−42,17 €)\n * - `always`: plus and minus, also on zero (+0,00 €)\n * - `exceptZero`: plus and minus, no sign on zero\n * - `never`: no sign\n */\nexport type MoneySignDisplay = 'auto' | 'always' | 'exceptZero' | 'never';\n\n/** Options of {@link formatMoney}. */\nexport interface MoneyFormatOptions {\n /** BCP 47 locale. An invalid one falls back to `en-US`. @default 'en-US' */\n locale?: string;\n /** As `Intl.NumberFormat`'s `signDisplay`. @default 'auto' */\n signDisplay?: MoneySignDisplay;\n /** Spoken word for the minus sign. @default 'minus' */\n minusLabel?: string;\n /** Spoken word for the plus sign. @default 'plus' */\n plusLabel?: string;\n}\n\n/** A formatted amount: the visible text and the words a screen reader speaks. */\nexport interface MoneyText {\n /** Visible text, e.g. „−42,17 €“ (U+2212 minus, U+00A0 from `Intl`). */\n text: string;\n /** Spoken text, e.g. „minus 42,17 Euro“. */\n spoken: string;\n}\n\n/**\n * Minor-unit exponents per ISO 4217 that are not 2. Not `Intl`'s digits:\n * CLDR gives 0 for HUF, IDR, RSD and IQD, which ISO 4217 does not.\n */\nconst MINOR_UNITS: Readonly<Record<string, number>> = {\n BIF: 0, CLP: 0, DJF: 0, GNF: 0, ISK: 0, JPY: 0, KMF: 0, KRW: 0, PYG: 0,\n RWF: 0, UGX: 0, UYI: 0, VND: 0, VUV: 0, XAF: 0, XOF: 0, XPF: 0,\n BHD: 3, IQD: 3, JOD: 3, KWD: 3, LYD: 3, OMR: 3, TND: 3,\n CLF: 4, UYW: 4,\n};\n\n/** The ISO 4217 minor-unit exponent of `currency` (2 unless listed). */\nexport function moneyMinorUnits(currency: string): number {\n return MINOR_UNITS[currency.toUpperCase()] ?? 2;\n}\n\n/** Primary language subtags written right to left (a fixed list: the same on server and browser). */\nconst RTL_LANGUAGES = new Set(['ar', 'ckb', 'dv', 'fa', 'he', 'iw', 'ks', 'ps', 'sd', 'ug', 'ur', 'yi']);\n\n/** Text direction of an amount formatted for `locale`. */\nexport function moneyDirection(locale: string): 'ltr' | 'rtl' {\n return RTL_LANGUAGES.has(locale.split(/[-_]/)[0].toLowerCase()) ? 'rtl' : 'ltr';\n}\n\n/** The exact decimal string of an integer amount in minor units: 4217, 2 → \"42.17\". */\nfunction decimalString(amountMinor: number, digits: number): string {\n const abs = String(Math.abs(amountMinor)).padStart(digits + 1, '0');\n const value = digits === 0 ? abs : `${abs.slice(0, -digits)}.${abs.slice(-digits)}`;\n return amountMinor < 0 ? `-${value}` : value; // −0 has no sign\n}\n\n/** `Intl.NumberFormat` for `locale`, or for `en-US` when the locale is invalid. */\nfunction numberFormat(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n try {\n return new Intl.NumberFormat(locale, options);\n } catch {\n return new Intl.NumberFormat('en-US', options);\n }\n}\n\n/**\n * Formats an amount of money given in integer minor units (cents, yen, fils)\n * exactly, never through a float: the decimal string goes into `Intl`.\n * Returns `null` for an amount that is not a safe integer or a currency that\n * is not a three-letter code. This is the formatter of `lc-money`, for table\n * `formatter`s and texts.\n *\n * @example\n * ```ts\n * formatMoney(-4217, 'EUR', { locale: 'de-DE' });\n * // { text: '−42,17 €', spoken: 'minus 42,17 Euro' }\n * ```\n */\nexport function formatMoney(amountMinor: number, currency: string, options: MoneyFormatOptions = {}): MoneyText | null {\n if (!Number.isSafeInteger(amountMinor) || !/^[A-Za-z]{3}$/.test(currency ?? '')) return null;\n const { locale = 'en-US', signDisplay = 'auto', minusLabel = 'minus', plusLabel = 'plus' } = options;\n const digits = moneyMinorUnits(currency);\n const value = decimalString(amountMinor, digits) as unknown as number; // exact decimal string (Intl.NumberFormat v3)\n const base: Intl.NumberFormatOptions = {\n style: 'currency',\n currency: currency.toUpperCase(),\n signDisplay,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n };\n let visible: Intl.NumberFormatPart[];\n let named: Intl.NumberFormatPart[];\n try {\n visible = numberFormat(locale || 'en-US', base).formatToParts(value);\n named = numberFormat(locale || 'en-US', { ...base, currencyDisplay: 'name' }).formatToParts(value);\n } catch {\n return null;\n }\n const text = visible.map((p) => (p.type === 'minusSign' ? p.value.replace(/-/g, '−') : p.value)).join('');\n const spoken = named\n .map((p) => (p.type === 'minusSign' ? `${minusLabel} ` : p.type === 'plusSign' ? `${plusLabel} ` : p.value))\n .join('')\n .replace(/\\s+/g, ' ')\n .trim();\n return { text, spoken };\n}\n","import { ChangeDetectionStrategy, Component, LOCALE_ID, computed, effect, inject, input, isDevMode } from '@angular/core';\nimport { formatMoney, moneyDirection, type MoneySignDisplay } from './money-format';\n\n/**\n * Displays an amount of money (3.16, BA-4): locale, currency, sign, tabular\n * figures and a spoken text. Display only, no input parsing or conversion.\n *\n * - `amountMinor` is an integer in the currency's minor units (cents, yen,\n * fils), formatted exactly through a decimal string, never a float. Minor\n * units follow ISO 4217: 2, except 0 (BIF CLP DJF GNF ISK JPY KMF KRW PYG\n * RWF UGX UYI VND VUV XAF XOF XPF), 3 (BHD IQD JOD KWD LYD OMR TND) and 4\n * (CLF UYW).\n * - The visible amount („−42,17 €“, U+2212 minus, a no-break space from\n * `Intl`) is `aria-hidden`; a visually hidden span holds the words\n * („minus 42,17 Euro“), so a screen reader reads the amount once.\n * - Tabular figures, no wrapping inside the amount; the amount is a `dir`\n * isolate (`ltr`, or `rtl` for an RTL locale), safe inside RTL text.\n * - Font and colour come from the context; colour never carries the sign.\n * - `locale` empty: the app's `LOCALE_ID` (Angular's default `en-US`), the\n * same on the server and in the browser.\n * - For table `formatter`s and texts use {@link formatMoney}.\n *\n * Separate a `lc-money` from adjacent text with a space in your template, so\n * the spoken text does not run into the words around it.\n *\n * @example\n * ```html\n * <lc-money [amountMinor]=\"-4217\" currency=\"EUR\" locale=\"de-DE\" />\n * ```\n */\n@Component({\n selector: 'lc-money',\n standalone: true,\n // No whitespace between the spans: the visible amount and the words are separate texts.\n template:\n '@if (formatted(); as m) {<span class=\"lc-money__amount\" aria-hidden=\"true\" [attr.dir]=\"dir()\">{{ m.text }}</span><span class=\"lc-money__spoken\">{{ m.spoken }}</span>}',\n styleUrl: './money.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'lc-money' },\n})\nexport class MoneyComponent {\n private readonly appLocale = inject(LOCALE_ID);\n\n /**\n * Amount in minor units of `currency` (cents, yen, fils): a safe integer\n * (±9,007,199,254,740,991), formatted exactly. Anything else renders\n * nothing (a dev warning).\n */\n readonly amountMinor = input.required<number>();\n\n /**\n * ISO 4217 currency code (`EUR`, `USD`, `JPY`, `KWD`). It sets the minor\n * units and the symbol. Not a three-letter code: renders nothing (a dev\n * warning).\n */\n readonly currency = input.required<string>();\n\n /**\n * BCP 47 locale of the amount (`de-DE`, `en-GB`). Empty: the app's\n * `LOCALE_ID` (Angular's default `en-US`). An invalid locale falls back to\n * `en-US`.\n * @default ''\n */\n readonly locale = input<string>('');\n\n /**\n * Which signs show, as `Intl.NumberFormat`: `auto` (negative only),\n * `always`, `exceptZero`, `never`. The spoken text says the sign whenever\n * it shows.\n * @default 'auto'\n */\n readonly signDisplay = input<MoneySignDisplay>('auto');\n\n /**\n * Spoken word for the minus sign („minus 42,17 Euro“; the same in German).\n * @default 'minus'\n */\n readonly minusLabel = input<string>('minus');\n\n /**\n * Spoken word for the plus sign („plus 42,17 Euro“; the same in German).\n * @default 'plus'\n */\n readonly plusLabel = input<string>('plus');\n\n /** The locale in use: the `locale` input or the app's `LOCALE_ID`. */\n protected readonly effectiveLocale = computed(() => this.locale() || this.appLocale || 'en-US');\n\n protected readonly formatted = computed(() =>\n formatMoney(this.amountMinor(), this.currency(), {\n locale: this.effectiveLocale(),\n signDisplay: this.signDisplay(),\n minusLabel: this.minusLabel(),\n plusLabel: this.plusLabel(),\n }),\n );\n\n protected readonly dir = computed(() => moneyDirection(this.effectiveLocale()));\n\n constructor() {\n effect(() => {\n if (!isDevMode()) return;\n if (!Number.isSafeInteger(this.amountMinor())) {\n console.warn('[lc-money] amountMinor must be a safe integer');\n } else if (!this.formatted()) {\n console.warn(`[lc-money] unknown currency \"${this.currency()}\"`);\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-money.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/money/money-format.ts","../../../../libs/angular-ui-kit/src/lib/money/money.component.ts"],"sourcesContent":["/**\n * How the sign of an amount shows, as `Intl.NumberFormat`'s `signDisplay`:\n * - `auto`: negative amounts only (−42,17 €)\n * - `always`: plus and minus, also on zero (+0,00 €)\n * - `exceptZero`: plus and minus, no sign on zero\n * - `never`: no sign\n */\nexport type MoneySignDisplay = 'auto' | 'always' | 'exceptZero' | 'never';\n\n/** Options of {@link formatMoney}. */\nexport interface MoneyFormatOptions {\n /** BCP 47 locale. An invalid one falls back to `en-US`. @default 'en-US' */\n locale?: string;\n /** As `Intl.NumberFormat`'s `signDisplay`. @default 'auto' */\n signDisplay?: MoneySignDisplay;\n /** Spoken word for the minus sign. @default 'minus' */\n minusLabel?: string;\n /** Spoken word for the plus sign. @default 'plus' */\n plusLabel?: string;\n /**\n * Whether the visible `text` shows the currency symbol (or code). `false`\n * leaves out the symbol and the space that belongs to it („−42,17“), for a\n * column or title that already names the unit („Kosten in €“). Digits,\n * grouping, sign and minor units stay. The `spoken` text still names the\n * currency.\n * @default true\n */\n showCurrency?: boolean;\n}\n\n/** A formatted amount: the visible text and the words a screen reader speaks. */\nexport interface MoneyText {\n /**\n * Visible text, e.g. „−42,17 €“ (U+2212 minus, U+00A0 from `Intl`), or\n * „−42,17“ with `showCurrency: false`.\n */\n text: string;\n /** Spoken text, e.g. „minus 42,17 Euro“. */\n spoken: string;\n}\n\n/**\n * Minor-unit exponents per ISO 4217 that are not 2. Not `Intl`'s digits:\n * CLDR gives 0 for HUF, IDR, RSD and IQD, which ISO 4217 does not.\n */\nconst MINOR_UNITS: Readonly<Record<string, number>> = {\n BIF: 0, CLP: 0, DJF: 0, GNF: 0, ISK: 0, JPY: 0, KMF: 0, KRW: 0, PYG: 0,\n RWF: 0, UGX: 0, UYI: 0, VND: 0, VUV: 0, XAF: 0, XOF: 0, XPF: 0,\n BHD: 3, IQD: 3, JOD: 3, KWD: 3, LYD: 3, OMR: 3, TND: 3,\n CLF: 4, UYW: 4,\n};\n\n/** The ISO 4217 minor-unit exponent of `currency` (2 unless listed). */\nexport function moneyMinorUnits(currency: string): number {\n return MINOR_UNITS[currency.toUpperCase()] ?? 2;\n}\n\n/** Primary language subtags written right to left (a fixed list: the same on server and browser). */\nconst RTL_LANGUAGES = new Set(['ar', 'ckb', 'dv', 'fa', 'he', 'iw', 'ks', 'ps', 'sd', 'ug', 'ur', 'yi']);\n\n/** Text direction of an amount formatted for `locale`. */\nexport function moneyDirection(locale: string): 'ltr' | 'rtl' {\n return RTL_LANGUAGES.has(locale.split(/[-_]/)[0].toLowerCase()) ? 'rtl' : 'ltr';\n}\n\n/** The exact decimal string of an integer amount in minor units: 4217, 2 → \"42.17\". */\nfunction decimalString(amountMinor: number, digits: number): string {\n const abs = String(Math.abs(amountMinor)).padStart(digits + 1, '0');\n const value = digits === 0 ? abs : `${abs.slice(0, -digits)}.${abs.slice(-digits)}`;\n return amountMinor < 0 ? `-${value}` : value; // −0 has no sign\n}\n\n/** `Intl.NumberFormat` for `locale`, or for `en-US` when the locale is invalid. */\nfunction numberFormat(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n try {\n return new Intl.NumberFormat(locale, options);\n } catch {\n return new Intl.NumberFormat('en-US', options);\n }\n}\n\n/**\n * The parts without the currency: drops the `currency` part and the whitespace\n * of the literals right next to it (U+0020, U+00A0, U+202F, U+2009), keeping\n * bidi marks. Never trims the whole string: fr-FR groups with U+202F.\n */\nfunction withoutCurrency(parts: Intl.NumberFormatPart[]): Intl.NumberFormatPart[] {\n const at = parts.findIndex((p) => p.type === 'currency');\n if (at < 0) return parts;\n return parts\n .map((p, i) => (p.type === 'literal' && Math.abs(i - at) === 1 ? { ...p, value: p.value.replace(/\\s/g, '') } : p))\n .filter((p, i) => i !== at && p.value !== '');\n}\n\n/**\n * Formats an amount of money given in integer minor units (cents, yen, fils)\n * exactly, never through a float: the decimal string goes into `Intl`.\n * Returns `null` for an amount that is not a safe integer or a currency that\n * is not a three-letter code. This is the formatter of `lc-money`, for table\n * `formatter`s and texts. `showCurrency: false` leaves the symbol and its\n * space out of `text` (a column headed „Kosten in €“); `spoken` keeps it.\n *\n * @example\n * ```ts\n * formatMoney(-4217, 'EUR', { locale: 'de-DE' });\n * // { text: '−42,17 €', spoken: 'minus 42,17 Euro' }\n * formatMoney(-4217, 'EUR', { locale: 'de-DE', showCurrency: false });\n * // { text: '−42,17', spoken: 'minus 42,17 Euro' }\n * ```\n */\nexport function formatMoney(amountMinor: number, currency: string, options: MoneyFormatOptions = {}): MoneyText | null {\n if (!Number.isSafeInteger(amountMinor) || !/^[A-Za-z]{3}$/.test(currency ?? '')) return null;\n const { locale = 'en-US', signDisplay = 'auto', minusLabel = 'minus', plusLabel = 'plus', showCurrency = true } = options;\n const digits = moneyMinorUnits(currency);\n const value = decimalString(amountMinor, digits) as unknown as number; // exact decimal string (Intl.NumberFormat v3)\n const base: Intl.NumberFormatOptions = {\n style: 'currency',\n currency: currency.toUpperCase(),\n signDisplay,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n };\n let visible: Intl.NumberFormatPart[];\n let named: Intl.NumberFormatPart[];\n try {\n visible = numberFormat(locale || 'en-US', base).formatToParts(value);\n named = numberFormat(locale || 'en-US', { ...base, currencyDisplay: 'name' }).formatToParts(value);\n } catch {\n return null;\n }\n if (showCurrency === false) visible = withoutCurrency(visible);\n const text = visible.map((p) => (p.type === 'minusSign' ? p.value.replace(/-/g, '−') : p.value)).join('');\n const spoken = named\n .map((p) => (p.type === 'minusSign' ? `${minusLabel} ` : p.type === 'plusSign' ? `${plusLabel} ` : p.value))\n .join('')\n .replace(/\\s+/g, ' ')\n .trim();\n return { text, spoken };\n}\n","import { ChangeDetectionStrategy, Component, LOCALE_ID, computed, effect, inject, input, isDevMode } from '@angular/core';\nimport { formatMoney, moneyDirection, type MoneySignDisplay } from './money-format';\n\n/**\n * Displays an amount of money (3.16, BA-4): locale, currency, sign, tabular\n * figures and a spoken text. Display only, no input parsing or conversion.\n *\n * - `amountMinor` is an integer in the currency's minor units (cents, yen,\n * fils), formatted exactly through a decimal string, never a float. Minor\n * units follow ISO 4217: 2, except 0 (BIF CLP DJF GNF ISK JPY KMF KRW PYG\n * RWF UGX UYI VND VUV XAF XOF XPF), 3 (BHD IQD JOD KWD LYD OMR TND) and 4\n * (CLF UYW).\n * - The visible amount („−42,17 €“, U+2212 minus, a no-break space from\n * `Intl`) is `aria-hidden`; a visually hidden span holds the words\n * („minus 42,17 Euro“), so a screen reader reads the amount once.\n * - Tabular figures, no wrapping inside the amount; the amount is a `dir`\n * isolate (`ltr`, or `rtl` for an RTL locale), safe inside RTL text.\n * - Font and colour come from the context; colour never carries the sign.\n * - `showCurrency=false`: no symbol in the visible amount (a column headed\n * „Kosten in €“); the spoken text keeps the currency.\n * - `locale` empty: the app's `LOCALE_ID` (Angular's default `en-US`), the\n * same on the server and in the browser.\n * - For table `formatter`s and texts use {@link formatMoney}.\n *\n * Separate a `lc-money` from adjacent text with a space in your template, so\n * the spoken text does not run into the words around it.\n *\n * @example\n * ```html\n * <lc-money [amountMinor]=\"-4217\" currency=\"EUR\" locale=\"de-DE\" />\n * ```\n */\n@Component({\n selector: 'lc-money',\n standalone: true,\n // No whitespace between the spans: the visible amount and the words are separate texts.\n template:\n '@if (formatted(); as m) {<span class=\"lc-money__amount\" aria-hidden=\"true\" [attr.dir]=\"dir()\">{{ m.text }}</span><span class=\"lc-money__spoken\">{{ m.spoken }}</span>}',\n styleUrl: './money.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'lc-money' },\n})\nexport class MoneyComponent {\n private readonly appLocale = inject(LOCALE_ID);\n\n /**\n * Amount in minor units of `currency` (cents, yen, fils): a safe integer\n * (±9,007,199,254,740,991), formatted exactly. Anything else renders\n * nothing (a dev warning).\n */\n readonly amountMinor = input.required<number>();\n\n /**\n * ISO 4217 currency code (`EUR`, `USD`, `JPY`, `KWD`). It sets the minor\n * units and the symbol. Not a three-letter code: renders nothing (a dev\n * warning).\n */\n readonly currency = input.required<string>();\n\n /**\n * BCP 47 locale of the amount (`de-DE`, `en-GB`). Empty: the app's\n * `LOCALE_ID` (Angular's default `en-US`). An invalid locale falls back to\n * `en-US`.\n * @default ''\n */\n readonly locale = input<string>('');\n\n /**\n * Which signs show, as `Intl.NumberFormat`: `auto` (negative only),\n * `always`, `exceptZero`, `never`. The spoken text says the sign whenever\n * it shows.\n * @default 'auto'\n */\n readonly signDisplay = input<MoneySignDisplay>('auto');\n\n /**\n * Spoken word for the minus sign („minus 42,17 Euro“; the same in German).\n * @default 'minus'\n */\n readonly minusLabel = input<string>('minus');\n\n /**\n * Spoken word for the plus sign („plus 42,17 Euro“; the same in German).\n * @default 'plus'\n */\n readonly plusLabel = input<string>('plus');\n\n /**\n * Shows the currency symbol in the visible amount. `false` leaves out the\n * symbol and its space („−42,17“) for a table column or a title that\n * already names the unit („Kosten in €“). Screen readers still hear the\n * currency („minus 42,17 Euro“), and `currency` still sets the minor units.\n * Use it only where the unit is visible next to the amount.\n * @default true\n */\n readonly showCurrency = input<boolean>(true);\n\n /** The locale in use: the `locale` input or the app's `LOCALE_ID`. */\n protected readonly effectiveLocale = computed(() => this.locale() || this.appLocale || 'en-US');\n\n protected readonly formatted = computed(() =>\n formatMoney(this.amountMinor(), this.currency(), {\n locale: this.effectiveLocale(),\n signDisplay: this.signDisplay(),\n minusLabel: this.minusLabel(),\n plusLabel: this.plusLabel(),\n showCurrency: this.showCurrency(),\n }),\n );\n\n protected readonly dir = computed(() => moneyDirection(this.effectiveLocale()));\n\n constructor() {\n effect(() => {\n if (!isDevMode()) return;\n if (!Number.isSafeInteger(this.amountMinor())) {\n console.warn('[lc-money] amountMinor must be a safe integer');\n } else if (!this.formatted()) {\n console.warn(`[lc-money] unknown currency \"${this.currency()}\"`);\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -180,7 +180,7 @@ var ToastComponent = class ToastComponent {
|
|
|
180
180
|
} },
|
|
181
181
|
ngImport: i0,
|
|
182
182
|
template: "<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n",
|
|
183
|
-
styles: ["lc-toast:not(lc-toast-outlet lc-toast){display:block;width:100%;max-width:400px;margin-bottom:var(--spacing-2)}.lc-toast{border-radius:var(--border-radius-md);box-shadow:var(--elevation-3);border-width:1px;border-style:solid;animation:slideIn .15s ease-out;pointer-events:auto}.lc-toast__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-toast__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-toast__message{flex:1;min-width:0;font-size:var(--font-size-sm);line-height:var(--line-height-normal);padding-top:2px;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-toast__action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:var(--size-min-touch-width, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);margin-block:min(0px,(1lh + 2 * var(--spacing-1) - var(--size-min-touch-height, 2.75rem)) / 2);background:transparent;border:none;cursor:pointer;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);transition:background-color .15s ease-in-out}.lc-toast__action:hover{background-color:#0000000d}.lc-toast__action:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast__close{flex-shrink:0;width:var(--size-min-touch-width, 2.75rem);height:var(--size-min-touch-height, 2.75rem);background:transparent;color:var(--color-text-secondary);border:none;cursor:pointer;padding:0;margin:calc((20px - var(--size-min-touch-height, 2.75rem)) / 2);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-toast__close:hover{background-color:#0000000d}.lc-toast__close:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast--success{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-success);color:var(--color-text-primary)}.lc-toast--success .lc-toast__icon{color:var(--color-success)}.lc-toast--success .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--success .lc-toast__action:hover,.lc-toast--success .lc-toast__close:hover{background-color:#8ea4751a}.lc-toast--error{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-error);color:var(--color-text-primary)}.lc-toast--error .lc-toast__icon{color:var(--color-error)}.lc-toast--error .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--error .lc-toast__action:hover,.lc-toast--error .lc-toast__close:hover{background-color:#9d0e0e1a}.lc-toast--warning{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-warning);color:var(--color-text-primary)}.lc-toast--warning .lc-toast__icon{color:var(--color-warning)}.lc-toast--warning .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--warning .lc-toast__action:hover,.lc-toast--warning .lc-toast__close:hover{background-color:#e1a0401a}.lc-toast--info{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-info);color:var(--color-text-primary)}.lc-toast--info .lc-toast__icon{color:var(--color-info)}.lc-toast--info .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--info .lc-toast__action:hover,.lc-toast--info .lc-toast__close:hover{background-color:#3b65881a}@keyframes slideIn{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%{transform:translateY(0);opacity:1}to{transform:translateY(-100%);opacity:0}}@media(prefers-reduced-motion:reduce){.lc-toast{animation:none}@keyframes slideIn{0%,to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%,to{transform:translateY(0);opacity:0}}}\n"],
|
|
183
|
+
styles: ["lc-toast:not(lc-toast-outlet lc-toast){display:block;width:100%;max-width:400px;margin-bottom:var(--spacing-2)}.lc-toast{border-radius:var(--border-radius-md);box-shadow:var(--elevation-3);border-width:1px;border-style:solid;animation:slideIn .15s ease-out;pointer-events:auto}.lc-toast__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-toast__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-toast__message{flex:1;min-width:0;font-size:var(--font-size-sm);line-height:var(--line-height-normal);padding-top:2px;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-toast__action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:var(--size-min-touch-width, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);margin-block:min(0px,(1lh + 2 * var(--spacing-1) - var(--size-min-touch-height, 2.75rem)) / 2);background:transparent;border:none;cursor:pointer;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);transition:background-color .15s ease-in-out}.lc-toast__action:hover{background-color:#0000000d}.lc-toast__action:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast__close{flex-shrink:0;width:var(--size-min-touch-width, 2.75rem);height:var(--size-min-touch-height, 2.75rem);background:transparent;color:var(--color-text-secondary);border:none;cursor:pointer;padding:0;margin:calc((20px - var(--size-min-touch-height, 2.75rem)) / 2);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-toast__close:hover{background-color:#0000000d}.lc-toast__close:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast--success{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-success);color:var(--color-text-primary)}.lc-toast--success .lc-toast__icon{color:var(--color-on-success-subtle)}.lc-toast--success .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--success .lc-toast__action:hover,.lc-toast--success .lc-toast__close:hover{background-color:#8ea4751a}.lc-toast--error{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-error);color:var(--color-text-primary)}.lc-toast--error .lc-toast__icon{color:var(--color-on-error-subtle)}.lc-toast--error .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--error .lc-toast__action:hover,.lc-toast--error .lc-toast__close:hover{background-color:#9d0e0e1a}.lc-toast--warning{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-warning);color:var(--color-text-primary)}.lc-toast--warning .lc-toast__icon{color:var(--color-on-warning-subtle)}.lc-toast--warning .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--warning .lc-toast__action:hover,.lc-toast--warning .lc-toast__close:hover{background-color:#e1a0401a}.lc-toast--info{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-info);color:var(--color-text-primary)}.lc-toast--info .lc-toast__icon{color:var(--color-on-info-subtle)}.lc-toast--info .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--info .lc-toast__action:hover,.lc-toast--info .lc-toast__close:hover{background-color:#3b65881a}@keyframes slideIn{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%{transform:translateY(0);opacity:1}to{transform:translateY(-100%);opacity:0}}@media(prefers-reduced-motion:reduce){.lc-toast{animation:none}@keyframes slideIn{0%,to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%,to{transform:translateY(0);opacity:0}}}\n"],
|
|
184
184
|
dependencies: [{
|
|
185
185
|
kind: "component",
|
|
186
186
|
type: IconComponent,
|
|
@@ -233,7 +233,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
233
233
|
"[attr.aria-atomic]": "announce() ? \"true\" : null"
|
|
234
234
|
},
|
|
235
235
|
template: "<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n",
|
|
236
|
-
styles: ["lc-toast:not(lc-toast-outlet lc-toast){display:block;width:100%;max-width:400px;margin-bottom:var(--spacing-2)}.lc-toast{border-radius:var(--border-radius-md);box-shadow:var(--elevation-3);border-width:1px;border-style:solid;animation:slideIn .15s ease-out;pointer-events:auto}.lc-toast__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-toast__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-toast__message{flex:1;min-width:0;font-size:var(--font-size-sm);line-height:var(--line-height-normal);padding-top:2px;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-toast__action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:var(--size-min-touch-width, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);margin-block:min(0px,(1lh + 2 * var(--spacing-1) - var(--size-min-touch-height, 2.75rem)) / 2);background:transparent;border:none;cursor:pointer;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);transition:background-color .15s ease-in-out}.lc-toast__action:hover{background-color:#0000000d}.lc-toast__action:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast__close{flex-shrink:0;width:var(--size-min-touch-width, 2.75rem);height:var(--size-min-touch-height, 2.75rem);background:transparent;color:var(--color-text-secondary);border:none;cursor:pointer;padding:0;margin:calc((20px - var(--size-min-touch-height, 2.75rem)) / 2);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-toast__close:hover{background-color:#0000000d}.lc-toast__close:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast--success{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-success);color:var(--color-text-primary)}.lc-toast--success .lc-toast__icon{color:var(--color-success)}.lc-toast--success .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--success .lc-toast__action:hover,.lc-toast--success .lc-toast__close:hover{background-color:#8ea4751a}.lc-toast--error{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-error);color:var(--color-text-primary)}.lc-toast--error .lc-toast__icon{color:var(--color-error)}.lc-toast--error .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--error .lc-toast__action:hover,.lc-toast--error .lc-toast__close:hover{background-color:#9d0e0e1a}.lc-toast--warning{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-warning);color:var(--color-text-primary)}.lc-toast--warning .lc-toast__icon{color:var(--color-warning)}.lc-toast--warning .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--warning .lc-toast__action:hover,.lc-toast--warning .lc-toast__close:hover{background-color:#e1a0401a}.lc-toast--info{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-info);color:var(--color-text-primary)}.lc-toast--info .lc-toast__icon{color:var(--color-info)}.lc-toast--info .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--info .lc-toast__action:hover,.lc-toast--info .lc-toast__close:hover{background-color:#3b65881a}@keyframes slideIn{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%{transform:translateY(0);opacity:1}to{transform:translateY(-100%);opacity:0}}@media(prefers-reduced-motion:reduce){.lc-toast{animation:none}@keyframes slideIn{0%,to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%,to{transform:translateY(0);opacity:0}}}\n"]
|
|
236
|
+
styles: ["lc-toast:not(lc-toast-outlet lc-toast){display:block;width:100%;max-width:400px;margin-bottom:var(--spacing-2)}.lc-toast{border-radius:var(--border-radius-md);box-shadow:var(--elevation-3);border-width:1px;border-style:solid;animation:slideIn .15s ease-out;pointer-events:auto}.lc-toast__container{display:flex;align-items:flex-start;gap:var(--spacing-3);padding:var(--lc-density-padding-md, var(--spacing-4))}.lc-toast__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.lc-toast__message{flex:1;min-width:0;font-size:var(--font-size-sm);line-height:var(--line-height-normal);padding-top:2px;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3}.lc-toast__action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:var(--size-min-touch-width, 2.75rem);min-height:var(--size-min-touch-height, 2.75rem);margin-block:min(0px,(1lh + 2 * var(--spacing-1) - var(--size-min-touch-height, 2.75rem)) / 2);background:transparent;border:none;cursor:pointer;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-sm);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);transition:background-color .15s ease-in-out}.lc-toast__action:hover{background-color:#0000000d}.lc-toast__action:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast__close{flex-shrink:0;width:var(--size-min-touch-width, 2.75rem);height:var(--size-min-touch-height, 2.75rem);background:transparent;color:var(--color-text-secondary);border:none;cursor:pointer;padding:0;margin:calc((20px - var(--size-min-touch-height, 2.75rem)) / 2);border-radius:var(--border-radius-sm);transition:background-color .15s ease-in-out;display:flex;align-items:center;justify-content:center}.lc-toast__close:hover{background-color:#0000000d}.lc-toast__close:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.lc-toast--success{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-success);color:var(--color-text-primary)}.lc-toast--success .lc-toast__icon{color:var(--color-on-success-subtle)}.lc-toast--success .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--success .lc-toast__action:hover,.lc-toast--success .lc-toast__close:hover{background-color:#8ea4751a}.lc-toast--error{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-error);color:var(--color-text-primary)}.lc-toast--error .lc-toast__icon{color:var(--color-on-error-subtle)}.lc-toast--error .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--error .lc-toast__action:hover,.lc-toast--error .lc-toast__close:hover{background-color:#9d0e0e1a}.lc-toast--warning{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-warning);color:var(--color-text-primary)}.lc-toast--warning .lc-toast__icon{color:var(--color-on-warning-subtle)}.lc-toast--warning .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--warning .lc-toast__action:hover,.lc-toast--warning .lc-toast__close:hover{background-color:#e1a0401a}.lc-toast--info{background-color:var(--color-surface);background-image:var(--gradient-surface);border-left:4px solid var(--color-info);color:var(--color-text-primary)}.lc-toast--info .lc-toast__icon{color:var(--color-on-info-subtle)}.lc-toast--info .lc-toast__action{color:var(--color-text-secondary)}.lc-toast--info .lc-toast__action:hover,.lc-toast--info .lc-toast__close:hover{background-color:#3b65881a}@keyframes slideIn{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%{transform:translateY(0);opacity:1}to{transform:translateY(-100%);opacity:0}}@media(prefers-reduced-motion:reduce){.lc-toast{animation:none}@keyframes slideIn{0%,to{transform:translateY(0);opacity:1}}@keyframes slideOut{0%,to{transform:translateY(0);opacity:0}}}\n"]
|
|
237
237
|
}]
|
|
238
238
|
}],
|
|
239
239
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -576,3 +576,84 @@ describe('Component pairings of 3.19.2 (request 026)', () => {
|
|
|
576
576
|
});
|
|
577
577
|
});
|
|
578
578
|
});
|
|
579
|
+
|
|
580
|
+
// Request 027 (3.20): the lc-toast and lc-alert variant icons (026 review
|
|
581
|
+
// finding 2). They read the plain fills --color-<variant>: success and warning
|
|
582
|
+
// in light, error and info in dark stayed below 3:1 (toast warning light 2.16,
|
|
583
|
+
// error dark 1.71). WCAG 1.4.11 asks 3:1. They now read the inks
|
|
584
|
+
// --color-on-<variant>-subtle; the fills keep the toast rail and the alert tint.
|
|
585
|
+
describe('Component pairings of 3.20 (request 027)', () => {
|
|
586
|
+
const sheets = {
|
|
587
|
+
toast: compile(resolve(__dirname, '../lib/toast/toast.component.scss')).css,
|
|
588
|
+
alert: compile(resolve(__dirname, '../lib/alert/alert.component.scss')).css,
|
|
589
|
+
};
|
|
590
|
+
const rulesOf = (css: string) => [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => ({ selector: m[1].trim(), body: m[2] }));
|
|
591
|
+
const rule = (component: keyof typeof sheets, selector: string) => {
|
|
592
|
+
const found = rulesOf(sheets[component]).filter((r) => r.selector === selector);
|
|
593
|
+
expect(found).toHaveLength(1);
|
|
594
|
+
return found[0].body;
|
|
595
|
+
};
|
|
596
|
+
const variants = ['success', 'error', 'warning', 'info'];
|
|
597
|
+
|
|
598
|
+
it('AC-14/AC-16: each variant icon reads its ink token, no raw colour; the fills stay on the rail and the tint', () => {
|
|
599
|
+
for (const c of ['toast', 'alert'] as const) {
|
|
600
|
+
for (const v of variants) {
|
|
601
|
+
expect(rule(c, `.lc-${c}--${v} .lc-${c}__icon`).trim()).toBe(`color: var(--color-on-${v}-subtle);`);
|
|
602
|
+
}
|
|
603
|
+
// The base icon rule sets no colour; nothing else colours the icon.
|
|
604
|
+
expect(rule(c, `.lc-${c}__icon`)).not.toMatch(/(^|[\s;])color:/);
|
|
605
|
+
const others = rulesOf(sheets[c]).filter((r) => r.selector.includes(`.lc-${c}__icon`) && /(^|[\s;])color:/.test(r.body));
|
|
606
|
+
expect(others).toHaveLength(4);
|
|
607
|
+
// AC-17: the UA forces the icon's currentColor in forced colours.
|
|
608
|
+
expect(sheets[c]).not.toMatch(/forced-color-adjust/);
|
|
609
|
+
}
|
|
610
|
+
for (const v of variants) {
|
|
611
|
+
expect(rule('toast', `.lc-toast--${v}`)).toMatch(new RegExp(`border-left: 4px solid var\\(--color-${v}\\);`));
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
/** Design 027 §5, per palette [light, dark]: the minimum over the variants and the lowest backgrounds. */
|
|
616
|
+
const expected = { toast: [5.07, 6.35], alert: [4.38, 4.97], before: [2.16, 1.71] };
|
|
617
|
+
|
|
618
|
+
describe.each(palettes.map(([name, tokens], i) => [name, tokens, i] as const))('%s palette', (_palette, tokens, i) => {
|
|
619
|
+
const page = color(tokens, '--color-background');
|
|
620
|
+
const on = (name: string, base: Rgba = page) => over(color(tokens, name), base);
|
|
621
|
+
/** The toast's ends: the stops of --gradient-surface, over its --color-surface fill. */
|
|
622
|
+
const gradient = tokens['--gradient-surface'].match(/^linear-gradient\(180deg,\s*(.+)\)$/);
|
|
623
|
+
const stops = (gradient ? gradient[1] : '').split(/,\s*(?![^(]*\))/).map((stop) => {
|
|
624
|
+
const ref = stop.match(/^var\((--[\w-]+)\)$/);
|
|
625
|
+
return over(ref ? color(tokens, ref[1]) : parse(stop), on('--color-surface'));
|
|
626
|
+
});
|
|
627
|
+
const toastMin = (icon: (v: string) => string) =>
|
|
628
|
+
Math.min(...variants.flatMap((v) => stops.map((end) => ratio(over(color(tokens, icon(v)), end), end))));
|
|
629
|
+
|
|
630
|
+
it('AC-12: the toast icon reaches 3:1 at both ends of the toast in every variant', () => {
|
|
631
|
+
expect(stops).toHaveLength(2);
|
|
632
|
+
const min = toastMin((v) => `--color-on-${v}-subtle`);
|
|
633
|
+
expect(min).toBeCloseTo(expected.toast[i], 2);
|
|
634
|
+
expect(min).toBeGreaterThanOrEqual(3);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('AC-13: the alert icon reaches 3:1 on its tint over every surface it may sit on', () => {
|
|
638
|
+
const bases = [...surfaces, '--color-background-low'].map((name) => on(name));
|
|
639
|
+
const min = Math.min(
|
|
640
|
+
...variants.flatMap((v) => {
|
|
641
|
+
const tint = rule('alert', `.lc-alert--${v}`).match(/background-color: (rgba\([^)]+\));/);
|
|
642
|
+
expect(tint).not.toBeNull();
|
|
643
|
+
return bases.map((base) => {
|
|
644
|
+
const fill = over(parse((tint as RegExpMatchArray)[1]), base);
|
|
645
|
+
return ratio(over(color(tokens, `--color-on-${v}-subtle`), fill), fill);
|
|
646
|
+
});
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
649
|
+
expect(min).toBeCloseTo(expected.alert[i], 2);
|
|
650
|
+
expect(min).toBeGreaterThanOrEqual(3);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('why: the plain fills of 3.19.2 stayed below 3:1 on the toast', () => {
|
|
654
|
+
const before = toastMin((v) => `--color-${v}`);
|
|
655
|
+
expect(before).toBeCloseTo(expected.before[i], 2);
|
|
656
|
+
expect(before).toBeLessThan(3);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
});
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import { ChartValueFormatter } from "@lifecorner/angular-ui-kit/internal/chart";
|
|
2
1
|
import * as i0 from "@angular/core";
|
|
3
2
|
export interface BarChartItem {
|
|
4
3
|
/** Bar value. Negative values draw downward (left) from the zero baseline. */
|
|
5
4
|
value: number;
|
|
6
5
|
label?: string;
|
|
7
6
|
color?: string;
|
|
7
|
+
/**
|
|
8
|
+
* The text shown as this bar's value and read in the generated aria label,
|
|
9
|
+
* instead of `formatValue(value, item)`, for example "313.000,00 · 54,8 %".
|
|
10
|
+
* The kit shows it as given and never formats or computes it. An empty
|
|
11
|
+
* string shows no value for this bar.
|
|
12
|
+
*/
|
|
13
|
+
valueText?: string;
|
|
8
14
|
}
|
|
9
15
|
export type BarChartOrientation = 'vertical' | 'horizontal';
|
|
16
|
+
/**
|
|
17
|
+
* How the category labels are set: `'axis'` (one line of SVG text on the
|
|
18
|
+
* category axis) or `'wrap'` (HTML text that wraps and follows the text size).
|
|
19
|
+
*/
|
|
20
|
+
export type BarChartLabelLayout = 'axis' | 'wrap';
|
|
21
|
+
/**
|
|
22
|
+
* Signature of `lc-bar-chart`'s `formatValue`. A bar's value label passes the
|
|
23
|
+
* bar's item; an axis tick passes the tick only (`item` is `undefined`).
|
|
24
|
+
* One-argument formatters keep working.
|
|
25
|
+
*/
|
|
26
|
+
export type BarChartValueFormatter = (value: number, item?: BarChartItem) => string;
|
|
10
27
|
export declare class BarChartComponent {
|
|
11
28
|
private readonly _el;
|
|
12
29
|
private readonly _destroyRef;
|
|
@@ -20,28 +37,71 @@ export declare class BarChartComponent {
|
|
|
20
37
|
* `width` is the viewBox width until the container has been measured and,
|
|
21
38
|
* when set, the host's `max-width`, so the chart never grows past it. Leave
|
|
22
39
|
* unset for a fully fluid chart.
|
|
40
|
+
* @default undefined
|
|
23
41
|
*/
|
|
24
42
|
width: import("@angular/core").InputSignal<number | undefined>;
|
|
25
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Chart height in pixels. A horizontal `'wrap'` chart takes the height of
|
|
45
|
+
* its rows; a vertical `'wrap'` chart adds its label row below this height.
|
|
46
|
+
* @default 200
|
|
47
|
+
*/
|
|
26
48
|
height: import("@angular/core").InputSignal<number>;
|
|
27
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Bar orientation.
|
|
51
|
+
* @default 'vertical'
|
|
52
|
+
*/
|
|
28
53
|
orientation: import("@angular/core").InputSignal<BarChartOrientation>;
|
|
29
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Show value labels on bars. In horizontal `'wrap'` rows the value sits at
|
|
56
|
+
* the end of the label line.
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
30
59
|
showValues: import("@angular/core").InputSignal<boolean>;
|
|
31
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Show axis labels.
|
|
62
|
+
* @default true
|
|
63
|
+
*/
|
|
32
64
|
showLabels: import("@angular/core").InputSignal<boolean>;
|
|
33
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* How the category labels are set. `'axis'`: one line of SVG text on the
|
|
67
|
+
* category axis, in a fixed 40 px band left of a horizontal bar or centred
|
|
68
|
+
* under a vertical column. Long labels can run out of the chart. `'wrap'`:
|
|
69
|
+
* HTML text that wraps to the room it needs and follows the text size. A
|
|
70
|
+
* horizontal chart becomes a list of rows: the label and the value on one
|
|
71
|
+
* line (the value moves under the label when both don't fit), and below
|
|
72
|
+
* them the bar across the full width. The longest bar fills the width, and
|
|
73
|
+
* `height`, `barGap` and `showGrid` don't apply. A vertical chart keeps its
|
|
74
|
+
* plot and wraps each label within its column, under the chart.
|
|
75
|
+
* @default 'axis'
|
|
76
|
+
*/
|
|
77
|
+
labelLayout: import("@angular/core").InputSignal<BarChartLabelLayout>;
|
|
78
|
+
/**
|
|
79
|
+
* Show grid lines. Not used by horizontal `'wrap'` rows.
|
|
80
|
+
* @default true
|
|
81
|
+
*/
|
|
34
82
|
showGrid: import("@angular/core").InputSignal<boolean>;
|
|
35
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Gap between bars as ratio (0-1). Not used by horizontal `'wrap'` rows.
|
|
85
|
+
* @default 0.3
|
|
86
|
+
*/
|
|
36
87
|
barGap: import("@angular/core").InputSignal<number>;
|
|
37
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Use a single color for all bars.
|
|
90
|
+
* @default ''
|
|
91
|
+
*/
|
|
38
92
|
color: import("@angular/core").InputSignal<string>;
|
|
39
|
-
/**
|
|
40
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Formats the value labels and the axis ticks. For a bar it gets the bar's
|
|
95
|
+
* item as the second argument; for an axis tick it gets the tick only
|
|
96
|
+
* (`item` is `undefined`). An item's `valueText` takes precedence.
|
|
97
|
+
* @default float-safe `String(value)`
|
|
98
|
+
*/
|
|
99
|
+
formatValue: import("@angular/core").InputSignal<BarChartValueFormatter>;
|
|
41
100
|
/**
|
|
42
101
|
* Accessible name of the chart. Defaults to a generated summary that lists
|
|
43
102
|
* every bar's label and value, so assistive technology hears the data and
|
|
44
103
|
* not just "Bar chart".
|
|
104
|
+
* @default ''
|
|
45
105
|
*/
|
|
46
106
|
ariaLabel: import("@angular/core").InputSignal<string>;
|
|
47
107
|
private readonly PADDING_LEFT;
|
|
@@ -51,7 +111,19 @@ export declare class BarChartComponent {
|
|
|
51
111
|
protected readonly effectiveWidth: import("@angular/core").Signal<number>;
|
|
52
112
|
protected readonly viewBox: import("@angular/core").Signal<string>;
|
|
53
113
|
protected readonly isVertical: import("@angular/core").Signal<boolean>;
|
|
114
|
+
/** Horizontal `'wrap'`: HTML rows instead of the SVG plot. */
|
|
115
|
+
protected readonly wrapRows: import("@angular/core").Signal<boolean>;
|
|
116
|
+
/** Vertical `'wrap'`: the SVG plot, with the category labels as an HTML row under it. */
|
|
117
|
+
protected readonly wrapColumns: import("@angular/core").Signal<boolean>;
|
|
118
|
+
/**
|
|
119
|
+
* Room under the plot. `'axis'` keeps 30 for the SVG labels; vertical
|
|
120
|
+
* `'wrap'` labels sit under the SVG, so only 2 are left, or 18 when a
|
|
121
|
+
* negative bar's value label hangs below it.
|
|
122
|
+
*/
|
|
123
|
+
private readonly paddingBottom;
|
|
54
124
|
private readonly plot;
|
|
125
|
+
/** A bar's text: `valueText`, else `formatValue(value, item)`. */
|
|
126
|
+
private textOf;
|
|
55
127
|
/**
|
|
56
128
|
* One scale for grid *and* bars: bounds are nice multiples of the tick step,
|
|
57
129
|
* always include zero, and extend below zero when the data does — so the top
|
|
@@ -95,6 +167,24 @@ export declare class BarChartComponent {
|
|
|
95
167
|
}>;
|
|
96
168
|
/** Bottom edge of the plot — where horizontal-orientation tick labels sit. */
|
|
97
169
|
protected readonly plotBottom: import("@angular/core").Signal<number>;
|
|
170
|
+
/**
|
|
171
|
+
* Bounds of the horizontal `'wrap'` rows: zero is always inside, and the
|
|
172
|
+
* longest bar fills the track.
|
|
173
|
+
*/
|
|
174
|
+
private readonly rowBounds;
|
|
175
|
+
/**
|
|
176
|
+
* Horizontal `'wrap'` rows. Bar `x` and `width` are percentages of the
|
|
177
|
+
* track, so the server HTML is final before the container is measured.
|
|
178
|
+
*/
|
|
179
|
+
protected readonly rows: import("@angular/core").Signal<{
|
|
180
|
+
label: string;
|
|
181
|
+
valueText: string;
|
|
182
|
+
color: string;
|
|
183
|
+
x: string;
|
|
184
|
+
width: string;
|
|
185
|
+
}[]>;
|
|
186
|
+
/** Position of the zero line in the `'wrap'` rows; `null` without negative values. */
|
|
187
|
+
protected readonly rowZero: import("@angular/core").Signal<string | null>;
|
|
98
188
|
protected readonly effectiveAriaLabel: import("@angular/core").Signal<string>;
|
|
99
189
|
static ɵfac: i0.ɵɵFactoryDeclaration<BarChartComponent, never>;
|
|
100
190
|
static ɵcmp: i0.ɵɵComponentDeclaration<BarChartComponent, "lc-bar-chart", never, {
|
|
@@ -128,6 +218,11 @@ export declare class BarChartComponent {
|
|
|
128
218
|
"required": false;
|
|
129
219
|
"isSignal": true;
|
|
130
220
|
};
|
|
221
|
+
"labelLayout": {
|
|
222
|
+
"alias": "labelLayout";
|
|
223
|
+
"required": false;
|
|
224
|
+
"isSignal": true;
|
|
225
|
+
};
|
|
131
226
|
"showGrid": {
|
|
132
227
|
"alias": "showGrid";
|
|
133
228
|
"required": false;
|
|
@@ -17,10 +17,22 @@ interface MoneyFormatOptions {
|
|
|
17
17
|
minusLabel?: string;
|
|
18
18
|
/** Spoken word for the plus sign. @default 'plus' */
|
|
19
19
|
plusLabel?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the visible `text` shows the currency symbol (or code). `false`
|
|
22
|
+
* leaves out the symbol and the space that belongs to it („−42,17“), for a
|
|
23
|
+
* column or title that already names the unit („Kosten in €“). Digits,
|
|
24
|
+
* grouping, sign and minor units stay. The `spoken` text still names the
|
|
25
|
+
* currency.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
showCurrency?: boolean;
|
|
20
29
|
}
|
|
21
30
|
/** A formatted amount: the visible text and the words a screen reader speaks. */
|
|
22
31
|
interface MoneyText {
|
|
23
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Visible text, e.g. „−42,17 €“ (U+2212 minus, U+00A0 from `Intl`), or
|
|
34
|
+
* „−42,17“ with `showCurrency: false`.
|
|
35
|
+
*/
|
|
24
36
|
text: string;
|
|
25
37
|
/** Spoken text, e.g. „minus 42,17 Euro“. */
|
|
26
38
|
spoken: string;
|
|
@@ -34,12 +46,15 @@ declare function moneyDirection(locale: string): 'ltr' | 'rtl';
|
|
|
34
46
|
* exactly, never through a float: the decimal string goes into `Intl`.
|
|
35
47
|
* Returns `null` for an amount that is not a safe integer or a currency that
|
|
36
48
|
* is not a three-letter code. This is the formatter of `lc-money`, for table
|
|
37
|
-
* `formatter`s and texts.
|
|
49
|
+
* `formatter`s and texts. `showCurrency: false` leaves the symbol and its
|
|
50
|
+
* space out of `text` (a column headed „Kosten in €“); `spoken` keeps it.
|
|
38
51
|
*
|
|
39
52
|
* @example
|
|
40
53
|
* ```ts
|
|
41
54
|
* formatMoney(-4217, 'EUR', { locale: 'de-DE' });
|
|
42
55
|
* // { text: '−42,17 €', spoken: 'minus 42,17 Euro' }
|
|
56
|
+
* formatMoney(-4217, 'EUR', { locale: 'de-DE', showCurrency: false });
|
|
57
|
+
* // { text: '−42,17', spoken: 'minus 42,17 Euro' }
|
|
43
58
|
* ```
|
|
44
59
|
*/
|
|
45
60
|
export declare function formatMoney(amountMinor: number, currency: string, options?: MoneyFormatOptions): MoneyText | null;
|
|
@@ -58,6 +73,8 @@ export declare function formatMoney(amountMinor: number, currency: string, optio
|
|
|
58
73
|
* - Tabular figures, no wrapping inside the amount; the amount is a `dir`
|
|
59
74
|
* isolate (`ltr`, or `rtl` for an RTL locale), safe inside RTL text.
|
|
60
75
|
* - Font and colour come from the context; colour never carries the sign.
|
|
76
|
+
* - `showCurrency=false`: no symbol in the visible amount (a column headed
|
|
77
|
+
* „Kosten in €“); the spoken text keeps the currency.
|
|
61
78
|
* - `locale` empty: the app's `LOCALE_ID` (Angular's default `en-US`), the
|
|
62
79
|
* same on the server and in the browser.
|
|
63
80
|
* - For table `formatter`s and texts use {@link formatMoney}.
|
|
@@ -108,6 +125,15 @@ export declare class MoneyComponent {
|
|
|
108
125
|
* @default 'plus'
|
|
109
126
|
*/
|
|
110
127
|
readonly plusLabel: import("@angular/core").InputSignal<string>;
|
|
128
|
+
/**
|
|
129
|
+
* Shows the currency symbol in the visible amount. `false` leaves out the
|
|
130
|
+
* symbol and its space („−42,17“) for a table column or a title that
|
|
131
|
+
* already names the unit („Kosten in €“). Screen readers still hear the
|
|
132
|
+
* currency („minus 42,17 Euro“), and `currency` still sets the minor units.
|
|
133
|
+
* Use it only where the unit is visible next to the amount.
|
|
134
|
+
* @default true
|
|
135
|
+
*/
|
|
136
|
+
readonly showCurrency: import("@angular/core").InputSignal<boolean>;
|
|
111
137
|
/** The locale in use: the `locale` input or the app's `LOCALE_ID`. */
|
|
112
138
|
protected readonly effectiveLocale: import("@angular/core").Signal<string>;
|
|
113
139
|
protected readonly formatted: import("@angular/core").Signal<import("@lifecorner/angular-ui-kit/money").MoneyText | null>;
|
|
@@ -145,6 +171,11 @@ export declare class MoneyComponent {
|
|
|
145
171
|
"required": false;
|
|
146
172
|
"isSignal": true;
|
|
147
173
|
};
|
|
174
|
+
"showCurrency": {
|
|
175
|
+
"alias": "showCurrency";
|
|
176
|
+
"required": false;
|
|
177
|
+
"isSignal": true;
|
|
178
|
+
};
|
|
148
179
|
}, {}, never, never, true, never>;
|
|
149
180
|
}
|
|
150
181
|
export type { MoneyFormatOptions, MoneySignDisplay, MoneyText };
|