@mk-kit/ui 0.41.0 → 0.42.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/README.md +3 -3
- package/fesm2022/mk-kit-ui-core.mjs +87 -0
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-data.mjs +368 -13
- package/fesm2022/mk-kit-ui-data.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-datetime.mjs +2 -2
- package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-directives.mjs +395 -2
- package/fesm2022/mk-kit-ui-directives.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dnd.mjs +183 -29
- package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-forms.mjs +6 -4
- package/fesm2022/mk-kit-ui-forms.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-table.mjs +26 -6
- package/fesm2022/mk-kit-ui-table.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/mk-kit.css +604 -0
- package/types/mk-kit-ui-core.d.ts +48 -1
- package/types/mk-kit-ui-data.d.ts +158 -3
- package/types/mk-kit-ui-directives.d.ts +234 -3
- package/types/mk-kit-ui-dnd.d.ts +96 -14
- package/types/mk-kit-ui-forms.d.ts +6 -4
- package/types/mk-kit-ui-table.d.ts +38 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, ChangeDetectionStrategy, Component, booleanAttribute,
|
|
3
|
-
import {
|
|
2
|
+
import { input, ChangeDetectionStrategy, Component, booleanAttribute, inject, ElementRef, Renderer2, DOCUMENT, PLATFORM_ID, numberAttribute, computed, effect, Directive, signal, contentChildren, output, DestroyRef, afterNextRender, afterRenderEffect, untracked, Injector, viewChild, model, forwardRef, contentChild, TemplateRef, ViewEncapsulation } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, DOCUMENT as DOCUMENT$1, NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import { mkUniqueId, MK_I18N, MkFieldContext, mkHighlight } from '@mk-kit/ui/core';
|
|
5
|
-
import {
|
|
5
|
+
import { MkIcon } from '@mk-kit/ui/icon';
|
|
6
6
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
7
|
import { MkCopyToClipboard } from '@mk-kit/ui/directives';
|
|
8
8
|
import { mkMoveItemInArray, MkDropList, MkDrag } from '@mk-kit/ui/dnd';
|
|
@@ -151,6 +151,186 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
151
151
|
}, template: "@if (!dot()) {\n <ng-content />\n}\n", styles: ["@charset \"UTF-8\";:host{--_main: var(--mk-primary);--_contrast: var(--mk-primary-contrast);--_subtle: var(--mk-primary-subtle);--_subtle-text: var(--mk-primary-subtle-text);display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-1);height:1.375rem;min-width:1.375rem;padding:0 var(--mk-space-2);border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-pill);font-family:inherit;font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-medium);line-height:1;white-space:nowrap;vertical-align:middle}:host([data-tone=primary]){--_main: var(--mk-primary);--_hover: var(--mk-primary-hover);--_active: var(--mk-primary-active);--_contrast: var(--mk-primary-contrast);--_subtle: var(--mk-primary-subtle);--_subtle-text: var(--mk-primary-subtle-text)}:host([data-tone=success]){--_main: var(--mk-success);--_hover: var(--mk-success-hover);--_active: var(--mk-success-hover);--_contrast: var(--mk-success-contrast);--_subtle: var(--mk-success-subtle);--_subtle-text: var(--mk-success-subtle-text)}:host([data-tone=warning]){--_main: var(--mk-warning);--_hover: var(--mk-warning-hover);--_active: var(--mk-warning-hover);--_contrast: var(--mk-warning-contrast);--_subtle: var(--mk-warning-subtle);--_subtle-text: var(--mk-warning-subtle-text)}:host([data-tone=danger]){--_main: var(--mk-danger);--_hover: var(--mk-danger-hover);--_active: var(--mk-danger-hover);--_contrast: var(--mk-danger-contrast);--_subtle: var(--mk-danger-subtle);--_subtle-text: var(--mk-danger-subtle-text)}:host([data-tone=info]){--_main: var(--mk-info);--_hover: var(--mk-info-hover);--_active: var(--mk-info-hover);--_contrast: var(--mk-info-contrast);--_subtle: var(--mk-info-subtle);--_subtle-text: var(--mk-info-subtle-text)}:host([data-tone=neutral]){--_main: var(--mk-surface-inverse);--_hover: var(--mk-surface-inverse);--_active: var(--mk-surface-inverse);--_contrast: var(--mk-text-inverse);--_subtle: var(--mk-neutral-subtle);--_subtle-text: var(--mk-neutral-subtle-text)}:host(.mk-badge--sm){height:1.125rem;min-width:1.125rem;padding:0 var(--mk-space-1);font-size:var(--mk-font-size-xs)}:host(.mk-badge--lg){height:1.625rem;min-width:1.625rem;padding:0 var(--mk-space-3);font-size:var(--mk-font-size-sm)}:host(.mk-badge--solid){background-color:var(--_main);color:var(--_contrast)}:host(.mk-badge--soft){background-color:var(--_subtle);color:var(--_subtle-text)}:host(.mk-badge--outline){background-color:transparent;color:var(--_subtle-text);border-color:color-mix(in srgb,var(--_main) 42%,var(--mk-border-strong))}:host(.mk-badge--dot){width:.625rem;height:.625rem;min-width:0;padding:0;border-radius:var(--mk-radius-circle)}:host(.mk-badge--dot.mk-badge--sm){width:.5rem;height:.5rem}:host(.mk-badge--dot.mk-badge--lg){width:.75rem;height:.75rem}\n"] }]
|
|
152
152
|
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Badge overlay — anchors a small badge (count, dot or short text) to a corner
|
|
156
|
+
* of ANY element: an icon button, an avatar, a tab label. The host stays a
|
|
157
|
+
* single tab stop; the badge is a decorative, non-focusable child.
|
|
158
|
+
*
|
|
159
|
+
* - Numbers above `mkBadgeOverlayMax` (default 99) collapse to `99+`.
|
|
160
|
+
* - `mkBadgeOverlayDot` renders a textless dot ("something is new").
|
|
161
|
+
* - `mkBadgeOverlayAriaLabel` is the screen-reader text ("3 unread"). It is
|
|
162
|
+
* rendered in a visually hidden span and wired to the host with
|
|
163
|
+
* `aria-describedby`, so it is announced after the host's own name even
|
|
164
|
+
* when the host has an `aria-label`. When it is given, the visible badge is
|
|
165
|
+
* `aria-hidden` so the count is not read twice.
|
|
166
|
+
* - Positions use logical insets, so `top-end` sits at the top-right in LTR
|
|
167
|
+
* and the top-left in RTL.
|
|
168
|
+
*
|
|
169
|
+
* The badge is painted with the global `.mk-badge-overlay` styles from
|
|
170
|
+
* `@mk-kit/ui/styles.css` and follows the `--mk-*` tone tokens.
|
|
171
|
+
*
|
|
172
|
+
* ```html
|
|
173
|
+
* <button mkButton iconOnly aria-label="Notifications"
|
|
174
|
+
* [mkBadgeOverlay]="unread()" mkBadgeOverlayTone="danger"
|
|
175
|
+
* [mkBadgeOverlayAriaLabel]="unread() + ' unread'">
|
|
176
|
+
* <mk-icon name="bell" />
|
|
177
|
+
* </button>
|
|
178
|
+
*
|
|
179
|
+
* <mk-avatar name="Ada Lovelace" mkBadgeOverlay mkBadgeOverlayDot
|
|
180
|
+
* mkBadgeOverlayTone="success" mkBadgeOverlayPosition="bottom-end" />
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
class MkBadgeOverlay {
|
|
184
|
+
host = inject(ElementRef);
|
|
185
|
+
renderer = inject(Renderer2);
|
|
186
|
+
document = inject(DOCUMENT);
|
|
187
|
+
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
188
|
+
/**
|
|
189
|
+
* Badge content — a count or a short label. `null` / empty hides the badge
|
|
190
|
+
* unless `mkBadgeOverlayDot` is set. Numeric strings are treated as numbers
|
|
191
|
+
* so `mkBadgeOverlay="120"` also collapses to `99+`.
|
|
192
|
+
*/
|
|
193
|
+
content = input(null, { ...(ngDevMode ? { debugName: "content" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlay' });
|
|
194
|
+
/** Corner the badge is anchored to (logical: `end` flips in RTL). */
|
|
195
|
+
position = input('top-end', { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayPosition' });
|
|
196
|
+
/** Semantic color tone (same scale as `mk-badge`). */
|
|
197
|
+
tone = input('primary', { ...(ngDevMode ? { debugName: "tone" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayTone' });
|
|
198
|
+
/** Render a textless dot instead of the content. */
|
|
199
|
+
dot = input(false, { ...(ngDevMode ? { debugName: "dot" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayDot',
|
|
200
|
+
transform: booleanAttribute });
|
|
201
|
+
/** Numbers above this render as `<max>+`. `0` disables the cap. */
|
|
202
|
+
max = input(99, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayMax',
|
|
203
|
+
transform: numberAttribute });
|
|
204
|
+
/** Hide the badge while keeping the directive (and its aria wiring) in place. */
|
|
205
|
+
hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayHidden',
|
|
206
|
+
transform: booleanAttribute });
|
|
207
|
+
/**
|
|
208
|
+
* Screen-reader text for the badge, e.g. `"3 unread"`. Announced as the
|
|
209
|
+
* host's description; the visible badge becomes `aria-hidden`.
|
|
210
|
+
*/
|
|
211
|
+
ariaLabel = input('', { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'mkBadgeOverlayAriaLabel' });
|
|
212
|
+
/** The text painted in the badge (`''` in dot mode). */
|
|
213
|
+
label = computed(() => {
|
|
214
|
+
if (this.dot())
|
|
215
|
+
return '';
|
|
216
|
+
const raw = this.content();
|
|
217
|
+
if (raw === null || raw === undefined || raw === '')
|
|
218
|
+
return '';
|
|
219
|
+
const max = this.max();
|
|
220
|
+
const n = typeof raw === 'number' ? raw : /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
221
|
+
if (Number.isFinite(n) && max > 0 && n > max)
|
|
222
|
+
return `${max}+`;
|
|
223
|
+
return String(raw);
|
|
224
|
+
}, /* @ts-ignore */
|
|
225
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
226
|
+
/** Whether the badge is currently rendered. */
|
|
227
|
+
visible = computed(() => !this.hidden() && (this.dot() || this.label() !== ''), /* @ts-ignore */
|
|
228
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
229
|
+
srId = mkUniqueId('mk-badge-overlay');
|
|
230
|
+
badge = null;
|
|
231
|
+
sr = null;
|
|
232
|
+
/** Set when WE made the host positioned, so we can undo it on removal. */
|
|
233
|
+
positionedHost = false;
|
|
234
|
+
constructor() {
|
|
235
|
+
if (!this.isBrowser)
|
|
236
|
+
return;
|
|
237
|
+
effect(() => {
|
|
238
|
+
const visible = this.visible();
|
|
239
|
+
if (!visible) {
|
|
240
|
+
this.detach();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const badge = this.attach();
|
|
244
|
+
const dot = this.dot();
|
|
245
|
+
const srText = this.ariaLabel().trim();
|
|
246
|
+
this.renderer.setAttribute(badge, 'data-tone', this.tone());
|
|
247
|
+
this.renderer.setAttribute(badge, 'data-position', this.position());
|
|
248
|
+
if (dot) {
|
|
249
|
+
this.renderer.addClass(badge, 'mk-badge-overlay--dot');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
this.renderer.removeClass(badge, 'mk-badge-overlay--dot');
|
|
253
|
+
}
|
|
254
|
+
this.renderer.setProperty(badge, 'textContent', this.label());
|
|
255
|
+
// A dot has nothing to read; a labelled badge is described via the
|
|
256
|
+
// hidden span instead — either way the visual is hidden from AT.
|
|
257
|
+
if (dot || srText) {
|
|
258
|
+
this.renderer.setAttribute(badge, 'aria-hidden', 'true');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
this.renderer.removeAttribute(badge, 'aria-hidden');
|
|
262
|
+
}
|
|
263
|
+
this.syncSr(srText);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
attach() {
|
|
267
|
+
if (this.badge)
|
|
268
|
+
return this.badge;
|
|
269
|
+
const el = this.host.nativeElement;
|
|
270
|
+
const view = this.document.defaultView;
|
|
271
|
+
const position = view?.getComputedStyle(el).position;
|
|
272
|
+
if (!position || position === 'static') {
|
|
273
|
+
this.renderer.setStyle(el, 'position', 'relative');
|
|
274
|
+
this.positionedHost = true;
|
|
275
|
+
}
|
|
276
|
+
const badge = this.renderer.createElement('span');
|
|
277
|
+
this.renderer.addClass(badge, 'mk-badge-overlay');
|
|
278
|
+
this.renderer.appendChild(el, badge);
|
|
279
|
+
this.badge = badge;
|
|
280
|
+
return badge;
|
|
281
|
+
}
|
|
282
|
+
detach() {
|
|
283
|
+
if (this.badge) {
|
|
284
|
+
this.renderer.removeChild(this.host.nativeElement, this.badge);
|
|
285
|
+
this.badge = null;
|
|
286
|
+
}
|
|
287
|
+
this.syncSr('');
|
|
288
|
+
if (this.positionedHost) {
|
|
289
|
+
this.renderer.removeStyle(this.host.nativeElement, 'position');
|
|
290
|
+
this.positionedHost = false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Create / update / remove the hidden description and its aria-describedby link. */
|
|
294
|
+
syncSr(text) {
|
|
295
|
+
const el = this.host.nativeElement;
|
|
296
|
+
if (text) {
|
|
297
|
+
if (!this.sr) {
|
|
298
|
+
const sr = this.renderer.createElement('span');
|
|
299
|
+
this.renderer.addClass(sr, 'mk-visually-hidden');
|
|
300
|
+
this.renderer.setAttribute(sr, 'id', this.srId);
|
|
301
|
+
this.renderer.appendChild(el, sr);
|
|
302
|
+
this.sr = sr;
|
|
303
|
+
const ids = (el.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean);
|
|
304
|
+
if (!ids.includes(this.srId))
|
|
305
|
+
ids.push(this.srId);
|
|
306
|
+
this.renderer.setAttribute(el, 'aria-describedby', ids.join(' '));
|
|
307
|
+
}
|
|
308
|
+
this.renderer.setProperty(this.sr, 'textContent', text);
|
|
309
|
+
}
|
|
310
|
+
else if (this.sr) {
|
|
311
|
+
this.renderer.removeChild(el, this.sr);
|
|
312
|
+
this.sr = null;
|
|
313
|
+
const ids = (el.getAttribute('aria-describedby') ?? '')
|
|
314
|
+
.split(/\s+/)
|
|
315
|
+
.filter((id) => id && id !== this.srId);
|
|
316
|
+
if (ids.length) {
|
|
317
|
+
this.renderer.setAttribute(el, 'aria-describedby', ids.join(' '));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.renderer.removeAttribute(el, 'aria-describedby');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBadgeOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
325
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: MkBadgeOverlay, isStandalone: true, selector: "[mkBadgeOverlay]", inputs: { content: { classPropertyName: "content", publicName: "mkBadgeOverlay", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "mkBadgeOverlayPosition", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "mkBadgeOverlayTone", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "mkBadgeOverlayDot", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "mkBadgeOverlayMax", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "mkBadgeOverlayHidden", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "mkBadgeOverlayAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
326
|
+
}
|
|
327
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBadgeOverlay, decorators: [{
|
|
328
|
+
type: Directive,
|
|
329
|
+
args: [{
|
|
330
|
+
selector: '[mkBadgeOverlay]',
|
|
331
|
+
}]
|
|
332
|
+
}], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlay", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayPosition", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayTone", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayDot", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayMax", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayHidden", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkBadgeOverlayAriaLabel", required: false }] }] } });
|
|
333
|
+
|
|
154
334
|
/**
|
|
155
335
|
* Tag — a non-interactive label for categories, keywords or metadata. Unlike
|
|
156
336
|
* {@link MkChip} it carries no interaction; use it purely for display.
|
|
@@ -1165,6 +1345,15 @@ function mkChartTapPin(apply) {
|
|
|
1165
1345
|
*/
|
|
1166
1346
|
const CHAR_PX = 6.2;
|
|
1167
1347
|
const estimateLabelPx = (label) => label.length * CHAR_PX;
|
|
1348
|
+
/**
|
|
1349
|
+
* Fraction of its band a label may fill before `auto` tilts the axis.
|
|
1350
|
+
*
|
|
1351
|
+
* Not 1.0: labels that *technically* fit still read as a cramped run of words
|
|
1352
|
+
* with no gap between them ("mar" "kwi" "maj" touching each other), which is
|
|
1353
|
+
* the look tilting exists to avoid. Leaving a fifth of the band as breathing
|
|
1354
|
+
* room tilts the tight cases and leaves genuinely roomy axes alone.
|
|
1355
|
+
*/
|
|
1356
|
+
const TILT_THRESHOLD = 0.8;
|
|
1168
1357
|
/**
|
|
1169
1358
|
* BarChart — compares a measure across categories with one or more series
|
|
1170
1359
|
* (grouped or `stacked`), `vertical` (default) or `horizontal`. Dependency-free
|
|
@@ -1257,7 +1446,7 @@ class MkBarChart {
|
|
|
1257
1446
|
if (!count)
|
|
1258
1447
|
return 0;
|
|
1259
1448
|
const band = Math.max(this.drawWidth() - 58, 1) / count;
|
|
1260
|
-
return this.widestLabelPx() <= band ? 0 : 45;
|
|
1449
|
+
return this.widestLabelPx() <= band * TILT_THRESHOLD ? 0 : 45;
|
|
1261
1450
|
}, /* @ts-ignore */
|
|
1262
1451
|
...(ngDevMode ? [{ debugName: "resolvedAngle" }] : /* istanbul ignore next */ []));
|
|
1263
1452
|
/**
|
|
@@ -2222,18 +2411,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
2222
2411
|
}, template: "<div class=\"mk-chart__frame\">\n <svg\n class=\"mk-chart__svg\"\n [attr.viewBox]=\"'0 0 ' + width() + ' ' + height()\"\n preserveAspectRatio=\"xMidYMid meet\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <!-- Horizontal gridlines + y-axis labels -->\n @for (row of yGrid(); track row.value) {\n @if (showGrid()) {\n <line\n class=\"mk-chart__grid\"\n [attr.x1]=\"row.x1\"\n [attr.x2]=\"row.x2\"\n [attr.y1]=\"row.y\"\n [attr.y2]=\"row.y\"\n />\n }\n <text\n class=\"mk-chart__axis-label mk-chart__axis-label--y\"\n [attr.x]=\"plot().x - 8\"\n [attr.y]=\"row.y\"\n >\n {{ row.label }}\n </text>\n }\n\n <!-- x-axis labels -->\n @for (col of xGrid(); track col.value) {\n <text\n class=\"mk-chart__axis-label mk-chart__axis-label--x\"\n [attr.x]=\"col.x\"\n [attr.y]=\"height() - 12\"\n >\n {{ col.label }}\n </text>\n }\n\n <!-- Axis titles -->\n @if (xLabel()) {\n <text\n class=\"mk-chart__axis-title mk-chart__axis-title--x\"\n [attr.x]=\"plot().x + plot().w / 2\"\n [attr.y]=\"height() - 1\"\n >\n {{ xLabel() }}\n </text>\n }\n @if (yLabel()) {\n <text\n class=\"mk-chart__axis-title mk-chart__axis-title--y\"\n [attr.transform]=\"'rotate(-90 10 ' + (plot().y + plot().h / 2) + ')'\"\n [attr.x]=\"10\"\n [attr.y]=\"plot().y + plot().h / 2\"\n >\n {{ yLabel() }}\n </text>\n }\n\n <!-- Points -->\n @for (p of points(); track $index; let i = $index) {\n <circle\n class=\"mk-chart__point\"\n [class.mk-chart__point--active]=\"hoverKey() === keyOf(i)\"\n [attr.cx]=\"p.cx\"\n [attr.cy]=\"p.cy\"\n [attr.r]=\"p.r\"\n [style.fill]=\"p.color\"\n (pointerenter)=\"setHover(keyOf(i))\"\n (pointerleave)=\"setHover(null)\"\n (pointerdown)=\"pinHover(keyOf(i), $event)\"\n />\n }\n </svg>\n\n @if (tooltip(); as t) {\n <div class=\"mk-chart__tooltip\" [style.left.%]=\"t.left\" [style.top.%]=\"t.top\">\n <span class=\"mk-chart__tooltip-title\">\n <span class=\"mk-chart__swatch\" [style.background]=\"t.color\"></span>\n {{ t.title }}\n </span>\n <span class=\"mk-chart__tooltip-row\">\n <span class=\"mk-chart__tooltip-name\">{{ xLabel() || 'x' }}</span>\n <span class=\"mk-chart__tooltip-value\">{{ t.x }}</span>\n </span>\n <span class=\"mk-chart__tooltip-row\">\n <span class=\"mk-chart__tooltip-name\">{{ yLabel() || 'y' }}</span>\n <span class=\"mk-chart__tooltip-value\">{{ t.y }}</span>\n </span>\n </div>\n }\n</div>\n\n@if (legendVisible()) {\n <ul class=\"mk-chart__legend\">\n @for (item of legend(); track item.name) {\n <li class=\"mk-chart__legend-item\">\n <span class=\"mk-chart__swatch\" [style.background]=\"item.color\"></span>\n {{ item.name }}\n </li>\n }\n </ul>\n}\n\n<table class=\"mk-chart__sr-table\">\n <caption>{{ resolvedLabel() }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ i18n.chartSeries }}</th>\n <th scope=\"col\">{{ xLabel() || 'x' }}</th>\n <th scope=\"col\">{{ yLabel() || 'y' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (s of series(); track s.name) {\n @for (p of s.points; track $index) {\n <tr>\n <th scope=\"row\">{{ s.name }}</th>\n <td>{{ p.x }}</td>\n <td>{{ p.y }}</td>\n </tr>\n }\n }\n </tbody>\n</table>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans);color:var(--mk-text)}.mk-chart__frame{position:relative}.mk-chart__svg{display:block;width:100%;height:auto;overflow:visible}.mk-chart__grid{stroke:var(--mk-chart-grid);stroke-width:1;vector-effect:non-scaling-stroke}.mk-chart__axis-label{fill:var(--mk-chart-axis);font-size:11px}.mk-chart__axis-label--y{text-anchor:end;dominant-baseline:middle}.mk-chart__axis-label--x{text-anchor:middle;dominant-baseline:hanging}.mk-chart__axis-label--tilted{text-anchor:end;dominant-baseline:middle}.mk-chart__bar{transition:opacity var(--mk-duration-fast) var(--mk-ease-standard),filter var(--mk-duration-fast) var(--mk-ease-standard)}.mk-chart__bar:hover{filter:brightness(1.08)}.mk-chart__bar--dim{opacity:.4}.mk-chart__area{stroke:none;opacity:.14}.mk-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}.mk-chart__dot{stroke:var(--mk-surface);stroke-width:1.5;vector-effect:non-scaling-stroke}.mk-chart__crosshair{stroke:var(--mk-chart-axis);stroke-width:1;stroke-dasharray:3 3;vector-effect:non-scaling-stroke;opacity:.6}.mk-chart__point{fill-opacity:.72;stroke:var(--mk-surface);stroke-width:1;vector-effect:non-scaling-stroke;cursor:pointer;transition:fill-opacity var(--mk-duration-fast) var(--mk-ease-standard)}.mk-chart__point:hover,.mk-chart__point--active{fill-opacity:1}.mk-radar__ring{fill:none}.mk-radar__area{fill-opacity:.16;stroke-width:2;stroke-linejoin:round;vector-effect:non-scaling-stroke}.mk-radar__axis-label{dominant-baseline:middle}.mk-chart__axis-title{fill:var(--mk-chart-axis);font-size:11px;font-weight:var(--mk-font-weight-medium)}.mk-chart__axis-title--x,.mk-chart__axis-title--y{text-anchor:middle}.mk-chart__slice{stroke:var(--mk-surface);stroke-width:2;transition:opacity var(--mk-duration-fast) var(--mk-ease-standard)}.mk-chart__slice--dim{opacity:.45}.mk-chart__tooltip{position:absolute;z-index:1;transform:translate(-50%,calc(-100% - 8px));pointer-events:none;display:flex;flex-direction:column;gap:2px;padding:var(--mk-space-2) var(--mk-space-3);background-color:var(--mk-surface);color:var(--mk-text);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-lg);font-size:var(--mk-font-size-sm);white-space:nowrap}.mk-chart__tooltip-title{font-weight:var(--mk-font-weight-semibold)}.mk-chart__tooltip-row{display:flex;align-items:center;gap:var(--mk-space-2)}.mk-chart__tooltip-name{color:var(--mk-text-muted)}.mk-chart__tooltip-value{margin-inline-start:auto;font-variant-numeric:tabular-nums;font-weight:var(--mk-font-weight-medium)}.mk-chart__swatch{flex:none;width:.75rem;height:.75rem;border-radius:var(--mk-radius-xs)}.mk-chart__legend{display:flex;flex-wrap:wrap;gap:var(--mk-space-2) var(--mk-space-4);margin:var(--mk-space-3) 0 0;padding:0;padding-inline-start:var(--mk-space-2);list-style:none}.mk-chart__legend-item{display:inline-flex;align-items:center;gap:var(--mk-space-2);font-size:var(--mk-font-size-sm);color:var(--mk-text-muted)}.mk-donut__frame{margin:0 auto}.mk-donut__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none;text-align:center}.mk-donut__center-label{font-size:var(--mk-font-size-2xl);font-weight:var(--mk-font-weight-bold);line-height:1;color:var(--mk-text);font-variant-numeric:tabular-nums}.mk-donut__center-sublabel{font-size:var(--mk-font-size-xs);color:var(--mk-text-subtle);text-transform:uppercase;letter-spacing:.04em}.mk-donut__legend{flex-direction:column;gap:var(--mk-space-2)}.mk-donut__legend .mk-chart__legend-item{width:100%}.mk-donut__legend-name{color:var(--mk-text)}.mk-donut__legend-value{margin-inline-start:auto;color:var(--mk-text-muted);font-variant-numeric:tabular-nums}.mk-chart__sr-table{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.mk-chart__bar,.mk-chart__slice{transition:none}}.mk-chart__slice--interactive,.mk-chart__bar--interactive,.mk-chart__band--interactive,.mk-chart__legend-item--interactive{cursor:pointer}.mk-chart__slice--interactive:focus-visible,.mk-chart__bar--interactive:focus-visible,.mk-chart__band--interactive:focus-visible{outline:2px solid var(--mk-primary);outline-offset:2px}.mk-chart__legend-button{display:inline-flex;align-items:center;gap:inherit;padding:0;margin:0;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer;border-radius:var(--mk-radius-sm)}.mk-chart__legend-button:focus-visible{outline:2px solid var(--mk-primary);outline-offset:2px}\n"] }]
|
|
2223
2412
|
}], propDecorators: { series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], pointRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "pointRadius", required: false }] }], bubble: [{ type: i0.Input, args: [{ isSignal: true, alias: "bubble", required: false }] }], maxRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRadius", required: false }] }], showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], xLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "xLabel", required: false }] }], yLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "yLabel", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
2224
2413
|
|
|
2414
|
+
/** WCAG AA minimum contrast for normal text — what cell values are held to. */
|
|
2415
|
+
const MK_HEATMAP_MIN_CONTRAST = 4.5;
|
|
2416
|
+
/** WCAG 2.x relative luminance of an sRGB colour. */
|
|
2417
|
+
function mkRelativeLuminance([r, g, b]) {
|
|
2418
|
+
const lin = (c) => {
|
|
2419
|
+
const s = c / 255;
|
|
2420
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
2421
|
+
};
|
|
2422
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
2423
|
+
}
|
|
2424
|
+
/** WCAG 2.x contrast ratio (1–21) between two colours. */
|
|
2425
|
+
function mkContrastRatio(a, b) {
|
|
2426
|
+
const la = mkRelativeLuminance(a);
|
|
2427
|
+
const lb = mkRelativeLuminance(b);
|
|
2428
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
2429
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
2430
|
+
}
|
|
2431
|
+
/** What `color-mix(in srgb, a t%, b)` yields: a linear blend in gamma-encoded sRGB. */
|
|
2432
|
+
function mkMixRgb(a, b, t) {
|
|
2433
|
+
const mix = (x, y) => Math.round(x * t + y * (1 - t));
|
|
2434
|
+
return [mix(a[0], b[0]), mix(a[1], b[1]), mix(a[2], b[2])];
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Parse a CSS colour as the CSSOM serialises it — `rgb(r, g, b)` /
|
|
2438
|
+
* `rgba(r, g, b, a)` — or a `#rgb` / `#rrggbb` literal. `null` for anything
|
|
2439
|
+
* else (an unresolved `var()`, `color(...)`, …).
|
|
2440
|
+
*/
|
|
2441
|
+
function mkParseRgb(text) {
|
|
2442
|
+
const s = (text ?? '').trim();
|
|
2443
|
+
const fn = /^rgba?\(\s*(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)/i.exec(s);
|
|
2444
|
+
if (fn)
|
|
2445
|
+
return [Number(fn[1]), Number(fn[2]), Number(fn[3])];
|
|
2446
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(s);
|
|
2447
|
+
if (!hex)
|
|
2448
|
+
return null;
|
|
2449
|
+
const h = hex[1].length === 3 ? [...hex[1]].map((c) => c + c).join('') : hex[1];
|
|
2450
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
2451
|
+
}
|
|
2452
|
+
/** Best of the two text tokens against the cell painted at intensity `t`. */
|
|
2453
|
+
function pickText(t, palette) {
|
|
2454
|
+
const bg = mkMixRgb(palette.accent, palette.surface, t);
|
|
2455
|
+
const onText = mkContrastRatio(palette.text, bg);
|
|
2456
|
+
const onInverse = mkContrastRatio(palette.textInverse, bg);
|
|
2457
|
+
return onInverse > onText
|
|
2458
|
+
? { intensity: t, inverse: true, contrast: onInverse }
|
|
2459
|
+
: { intensity: t, inverse: false, contrast: onText };
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Choose the text token for a cell at intensity `t` (0–1) — light text on
|
|
2463
|
+
* dark cells, dark text on light cells, decided by contrast against the
|
|
2464
|
+
* actual blend rather than a fixed threshold.
|
|
2465
|
+
*
|
|
2466
|
+
* Every accent ramp has a crossover band where neither `--mk-text` nor
|
|
2467
|
+
* `--mk-text-inverse` reaches 4.5:1 (the cell is mid-luminance). When values
|
|
2468
|
+
* are printed in the cells, a cell in that band is painted at the nearest
|
|
2469
|
+
* intensity just outside it, so its value stays readable; the shift is a few
|
|
2470
|
+
* percent of the ramp and the cell keeps its order relative to its
|
|
2471
|
+
* neighbours. Without a resolved palette (server rendering, tests) a plain
|
|
2472
|
+
* threshold is used.
|
|
2473
|
+
*/
|
|
2474
|
+
function mkHeatmapCellColors(t, palette, showValues) {
|
|
2475
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
2476
|
+
if (!palette)
|
|
2477
|
+
return { intensity: clamped, inverse: clamped >= 0.55, contrast: NaN };
|
|
2478
|
+
const best = pickText(clamped, palette);
|
|
2479
|
+
if (!showValues || best.contrast >= MK_HEATMAP_MIN_CONTRAST)
|
|
2480
|
+
return best;
|
|
2481
|
+
// In the crossover band: step outward (down and up alternately, 1 % at a
|
|
2482
|
+
// time) to the closest intensity at which one of the tokens passes.
|
|
2483
|
+
for (let step = 1; step <= 100; step++) {
|
|
2484
|
+
const delta = step / 100;
|
|
2485
|
+
for (const candidate of [clamped - delta, clamped + delta]) {
|
|
2486
|
+
if (candidate < 0 || candidate > 1)
|
|
2487
|
+
continue;
|
|
2488
|
+
const pick = pickText(candidate, palette);
|
|
2489
|
+
if (pick.contrast >= MK_HEATMAP_MIN_CONTRAST)
|
|
2490
|
+
return pick;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return best;
|
|
2494
|
+
}
|
|
2225
2495
|
/**
|
|
2226
2496
|
* Heatmap — a matrix of cells shaded by value intensity, for correlation
|
|
2227
2497
|
* grids, activity-by-time tables and the like. Rendered as a semantic
|
|
2228
2498
|
* `<table>` (so it is inherently screen-reader friendly) with cell colours
|
|
2229
2499
|
* mixed from `accent` over the surface via `color-mix`, so it tracks the theme.
|
|
2230
2500
|
*
|
|
2501
|
+
* Printed values (`showValues`) pick `--mk-text` or `--mk-text-inverse` per
|
|
2502
|
+
* cell by contrast against that cell's actual colour, in both themes — see
|
|
2503
|
+
* {@link mkHeatmapCellColors}.
|
|
2504
|
+
*
|
|
2231
2505
|
* ```html
|
|
2232
2506
|
* <mk-heatmap
|
|
2233
2507
|
* [xLabels]="hours" [yLabels]="days" [data]="matrix()" showValues />
|
|
2234
2508
|
* ```
|
|
2235
2509
|
*/
|
|
2236
2510
|
class MkHeatmap {
|
|
2511
|
+
doc = inject(DOCUMENT$1);
|
|
2512
|
+
host = inject(ElementRef).nativeElement;
|
|
2237
2513
|
/** Column labels (x axis). */
|
|
2238
2514
|
xLabels = input([], /* @ts-ignore */
|
|
2239
2515
|
...(ngDevMode ? [{ debugName: "xLabels" }] : /* istanbul ignore next */ []));
|
|
@@ -2262,6 +2538,25 @@ class MkHeatmap {
|
|
|
2262
2538
|
/** Accessible summary; generated from the data when omitted. */
|
|
2263
2539
|
label = input('', /* @ts-ignore */
|
|
2264
2540
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
2541
|
+
/**
|
|
2542
|
+
* The accent and the text/surface tokens resolved through the CSSOM, so
|
|
2543
|
+
* each cell can pick a text colour that actually contrasts with its blend.
|
|
2544
|
+
* `null` until resolved (server, jsdom) — the cells then fall back to a
|
|
2545
|
+
* threshold.
|
|
2546
|
+
*/
|
|
2547
|
+
palette = signal(null, /* @ts-ignore */
|
|
2548
|
+
...(ngDevMode ? [{ debugName: "palette" }] : /* istanbul ignore next */ []));
|
|
2549
|
+
/** Bumped when the document theme changes so the palette is re-resolved. */
|
|
2550
|
+
themeTick = signal(0, /* @ts-ignore */
|
|
2551
|
+
...(ngDevMode ? [{ debugName: "themeTick" }] : /* istanbul ignore next */ []));
|
|
2552
|
+
constructor() {
|
|
2553
|
+
afterRenderEffect(() => {
|
|
2554
|
+
this.accent();
|
|
2555
|
+
this.themeTick();
|
|
2556
|
+
untracked(() => this.palette.set(this.resolvePalette()));
|
|
2557
|
+
});
|
|
2558
|
+
this.watchTheme();
|
|
2559
|
+
}
|
|
2265
2560
|
domain = computed(() => {
|
|
2266
2561
|
const flat = this.data()
|
|
2267
2562
|
.flat()
|
|
@@ -2277,26 +2572,32 @@ class MkHeatmap {
|
|
|
2277
2572
|
const span = hi - lo || 1;
|
|
2278
2573
|
return Math.max(0, Math.min(1, (value - lo) / span));
|
|
2279
2574
|
}
|
|
2575
|
+
/** The `color-mix()` expression painting a cell at intensity `t`. */
|
|
2576
|
+
cellBackground(t) {
|
|
2577
|
+
return `color-mix(in srgb, ${this.accent()} ${Math.round(t * 100)}%, var(--mk-surface-2))`;
|
|
2578
|
+
}
|
|
2280
2579
|
rows = computed(() => {
|
|
2281
2580
|
const xs = this.xLabels();
|
|
2282
2581
|
const ys = this.yLabels();
|
|
2283
2582
|
const data = this.data();
|
|
2284
2583
|
const fmt = this.format();
|
|
2584
|
+
const palette = this.palette();
|
|
2585
|
+
const showValues = this.showValues();
|
|
2285
2586
|
return ys.map((yLabel, r) => ({
|
|
2286
2587
|
label: yLabel,
|
|
2287
2588
|
cells: xs.map((xLabel, c) => {
|
|
2288
2589
|
const value = data[r]?.[c] ?? null;
|
|
2289
2590
|
if (value == null) {
|
|
2290
|
-
return { value: null, display: '', xLabel, bg: 'transparent',
|
|
2591
|
+
return { value: null, display: '', xLabel, bg: 'transparent', inverse: false };
|
|
2291
2592
|
}
|
|
2292
|
-
const
|
|
2593
|
+
const colors = mkHeatmapCellColors(this.ratio(value), palette, showValues);
|
|
2293
2594
|
return {
|
|
2294
2595
|
value,
|
|
2295
2596
|
display: fmt(value),
|
|
2296
2597
|
xLabel,
|
|
2297
2598
|
// Mix the accent over the surface; more intense = more accent.
|
|
2298
|
-
bg:
|
|
2299
|
-
|
|
2599
|
+
bg: this.cellBackground(colors.intensity),
|
|
2600
|
+
inverse: colors.inverse,
|
|
2300
2601
|
};
|
|
2301
2602
|
}),
|
|
2302
2603
|
}));
|
|
@@ -2309,7 +2610,7 @@ class MkHeatmap {
|
|
|
2309
2610
|
return {
|
|
2310
2611
|
min: fmt(lo),
|
|
2311
2612
|
max: fmt(hi),
|
|
2312
|
-
swatches: [0, 0.25, 0.5, 0.75, 1].map((t) =>
|
|
2613
|
+
swatches: [0, 0.25, 0.5, 0.75, 1].map((t) => this.cellBackground(t)),
|
|
2313
2614
|
};
|
|
2314
2615
|
}, /* @ts-ignore */
|
|
2315
2616
|
...(ngDevMode ? [{ debugName: "scaleStops" }] : /* istanbul ignore next */ []));
|
|
@@ -2321,8 +2622,62 @@ class MkHeatmap {
|
|
|
2321
2622
|
return `Heatmap, ${rows} rows by ${cols} columns`;
|
|
2322
2623
|
}, /* @ts-ignore */
|
|
2323
2624
|
...(ngDevMode ? [{ debugName: "resolvedLabel" }] : /* istanbul ignore next */ []));
|
|
2625
|
+
/**
|
|
2626
|
+
* Resolve the accent and the tokens to sRGB through a hidden probe: the
|
|
2627
|
+
* probe's computed `color` is what the browser would paint, `var()`s and
|
|
2628
|
+
* `color-mix()` included. `null` when the environment cannot resolve them.
|
|
2629
|
+
*/
|
|
2630
|
+
resolvePalette() {
|
|
2631
|
+
const win = this.doc.defaultView;
|
|
2632
|
+
if (!win?.getComputedStyle)
|
|
2633
|
+
return null;
|
|
2634
|
+
const probe = this.doc.createElement('span');
|
|
2635
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
2636
|
+
probe.style.position = 'absolute';
|
|
2637
|
+
probe.style.visibility = 'hidden';
|
|
2638
|
+
this.host.appendChild(probe);
|
|
2639
|
+
try {
|
|
2640
|
+
const read = (expr) => {
|
|
2641
|
+
probe.style.color = '';
|
|
2642
|
+
probe.style.color = expr;
|
|
2643
|
+
return mkParseRgb(win.getComputedStyle(probe).color);
|
|
2644
|
+
};
|
|
2645
|
+
const accent = read(this.accent());
|
|
2646
|
+
const surface = read('var(--mk-surface-2)');
|
|
2647
|
+
const text = read('var(--mk-text)');
|
|
2648
|
+
const textInverse = read('var(--mk-text-inverse)');
|
|
2649
|
+
return accent && surface && text && textInverse
|
|
2650
|
+
? { accent, surface, text, textInverse }
|
|
2651
|
+
: null;
|
|
2652
|
+
}
|
|
2653
|
+
finally {
|
|
2654
|
+
probe.remove();
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Re-resolve when the theme flips: `data-theme` / class changes on the root
|
|
2659
|
+
* element (MkThemeService and most hand-rolled toggles) and the OS scheme.
|
|
2660
|
+
*/
|
|
2661
|
+
watchTheme() {
|
|
2662
|
+
const win = this.doc.defaultView;
|
|
2663
|
+
const root = this.doc.documentElement;
|
|
2664
|
+
if (!win || !root)
|
|
2665
|
+
return;
|
|
2666
|
+
const bump = () => this.themeTick.update((n) => n + 1);
|
|
2667
|
+
const destroyRef = inject(DestroyRef);
|
|
2668
|
+
if (typeof win.MutationObserver === 'function') {
|
|
2669
|
+
const observer = new win.MutationObserver(bump);
|
|
2670
|
+
observer.observe(root, { attributes: true, attributeFilter: ['data-theme', 'class', 'style'] });
|
|
2671
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
2672
|
+
}
|
|
2673
|
+
const mql = win.matchMedia?.('(prefers-color-scheme: dark)');
|
|
2674
|
+
if (mql?.addEventListener) {
|
|
2675
|
+
mql.addEventListener('change', bump);
|
|
2676
|
+
destroyRef.onDestroy(() => mql.removeEventListener('change', bump));
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2324
2679
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkHeatmap, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2325
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkHeatmap, isStandalone: true, selector: "mk-heatmap", inputs: { xLabels: { classPropertyName: "xLabels", publicName: "xLabels", isSignal: true, isRequired: false, transformFunction: null }, yLabels: { classPropertyName: "yLabels", publicName: "yLabels", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showValues: { classPropertyName: "showValues", publicName: "showValues", isSignal: true, isRequired: false, transformFunction: null }, showScale: { classPropertyName: "showScale", publicName: "showScale", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "resolvedLabel()" }, classAttribute: "mk-chart mk-heatmap" }, ngImport: i0, template: "<!-- Focusable so keyboard users can scroll the overflowing grid (WCAG 2.1.1). -->\n<div\n class=\"mk-chart__frame mk-heatmap__scroll\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedLabel()\"\n>\n <table class=\"mk-heatmap__grid\">\n <caption class=\"mk-visually-hidden\">{{ resolvedLabel() }}</caption>\n <thead>\n <tr>\n <td class=\"mk-heatmap__corner\"></td>\n @for (x of xLabels(); track $index) {\n <th scope=\"col\" class=\"mk-heatmap__col-head\">{{ x }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.label) {\n <tr>\n <th scope=\"row\" class=\"mk-heatmap__row-head\">{{ row.label }}</th>\n @for (cell of row.cells; track $index) {\n <td\n class=\"mk-heatmap__cell\"\n [class.mk-heatmap__cell--
|
|
2680
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkHeatmap, isStandalone: true, selector: "mk-heatmap", inputs: { xLabels: { classPropertyName: "xLabels", publicName: "xLabels", isSignal: true, isRequired: false, transformFunction: null }, yLabels: { classPropertyName: "yLabels", publicName: "yLabels", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showValues: { classPropertyName: "showValues", publicName: "showValues", isSignal: true, isRequired: false, transformFunction: null }, showScale: { classPropertyName: "showScale", publicName: "showScale", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "resolvedLabel()" }, classAttribute: "mk-chart mk-heatmap" }, ngImport: i0, template: "<!-- Focusable so keyboard users can scroll the overflowing grid (WCAG 2.1.1). -->\n<div\n class=\"mk-chart__frame mk-heatmap__scroll\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedLabel()\"\n>\n <table class=\"mk-heatmap__grid\">\n <caption class=\"mk-visually-hidden\">{{ resolvedLabel() }}</caption>\n <thead>\n <tr>\n <td class=\"mk-heatmap__corner\"></td>\n @for (x of xLabels(); track $index) {\n <th scope=\"col\" class=\"mk-heatmap__col-head\">{{ x }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.label) {\n <tr>\n <th scope=\"row\" class=\"mk-heatmap__row-head\">{{ row.label }}</th>\n @for (cell of row.cells; track $index) {\n <td\n class=\"mk-heatmap__cell\"\n [class.mk-heatmap__cell--inverse]=\"cell.inverse\"\n [class.mk-heatmap__cell--empty]=\"cell.value === null\"\n [style.background]=\"cell.bg\"\n [attr.title]=\"\n cell.value === null\n ? null\n : row.label + ' \u00B7 ' + cell.xLabel + ': ' + cell.display\n \"\n >\n @if (cell.value !== null) {\n @if (showValues()) {\n <span class=\"mk-heatmap__value\">{{ cell.display }}</span>\n } @else {\n <!-- Colour must not be the only signal: expose the value to AT. -->\n <span class=\"mk-visually-hidden\">{{ cell.display }}</span>\n }\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n@if (showScale()) {\n <div class=\"mk-heatmap__legend\" aria-hidden=\"true\">\n <span class=\"mk-heatmap__legend-label\">{{ scaleStops().min }}</span>\n <span class=\"mk-heatmap__legend-ramp\">\n @for (sw of scaleStops().swatches; track $index) {\n <span class=\"mk-heatmap__legend-swatch\" [style.background]=\"sw\"></span>\n }\n </span>\n <span class=\"mk-heatmap__legend-label\">{{ scaleStops().max }}</span>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-heatmap__scroll{width:100%;overflow-x:auto}.mk-heatmap__scroll:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-heatmap__grid{border-collapse:separate;border-spacing:3px;font-size:var(--mk-font-size-sm);color:var(--mk-text)}.mk-heatmap__corner{border:0}.mk-heatmap__col-head,.mk-heatmap__row-head{padding:var(--mk-space-1) var(--mk-space-2);font-weight:var(--mk-font-weight-medium);color:var(--mk-text-muted);white-space:nowrap}.mk-heatmap__col-head{text-align:center;vertical-align:bottom}.mk-heatmap__row-head{text-align:end}.mk-heatmap__cell{width:2.25rem;height:2.25rem;padding:0;text-align:center;vertical-align:middle;border-radius:var(--mk-radius-sm);color:var(--mk-text);transition:outline-color var(--mk-duration-fast) var(--mk-ease-standard);outline:2px solid transparent;outline-offset:-2px}.mk-heatmap__cell:hover{outline-color:var(--mk-border-strong)}.mk-heatmap__cell--inverse{color:var(--mk-text-inverse)}.mk-heatmap__cell--empty{outline:none}.mk-heatmap__cell--empty:hover{outline-color:transparent}.mk-heatmap__value{font-size:var(--mk-font-size-xs);font-variant-numeric:tabular-nums}.mk-heatmap__legend{display:flex;align-items:center;gap:var(--mk-space-2);margin-top:var(--mk-space-3);font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}.mk-heatmap__legend-ramp{display:inline-flex;gap:2px}.mk-heatmap__legend-swatch{width:1.1rem;height:.75rem;border-radius:2px;box-shadow:inset 0 0 0 1px var(--mk-border-subtle)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2326
2681
|
}
|
|
2327
2682
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkHeatmap, decorators: [{
|
|
2328
2683
|
type: Component,
|
|
@@ -2330,8 +2685,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
2330
2685
|
class: 'mk-chart mk-heatmap',
|
|
2331
2686
|
role: 'group',
|
|
2332
2687
|
'[attr.aria-label]': 'resolvedLabel()',
|
|
2333
|
-
}, template: "<!-- Focusable so keyboard users can scroll the overflowing grid (WCAG 2.1.1). -->\n<div\n class=\"mk-chart__frame mk-heatmap__scroll\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedLabel()\"\n>\n <table class=\"mk-heatmap__grid\">\n <caption class=\"mk-visually-hidden\">{{ resolvedLabel() }}</caption>\n <thead>\n <tr>\n <td class=\"mk-heatmap__corner\"></td>\n @for (x of xLabels(); track $index) {\n <th scope=\"col\" class=\"mk-heatmap__col-head\">{{ x }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.label) {\n <tr>\n <th scope=\"row\" class=\"mk-heatmap__row-head\">{{ row.label }}</th>\n @for (cell of row.cells; track $index) {\n <td\n class=\"mk-heatmap__cell\"\n [class.mk-heatmap__cell--
|
|
2334
|
-
}], propDecorators: { xLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "xLabels", required: false }] }], yLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "yLabels", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValues", required: false }] }], showScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "showScale", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
2688
|
+
}, template: "<!-- Focusable so keyboard users can scroll the overflowing grid (WCAG 2.1.1). -->\n<div\n class=\"mk-chart__frame mk-heatmap__scroll\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedLabel()\"\n>\n <table class=\"mk-heatmap__grid\">\n <caption class=\"mk-visually-hidden\">{{ resolvedLabel() }}</caption>\n <thead>\n <tr>\n <td class=\"mk-heatmap__corner\"></td>\n @for (x of xLabels(); track $index) {\n <th scope=\"col\" class=\"mk-heatmap__col-head\">{{ x }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.label) {\n <tr>\n <th scope=\"row\" class=\"mk-heatmap__row-head\">{{ row.label }}</th>\n @for (cell of row.cells; track $index) {\n <td\n class=\"mk-heatmap__cell\"\n [class.mk-heatmap__cell--inverse]=\"cell.inverse\"\n [class.mk-heatmap__cell--empty]=\"cell.value === null\"\n [style.background]=\"cell.bg\"\n [attr.title]=\"\n cell.value === null\n ? null\n : row.label + ' \u00B7 ' + cell.xLabel + ': ' + cell.display\n \"\n >\n @if (cell.value !== null) {\n @if (showValues()) {\n <span class=\"mk-heatmap__value\">{{ cell.display }}</span>\n } @else {\n <!-- Colour must not be the only signal: expose the value to AT. -->\n <span class=\"mk-visually-hidden\">{{ cell.display }}</span>\n }\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n@if (showScale()) {\n <div class=\"mk-heatmap__legend\" aria-hidden=\"true\">\n <span class=\"mk-heatmap__legend-label\">{{ scaleStops().min }}</span>\n <span class=\"mk-heatmap__legend-ramp\">\n @for (sw of scaleStops().swatches; track $index) {\n <span class=\"mk-heatmap__legend-swatch\" [style.background]=\"sw\"></span>\n }\n </span>\n <span class=\"mk-heatmap__legend-label\">{{ scaleStops().max }}</span>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-heatmap__scroll{width:100%;overflow-x:auto}.mk-heatmap__scroll:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-heatmap__grid{border-collapse:separate;border-spacing:3px;font-size:var(--mk-font-size-sm);color:var(--mk-text)}.mk-heatmap__corner{border:0}.mk-heatmap__col-head,.mk-heatmap__row-head{padding:var(--mk-space-1) var(--mk-space-2);font-weight:var(--mk-font-weight-medium);color:var(--mk-text-muted);white-space:nowrap}.mk-heatmap__col-head{text-align:center;vertical-align:bottom}.mk-heatmap__row-head{text-align:end}.mk-heatmap__cell{width:2.25rem;height:2.25rem;padding:0;text-align:center;vertical-align:middle;border-radius:var(--mk-radius-sm);color:var(--mk-text);transition:outline-color var(--mk-duration-fast) var(--mk-ease-standard);outline:2px solid transparent;outline-offset:-2px}.mk-heatmap__cell:hover{outline-color:var(--mk-border-strong)}.mk-heatmap__cell--inverse{color:var(--mk-text-inverse)}.mk-heatmap__cell--empty{outline:none}.mk-heatmap__cell--empty:hover{outline-color:transparent}.mk-heatmap__value{font-size:var(--mk-font-size-xs);font-variant-numeric:tabular-nums}.mk-heatmap__legend{display:flex;align-items:center;gap:var(--mk-space-2);margin-top:var(--mk-space-3);font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}.mk-heatmap__legend-ramp{display:inline-flex;gap:2px}.mk-heatmap__legend-swatch{width:1.1rem;height:.75rem;border-radius:2px;box-shadow:inset 0 0 0 1px var(--mk-border-subtle)}\n"] }]
|
|
2689
|
+
}], ctorParameters: () => [], propDecorators: { xLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "xLabels", required: false }] }], yLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "yLabels", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValues", required: false }] }], showScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "showScale", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
2335
2690
|
|
|
2336
2691
|
/** Local midnight for `d` (drops the time-of-day). */
|
|
2337
2692
|
function stripTime(d) {
|
|
@@ -6137,5 +6492,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
6137
6492
|
* Generated bundle index. Do not edit.
|
|
6138
6493
|
*/
|
|
6139
6494
|
|
|
6140
|
-
export { MK_CHART_PALETTE_SIZE, MkAvatar, MkAvatarGroup, MkBadge, MkBarChart, MkCalendarHeatmap, MkCard, MkCardFooter, MkCardHeader, MkCardTitle, MkCarousel, MkCarouselSlide, MkCode, MkCountdown, MkDescItem, MkDescriptionList, MkDiff, MkDivider, MkDonutChart, MkEmptyState, MkFunnelChart, MkGauge, MkHeatmap, MkInlineEdit, MkJsonViewer, MkKanban, MkLineChart, MkList, MkListItem, MkLogViewer, MkMarkdown, MkProfileActions, MkProfileCard, MkProfileMeta, MkProgressBar, MkProgressRing, MkQrCode, MkRadarChart, MkScatterChart, MkSkeleton, MkSkeletonPreset, MkSparkline, MkSpinner, MkStatCard, MkTag, MkTimeline, MkTimelineItem, MkTreemap, MkVirtualScroll, mkArcPath, mkAreaPath, mkBuildJsonTree, mkChartColor, mkComputeDiff, mkDiffStats, mkEncodeQr, mkFormatCompact, mkLinePath, mkLinearScale, mkNiceTicks, mkParseAnsi, mkParseMarkdown, mkRenderMarkdown, mkSplitDuration, mkSquarify, mkStripAnsi };
|
|
6495
|
+
export { MK_CHART_PALETTE_SIZE, MK_HEATMAP_MIN_CONTRAST, MkAvatar, MkAvatarGroup, MkBadge, MkBadgeOverlay, MkBarChart, MkCalendarHeatmap, MkCard, MkCardFooter, MkCardHeader, MkCardTitle, MkCarousel, MkCarouselSlide, MkCode, MkCountdown, MkDescItem, MkDescriptionList, MkDiff, MkDivider, MkDonutChart, MkEmptyState, MkFunnelChart, MkGauge, MkHeatmap, MkInlineEdit, MkJsonViewer, MkKanban, MkLineChart, MkList, MkListItem, MkLogViewer, MkMarkdown, MkProfileActions, MkProfileCard, MkProfileMeta, MkProgressBar, MkProgressRing, MkQrCode, MkRadarChart, MkScatterChart, MkSkeleton, MkSkeletonPreset, MkSparkline, MkSpinner, MkStatCard, MkTag, MkTimeline, MkTimelineItem, MkTreemap, MkVirtualScroll, mkArcPath, mkAreaPath, mkBuildJsonTree, mkChartColor, mkComputeDiff, mkContrastRatio, mkDiffStats, mkEncodeQr, mkFormatCompact, mkHeatmapCellColors, mkLinePath, mkLinearScale, mkMixRgb, mkNiceTicks, mkParseAnsi, mkParseMarkdown, mkParseRgb, mkRelativeLuminance, mkRenderMarkdown, mkSplitDuration, mkSquarify, mkStripAnsi };
|
|
6141
6496
|
//# sourceMappingURL=mk-kit-ui-data.mjs.map
|