@h-k-dev/angular-email-editor 0.0.6 → 0.0.8
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 +2 -1
- package/fesm2022/h-k-dev-angular-email-editor-toggle-button-group.mjs +342 -0
- package/fesm2022/h-k-dev-angular-email-editor-toggle-button-group.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor.mjs +1591 -110
- package/fesm2022/h-k-dev-angular-email-editor.mjs.map +1 -1
- package/package.json +5 -1
- package/types/h-k-dev-angular-email-editor-toggle-button-group.d.ts +172 -0
- package/types/h-k-dev-angular-email-editor.d.ts +502 -23
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ copy of each.
|
|
|
47
47
|
|
|
48
48
|
| Optional peer | Needed for |
|
|
49
49
|
| --- | --- |
|
|
50
|
-
| `@angular/forms` | `/address-input`, `/attachment-chips` (signal-forms controls) |
|
|
50
|
+
| `@angular/forms` | `/address-input`, `/attachment-chips`, `/toggle-button-group` (signal-forms controls) |
|
|
51
51
|
| `@angular/material`, `rxjs` | `/material` (the `mat-form-field` adapter) |
|
|
52
52
|
| `@chenglou/pretext` `^1.0.0` | `/metrics` (word, line and height metrics) |
|
|
53
53
|
|
|
@@ -96,6 +96,7 @@ Every UI piece is its own secondary entry point, so you only bundle what you imp
|
|
|
96
96
|
| `…/address-chip` | One address chip |
|
|
97
97
|
| `…/attachment-chips` | The attachment strip, a `FormValueControl` over the list |
|
|
98
98
|
| `…/attachment-chip` | One attachment chip with upload stages and progress |
|
|
99
|
+
| `…/toggle-button-group` | `<hk-tgb>` with `hkTgbIcon` (round) and `hkTgbText` (pill) items on any element, a sliding thumb, a `FormValueControl` |
|
|
99
100
|
| `…/suggestion-menu` | The `/` and `{{` menus' floating box, with roles and ids |
|
|
100
101
|
| `…/palette` | The colour palette (readable on light and dark) |
|
|
101
102
|
| `…/proposal` | A streamed proposal's state for your panel (accept / discard) |
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, input, booleanAttribute, inject, ElementRef, HostAttributeToken, computed, Directive, forwardRef, Component, model, output, contentChildren, signal, afterRenderEffect, DestroyRef, effect } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
const TOGGLE_BUTTON_GROUP = new InjectionToken('ToggleButtonGroup');
|
|
5
|
+
/**
|
|
6
|
+
* What every choice of an `hk-tgb` is, whichever shape it wears — the
|
|
7
|
+
* group finds its items by this class. Use one of the shapes:
|
|
8
|
+
* `hkTgbIcon` (round) or `hkTgbText` (a pill).
|
|
9
|
+
*
|
|
10
|
+
* An item makes its element — a `<button>`, an `<a>`, anything — a `radio`
|
|
11
|
+
* that carries a `value` and is `aria-checked` while the group holds it.
|
|
12
|
+
* The element and its content stay the host's: a font icon, a `mat-icon`,
|
|
13
|
+
* an SVG, a word. A click picks it, and Space and Enter do too (the
|
|
14
|
+
* group's keys), so a `<div>` works as well as a button. A `<button>`
|
|
15
|
+
* without a `type` gets `type="button"`, so an item in a form never
|
|
16
|
+
* submits it. `aria-disabled` takes it out of the choices and the arrow
|
|
17
|
+
* keys' way — it stays in the track, so the others keep their places —
|
|
18
|
+
* and stops a disabled link from being followed.
|
|
19
|
+
*/
|
|
20
|
+
class ToggleButtonGroupItem {
|
|
21
|
+
/** What the group's value is while this item is picked. */
|
|
22
|
+
value = input.required(/* @ts-ignore */
|
|
23
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
24
|
+
/** This one can't be picked. */
|
|
25
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), alias: 'aria-disabled', transform: booleanAttribute });
|
|
26
|
+
group = inject(TOGGLE_BUTTON_GROUP);
|
|
27
|
+
element = inject(ElementRef).nativeElement;
|
|
28
|
+
/** `button` for a `<button>` the host gave no type; the host's own type
|
|
29
|
+
otherwise, and none on any other element. */
|
|
30
|
+
type = this.element.tagName === 'BUTTON'
|
|
31
|
+
? (inject(new HostAttributeToken('type'), { optional: true }) ?? 'button')
|
|
32
|
+
: null;
|
|
33
|
+
/** The group holds this item's value. */
|
|
34
|
+
checked = computed(() => this.group.value() === this.value(), /* @ts-ignore */
|
|
35
|
+
...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
36
|
+
/** Out of the choices: its own `aria-disabled`, or the group's. */
|
|
37
|
+
blocked = computed(() => this.disabled() || this.group.disabled(), /* @ts-ignore */
|
|
38
|
+
...(ngDevMode ? [{ debugName: "blocked" }] : /* istanbul ignore next */ []));
|
|
39
|
+
focus(options) {
|
|
40
|
+
this.element.focus(options);
|
|
41
|
+
}
|
|
42
|
+
onClick(event) {
|
|
43
|
+
if (this.blocked()) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.group.select(this.value());
|
|
48
|
+
}
|
|
49
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
50
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ToggleButtonGroupItem, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "aria-disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "radio" }, listeners: { "click": "onClick($event)" }, properties: { "attr.type": "type", "attr.aria-checked": "checked()", "attr.aria-disabled": "blocked() || null", "tabIndex": "group.tabStop() === this ? 0 : -1" } }, ngImport: i0 });
|
|
51
|
+
}
|
|
52
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupItem, decorators: [{
|
|
53
|
+
type: Directive,
|
|
54
|
+
args: [{
|
|
55
|
+
host: {
|
|
56
|
+
role: 'radio',
|
|
57
|
+
'[attr.type]': 'type',
|
|
58
|
+
'[attr.aria-checked]': 'checked()',
|
|
59
|
+
'[attr.aria-disabled]': 'blocked() || null',
|
|
60
|
+
'[tabIndex]': 'group.tabStop() === this ? 0 : -1',
|
|
61
|
+
'(click)': 'onClick($event)',
|
|
62
|
+
},
|
|
63
|
+
}]
|
|
64
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-disabled", required: false }] }] } });
|
|
65
|
+
/**
|
|
66
|
+
* A round choice holding an icon: a circle as wide as the track is tall
|
|
67
|
+
* (`--hk-tgb-size`). The icon sits in a box of the item's own
|
|
68
|
+
* (`[data-slot=icon]`, `--hk-tgb-icon-size`), whose font size and colour it
|
|
69
|
+
* takes: a `mat-icon` marked `inline`, a font icon's `<i>`, or an SVG sized
|
|
70
|
+
* `1em` fits with no rule of the host's. It has no words, so name it:
|
|
71
|
+
* `aria-label` (and a `title` for a tooltip).
|
|
72
|
+
*
|
|
73
|
+
* ```html
|
|
74
|
+
* <button hkTgbIcon value="light" aria-label="Light"><mat-icon inline>light_mode</mat-icon></button>
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* A component on the host's element, not a directive: it brings its own
|
|
78
|
+
* encapsulated look (`:host`), which nothing outside could give an element
|
|
79
|
+
* the host wrote.
|
|
80
|
+
*/
|
|
81
|
+
class ToggleButtonGroupIcon extends ToggleButtonGroupItem {
|
|
82
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupIcon, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
83
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: ToggleButtonGroupIcon, isStandalone: true, selector: "[hkTgbIcon]", providers: [
|
|
84
|
+
{ provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupIcon) },
|
|
85
|
+
], exportAs: ["hkTgbIcon"], usesInheritance: true, ngImport: i0, template: "<!-- The icon box: sized by --hk-tgb-icon-size; the host's icon takes its\n font size and colour. -->\n<span data-slot=\"icon\"><ng-content /></span>\n", styles: [":host{position:relative;display:inline-flex;flex:none;align-items:center;justify-content:center;gap:var(--hk-tgb-item-gap, 4px);block-size:var(--hk-tgb-size, 32px);min-inline-size:var(--hk-tgb-size, 32px);margin:0;padding:0;box-sizing:border-box;border:0;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background:transparent;color:var(--hk-tgb-color, var(--mat-sys-on-surface-variant, light-dark(#444746, #c4c7c5)));font-family:var(--hk-tgb-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--hk-tgb-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--hk-tgb-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--hk-tgb-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--hk-tgb-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .2s ease,background-color .15s ease}@media(hover:hover){:host(:hover:not([aria-checked=true],[aria-disabled=true])){color:var(--hk-tgb-hover-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--hk-tgb-hover-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent))}}:host([aria-checked=true]){color:var(--hk-tgb-checked-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));cursor:default}:host(:focus-visible){outline:2px solid var(--hk-tgb-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host([aria-disabled=true]){opacity:.38;cursor:default}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n", ":host{inline-size:var(--hk-tgb-size, 32px)}:host :where([data-slot=icon]){display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hk-tgb-icon-size, 18px);block-size:var(--hk-tgb-icon-size, 18px);font-size:var(--hk-tgb-icon-size, 18px);line-height:1}\n"] });
|
|
86
|
+
}
|
|
87
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupIcon, decorators: [{
|
|
88
|
+
type: Component,
|
|
89
|
+
args: [{ selector: '[hkTgbIcon]', exportAs: 'hkTgbIcon', providers: [
|
|
90
|
+
{ provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupIcon) },
|
|
91
|
+
], template: "<!-- The icon box: sized by --hk-tgb-icon-size; the host's icon takes its\n font size and colour. -->\n<span data-slot=\"icon\"><ng-content /></span>\n", styles: [":host{position:relative;display:inline-flex;flex:none;align-items:center;justify-content:center;gap:var(--hk-tgb-item-gap, 4px);block-size:var(--hk-tgb-size, 32px);min-inline-size:var(--hk-tgb-size, 32px);margin:0;padding:0;box-sizing:border-box;border:0;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background:transparent;color:var(--hk-tgb-color, var(--mat-sys-on-surface-variant, light-dark(#444746, #c4c7c5)));font-family:var(--hk-tgb-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--hk-tgb-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--hk-tgb-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--hk-tgb-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--hk-tgb-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .2s ease,background-color .15s ease}@media(hover:hover){:host(:hover:not([aria-checked=true],[aria-disabled=true])){color:var(--hk-tgb-hover-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--hk-tgb-hover-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent))}}:host([aria-checked=true]){color:var(--hk-tgb-checked-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));cursor:default}:host(:focus-visible){outline:2px solid var(--hk-tgb-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host([aria-disabled=true]){opacity:.38;cursor:default}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n", ":host{inline-size:var(--hk-tgb-size, 32px)}:host :where([data-slot=icon]){display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hk-tgb-icon-size, 18px);block-size:var(--hk-tgb-icon-size, 18px);font-size:var(--hk-tgb-icon-size, 18px);line-height:1}\n"] }]
|
|
92
|
+
}] });
|
|
93
|
+
/**
|
|
94
|
+
* A pill-shaped choice holding words — as wide as its text, padded by
|
|
95
|
+
* `--hk-tgb-padding`.
|
|
96
|
+
*
|
|
97
|
+
* ```html
|
|
98
|
+
* <button hkTgbText value="auto">Auto</button>
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
class ToggleButtonGroupText extends ToggleButtonGroupItem {
|
|
102
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupText, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
103
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: ToggleButtonGroupText, isStandalone: true, selector: "[hkTgbText]", providers: [
|
|
104
|
+
{ provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupText) },
|
|
105
|
+
], exportAs: ["hkTgbText"], usesInheritance: true, ngImport: i0, template: "<ng-content />\n", styles: [":host{position:relative;display:inline-flex;flex:none;align-items:center;justify-content:center;gap:var(--hk-tgb-item-gap, 4px);block-size:var(--hk-tgb-size, 32px);min-inline-size:var(--hk-tgb-size, 32px);margin:0;padding:0;box-sizing:border-box;border:0;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background:transparent;color:var(--hk-tgb-color, var(--mat-sys-on-surface-variant, light-dark(#444746, #c4c7c5)));font-family:var(--hk-tgb-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--hk-tgb-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--hk-tgb-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--hk-tgb-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--hk-tgb-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .2s ease,background-color .15s ease}@media(hover:hover){:host(:hover:not([aria-checked=true],[aria-disabled=true])){color:var(--hk-tgb-hover-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--hk-tgb-hover-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent))}}:host([aria-checked=true]){color:var(--hk-tgb-checked-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));cursor:default}:host(:focus-visible){outline:2px solid var(--hk-tgb-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host([aria-disabled=true]){opacity:.38;cursor:default}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n", ":host{padding-inline:var(--hk-tgb-padding, 12px)}\n"] });
|
|
106
|
+
}
|
|
107
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroupText, decorators: [{
|
|
108
|
+
type: Component,
|
|
109
|
+
args: [{ selector: '[hkTgbText]', exportAs: 'hkTgbText', providers: [
|
|
110
|
+
{ provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupText) },
|
|
111
|
+
], template: "<ng-content />\n", styles: [":host{position:relative;display:inline-flex;flex:none;align-items:center;justify-content:center;gap:var(--hk-tgb-item-gap, 4px);block-size:var(--hk-tgb-size, 32px);min-inline-size:var(--hk-tgb-size, 32px);margin:0;padding:0;box-sizing:border-box;border:0;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background:transparent;color:var(--hk-tgb-color, var(--mat-sys-on-surface-variant, light-dark(#444746, #c4c7c5)));font-family:var(--hk-tgb-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--hk-tgb-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--hk-tgb-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--hk-tgb-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--hk-tgb-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .2s ease,background-color .15s ease}@media(hover:hover){:host(:hover:not([aria-checked=true],[aria-disabled=true])){color:var(--hk-tgb-hover-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--hk-tgb-hover-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent))}}:host([aria-checked=true]){color:var(--hk-tgb-checked-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));cursor:default}:host(:focus-visible){outline:2px solid var(--hk-tgb-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host([aria-disabled=true]){opacity:.38;cursor:default}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n", ":host{padding-inline:var(--hk-tgb-padding, 12px)}\n"] }]
|
|
112
|
+
}] });
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A toggle button group: one of a few choices on a track, the picked one
|
|
116
|
+
* under a thumb that slides — and changes shape — to whichever is picked
|
|
117
|
+
* next. The pricing page's Monthly | Yearly; a theme's ☀ | ☾ | Auto.
|
|
118
|
+
*
|
|
119
|
+
* ```html
|
|
120
|
+
* <hk-tgb aria-label="Appearance" [(value)]="appearance">
|
|
121
|
+
* <button hkTgbIcon value="light" aria-label="Light"><i class="icon-sun"></i></button>
|
|
122
|
+
* <a hkTgbIcon value="dark" aria-label="Dark" href="?theme=dark"><mat-icon inline>dark_mode</mat-icon></a>
|
|
123
|
+
* <button hkTgbText value="auto">Auto</button>
|
|
124
|
+
* </hk-tgb>
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* **The group is the control; the items are the host's own elements.** Any
|
|
128
|
+
* element becomes a choice by wearing one of two attributes — `hkTgbIcon`,
|
|
129
|
+
* a circle around an icon, or `hkTgbText`, a pill around words — and
|
|
130
|
+
* registers by being content, so the host writes the items as it would
|
|
131
|
+
* plain buttons or links. A pick is all a click does: a group inside a
|
|
132
|
+
* menu leaves the menu open.
|
|
133
|
+
*
|
|
134
|
+
* **A signal-forms control, optionally.** It implements `FormValueControl`:
|
|
135
|
+
* bind a field with `[formField]="settings.theme"` and the directive drives
|
|
136
|
+
* `value` both ways, pushes `disabled`, `readonly`, `required`, `invalid`
|
|
137
|
+
* and `touched` in, and hears `touch` when focus leaves the group. Without
|
|
138
|
+
* a form, `[(value)]` alone is the whole contract. The value is whatever
|
|
139
|
+
* the items carry — strings, an enum, objects — matched by identity; a
|
|
140
|
+
* value no item carries picks none.
|
|
141
|
+
*
|
|
142
|
+
* **Radio semantics.** The group is a `radiogroup`, each item a `radio`
|
|
143
|
+
* with `aria-checked`, and one tab stop: the picked item (the first
|
|
144
|
+
* enabled one while none is). The arrow keys of the group's `orientation`
|
|
145
|
+
* move the pick — Left and Right across, mirrored right to left; Up and
|
|
146
|
+
* Down down a vertical group — Home and End go to the ends, Space and
|
|
147
|
+
* Enter pick in place. The arrows across the other way are left alone for
|
|
148
|
+
* whatever the group sits in: a vertical menu moves on with them. A key
|
|
149
|
+
* the group acted on is spent: it does not travel on to a menu or toolbar
|
|
150
|
+
* around it, whose Space or Enter would act a second time.
|
|
151
|
+
*
|
|
152
|
+
* **The thumb follows the picked item's box.** Items differ in size — a
|
|
153
|
+
* circle, a pill as wide as its words — so the group reads the picked
|
|
154
|
+
* item's box once per pick, and again only when an item resizes (a font
|
|
155
|
+
* arriving, a language switched); the thumb slides there by CSS, its size
|
|
156
|
+
* and corner easing from one shape to the other, and stands still for
|
|
157
|
+
* reduced motion. It takes its first place without sliding.
|
|
158
|
+
*
|
|
159
|
+
* **Each part styles itself, encapsulated.** The group draws the track
|
|
160
|
+
* and the thumb; each item is a component on the host's element and brings
|
|
161
|
+
* its shape with it, so nothing reaches past an encapsulation boundary.
|
|
162
|
+
* Every look is a `--hk-tgb-*` token, defaulting to Material's system
|
|
163
|
+
* tokens, then a light-dark() pair.
|
|
164
|
+
*
|
|
165
|
+
* **State on the host:** `aria-disabled`, `aria-readonly`,
|
|
166
|
+
* `aria-required`, `aria-invalid` (invalid *and* touched, so nothing
|
|
167
|
+
* shouts early), `data-orientation`, and `data-empty` while nothing is
|
|
168
|
+
* picked. No Material, no icon font, no headless library.
|
|
169
|
+
*/
|
|
170
|
+
class ToggleButtonGroup {
|
|
171
|
+
/** The picked item's value. Two-way; the form's value. */
|
|
172
|
+
value = model.required(/* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
174
|
+
/** Which way the items line up, and so which arrow keys are the
|
|
175
|
+
group's. */
|
|
176
|
+
orientation = input('horizontal', /* @ts-ignore */
|
|
177
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
178
|
+
// ── the form contract: pushed in by the field directive ───────────────
|
|
179
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
180
|
+
/** The pick holds: the items can be reached and read, not changed. */
|
|
181
|
+
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
182
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
183
|
+
invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
184
|
+
touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
185
|
+
/** Focus left the group. The directive marks the field touched on it. */
|
|
186
|
+
touch = output();
|
|
187
|
+
/** The items, in the order they stand. */
|
|
188
|
+
items = contentChildren(ToggleButtonGroupItem, { ...(ngDevMode ? { debugName: "items" } : /* istanbul ignore next */ {}), descendants: true });
|
|
189
|
+
/** The item the group's value picks, if any. */
|
|
190
|
+
picked = computed(() => this.items().find((item) => item.checked()), /* @ts-ignore */
|
|
191
|
+
...(ngDevMode ? [{ debugName: "picked" }] : /* istanbul ignore next */ []));
|
|
192
|
+
/** The one item Tab lands on: the picked one, else the first that can be
|
|
193
|
+
picked — none while every one is disabled. */
|
|
194
|
+
tabStop = computed(() => {
|
|
195
|
+
const enabled = this.items().filter((item) => !item.blocked());
|
|
196
|
+
return enabled.find((item) => item.checked()) ?? enabled[0];
|
|
197
|
+
}, /* @ts-ignore */
|
|
198
|
+
...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
|
|
199
|
+
#host = inject(ElementRef).nativeElement;
|
|
200
|
+
/** Bumped when an item or the group resizes, for the thumb to read the
|
|
201
|
+
picked box again. */
|
|
202
|
+
#resized = signal(0, /* @ts-ignore */
|
|
203
|
+
...(ngDevMode ? [{ debugName: "#resized" }] : /* istanbul ignore next */ []));
|
|
204
|
+
constructor() {
|
|
205
|
+
this.#watchSizes();
|
|
206
|
+
afterRenderEffect({
|
|
207
|
+
earlyRead: () => {
|
|
208
|
+
this.#resized();
|
|
209
|
+
const element = this.picked()?.element;
|
|
210
|
+
return element
|
|
211
|
+
? {
|
|
212
|
+
x: element.offsetLeft,
|
|
213
|
+
y: element.offsetTop,
|
|
214
|
+
width: element.offsetWidth,
|
|
215
|
+
height: element.offsetHeight,
|
|
216
|
+
}
|
|
217
|
+
: null;
|
|
218
|
+
},
|
|
219
|
+
write: (box) => this.#place(box()),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/** Picks a value — an item's click, a key. A no-op while disabled or
|
|
223
|
+
read-only. */
|
|
224
|
+
select(value) {
|
|
225
|
+
if (this.disabled() || this.readonly())
|
|
226
|
+
return;
|
|
227
|
+
this.value.set(value);
|
|
228
|
+
}
|
|
229
|
+
/** Puts focus on the group's tab stop. */
|
|
230
|
+
focus(options) {
|
|
231
|
+
this.tabStop()?.focus(options);
|
|
232
|
+
}
|
|
233
|
+
/** Stands the thumb on the picked box. The first place is taken without
|
|
234
|
+
sliding: `data-placed` turns the motion on only after it. Nothing
|
|
235
|
+
picked keeps the last place, so the thumb fades out where it stood. */
|
|
236
|
+
#place(box) {
|
|
237
|
+
if (!box)
|
|
238
|
+
return;
|
|
239
|
+
const style = this.#host.style;
|
|
240
|
+
style.setProperty('--hk-tgb-thumb-x', `${box.x}px`);
|
|
241
|
+
style.setProperty('--hk-tgb-thumb-y', `${box.y}px`);
|
|
242
|
+
style.setProperty('--hk-tgb-thumb-width', `${box.width}px`);
|
|
243
|
+
style.setProperty('--hk-tgb-thumb-height', `${box.height}px`);
|
|
244
|
+
if (!this.#host.hasAttribute('data-placed') && typeof requestAnimationFrame === 'function') {
|
|
245
|
+
requestAnimationFrame(() => this.#host.setAttribute('data-placed', ''));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Watches the group and its items for size changes — the only thing
|
|
249
|
+
besides a pick that moves the picked box. */
|
|
250
|
+
#watchSizes() {
|
|
251
|
+
if (typeof ResizeObserver === 'undefined')
|
|
252
|
+
return;
|
|
253
|
+
const observer = new ResizeObserver(() => this.#resized.update((n) => n + 1));
|
|
254
|
+
inject(DestroyRef).onDestroy(() => observer.disconnect());
|
|
255
|
+
effect((onCleanup) => {
|
|
256
|
+
observer.observe(this.#host);
|
|
257
|
+
const items = this.items().map((item) => item.element);
|
|
258
|
+
for (const element of items)
|
|
259
|
+
observer.observe(element);
|
|
260
|
+
onCleanup(() => {
|
|
261
|
+
observer.unobserve(this.#host);
|
|
262
|
+
for (const element of items)
|
|
263
|
+
observer.unobserve(element);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
onKeydown(event) {
|
|
268
|
+
const items = this.items().filter((item) => !item.blocked());
|
|
269
|
+
const current = items.findIndex((item) => item.element === event.target);
|
|
270
|
+
if (current < 0)
|
|
271
|
+
return;
|
|
272
|
+
const target = this.#target(event, current, items.length);
|
|
273
|
+
if (target === undefined)
|
|
274
|
+
return;
|
|
275
|
+
event.preventDefault();
|
|
276
|
+
event.stopPropagation();
|
|
277
|
+
const item = items[target];
|
|
278
|
+
item.focus();
|
|
279
|
+
this.select(item.value());
|
|
280
|
+
}
|
|
281
|
+
/** The index a key moves to, or `undefined` for a key that is not the
|
|
282
|
+
group's. Space and Enter pick the item in place — a button's own
|
|
283
|
+
click would too, but a menu around it hears the keydown first. */
|
|
284
|
+
#target(event, current, count) {
|
|
285
|
+
const vertical = this.orientation() === 'vertical';
|
|
286
|
+
const rtl = !vertical && getComputedStyle(this.#host).direction === 'rtl';
|
|
287
|
+
const step = (by) => (current + by + count) % count;
|
|
288
|
+
switch (event.key) {
|
|
289
|
+
case ' ':
|
|
290
|
+
case 'Enter':
|
|
291
|
+
return current;
|
|
292
|
+
case 'Home':
|
|
293
|
+
return 0;
|
|
294
|
+
case 'End':
|
|
295
|
+
return count - 1;
|
|
296
|
+
case 'ArrowLeft':
|
|
297
|
+
return vertical ? undefined : step(rtl ? 1 : -1);
|
|
298
|
+
case 'ArrowRight':
|
|
299
|
+
return vertical ? undefined : step(rtl ? -1 : 1);
|
|
300
|
+
case 'ArrowUp':
|
|
301
|
+
return vertical ? step(-1) : undefined;
|
|
302
|
+
case 'ArrowDown':
|
|
303
|
+
return vertical ? step(1) : undefined;
|
|
304
|
+
default:
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
onFocusout(event) {
|
|
309
|
+
if (!this.#host.contains(event.relatedTarget))
|
|
310
|
+
this.touch.emit();
|
|
311
|
+
}
|
|
312
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
313
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.6", type: ToggleButtonGroup, isStandalone: true, selector: "hk-tgb", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "radiogroup" }, listeners: { "keydown": "onKeydown($event)", "focusout": "onFocusout($event)" }, properties: { "attr.aria-orientation": "orientation()", "attr.data-orientation": "orientation()", "attr.aria-disabled": "disabled() || null", "attr.aria-readonly": "readonly() || null", "attr.aria-required": "required() || null", "attr.aria-invalid": "(invalid() && touched()) || null", "attr.data-empty": "!picked() || null" } }, providers: [{ provide: TOGGLE_BUTTON_GROUP, useExisting: forwardRef(() => ToggleButtonGroup) }], queries: [{ propertyName: "items", predicate: ToggleButtonGroupItem, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- The thumb: painted under the picked item, never laid out. The group\n places it from that item's box (`--hk-tgb-thumb-*`). -->\n<span data-slot=\"thumb\" aria-hidden=\"true\"></span>\n<ng-content />\n", styles: [":host{position:relative;isolation:isolate;display:inline-flex;align-items:center;gap:var(--hk-tgb-gap, 2px);padding:var(--hk-tgb-inset, 2px);box-sizing:border-box;border-radius:var(--hk-tgb-corner, var(--mat-sys-corner-full, 9999px));background-color:var(--hk-tgb-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent));vertical-align:middle}:host([data-orientation=vertical]){flex-direction:column;align-items:stretch}:host :where([data-slot=thumb]){position:absolute;z-index:-1;top:0;left:0;width:var(--hk-tgb-thumb-width, 0);height:var(--hk-tgb-thumb-height, 0);box-sizing:border-box;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background-color:var(--hk-tgb-thumb, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) calc(var(--mat-sys-hover-state-layer-opacity, .08) * 100%), transparent));box-shadow:var(--hk-tgb-thumb-shadow, none);translate:var(--hk-tgb-thumb-x, 0) var(--hk-tgb-thumb-y, 0);pointer-events:none;transition:opacity .15s ease}:host([data-placed]) :where([data-slot=thumb]){--_duration: var(--hk-tgb-duration, .32s);--_easing: var(--hk-tgb-easing, cubic-bezier(.3, 1.2, .5, 1));transition:translate var(--_duration) var(--_easing),width var(--_duration) var(--_easing),height var(--_duration) var(--_easing),opacity .15s ease}:host([data-empty]) :where([data-slot=thumb]){opacity:0}:host([aria-disabled=true]) :where([data-slot=thumb]){opacity:.38}@media(prefers-reduced-motion:reduce){:host :where([data-slot=thumb]){transition:none}}\n"] });
|
|
314
|
+
}
|
|
315
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ToggleButtonGroup, decorators: [{
|
|
316
|
+
type: Component,
|
|
317
|
+
args: [{ selector: 'hk-tgb', providers: [{ provide: TOGGLE_BUTTON_GROUP, useExisting: forwardRef(() => ToggleButtonGroup) }], host: {
|
|
318
|
+
role: 'radiogroup',
|
|
319
|
+
'[attr.aria-orientation]': 'orientation()',
|
|
320
|
+
'[attr.data-orientation]': 'orientation()',
|
|
321
|
+
'[attr.aria-disabled]': 'disabled() || null',
|
|
322
|
+
'[attr.aria-readonly]': 'readonly() || null',
|
|
323
|
+
'[attr.aria-required]': 'required() || null',
|
|
324
|
+
'[attr.aria-invalid]': '(invalid() && touched()) || null',
|
|
325
|
+
'[attr.data-empty]': '!picked() || null',
|
|
326
|
+
'(keydown)': 'onKeydown($event)',
|
|
327
|
+
'(focusout)': 'onFocusout($event)',
|
|
328
|
+
}, template: "<!-- The thumb: painted under the picked item, never laid out. The group\n places it from that item's box (`--hk-tgb-thumb-*`). -->\n<span data-slot=\"thumb\" aria-hidden=\"true\"></span>\n<ng-content />\n", styles: [":host{position:relative;isolation:isolate;display:inline-flex;align-items:center;gap:var(--hk-tgb-gap, 2px);padding:var(--hk-tgb-inset, 2px);box-sizing:border-box;border-radius:var(--hk-tgb-corner, var(--mat-sys-corner-full, 9999px));background-color:var(--hk-tgb-background, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) 4%, transparent));vertical-align:middle}:host([data-orientation=vertical]){flex-direction:column;align-items:stretch}:host :where([data-slot=thumb]){position:absolute;z-index:-1;top:0;left:0;width:var(--hk-tgb-thumb-width, 0);height:var(--hk-tgb-thumb-height, 0);box-sizing:border-box;border-radius:var(--hk-tgb-thumb-corner, var(--mat-sys-corner-full, 9999px));background-color:var(--hk-tgb-thumb, color-mix(in oklch, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)) calc(var(--mat-sys-hover-state-layer-opacity, .08) * 100%), transparent));box-shadow:var(--hk-tgb-thumb-shadow, none);translate:var(--hk-tgb-thumb-x, 0) var(--hk-tgb-thumb-y, 0);pointer-events:none;transition:opacity .15s ease}:host([data-placed]) :where([data-slot=thumb]){--_duration: var(--hk-tgb-duration, .32s);--_easing: var(--hk-tgb-easing, cubic-bezier(.3, 1.2, .5, 1));transition:translate var(--_duration) var(--_easing),width var(--_duration) var(--_easing),height var(--_duration) var(--_easing),opacity .15s ease}:host([data-empty]) :where([data-slot=thumb]){opacity:0}:host([aria-disabled=true]) :where([data-slot=thumb]){opacity:.38}@media(prefers-reduced-motion:reduce){:host :where([data-slot=thumb]){transition:none}}\n"] }]
|
|
329
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ToggleButtonGroupItem), { ...{
|
|
330
|
+
descendants: true,
|
|
331
|
+
}, isSignal: true }] }] } });
|
|
332
|
+
|
|
333
|
+
/*
|
|
334
|
+
* Public API Surface of @h-k-dev/angular-email-editor/toggle-button-group
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Generated bundle index. Do not edit.
|
|
339
|
+
*/
|
|
340
|
+
|
|
341
|
+
export { TOGGLE_BUTTON_GROUP, ToggleButtonGroup, ToggleButtonGroupIcon, ToggleButtonGroupItem, ToggleButtonGroupText };
|
|
342
|
+
//# sourceMappingURL=h-k-dev-angular-email-editor-toggle-button-group.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h-k-dev-angular-email-editor-toggle-button-group.mjs","sources":["../../../projects/angular-email-editor/toggle-button-group/toggle-button-group-item.ts","../../../projects/angular-email-editor/toggle-button-group/toggle-button-group-icon.html","../../../projects/angular-email-editor/toggle-button-group/toggle-button-group-text.html","../../../projects/angular-email-editor/toggle-button-group/toggle-button-group.ts","../../../projects/angular-email-editor/toggle-button-group/toggle-button-group.html","../../../projects/angular-email-editor/toggle-button-group/public-api.ts","../../../projects/angular-email-editor/toggle-button-group/h-k-dev-angular-email-editor-toggle-button-group.ts"],"sourcesContent":["import {\n Component,\n Directive,\n ElementRef,\n HostAttributeToken,\n InjectionToken,\n Signal,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n} from '@angular/core';\n\n/** What an item needs of the group it stands in — `hk-tgb` provides it, so\n an item finds its group without importing it. */\nexport interface ToggleButtonGroupHost<T = unknown> {\n readonly value: Signal<T>;\n readonly disabled: Signal<boolean>;\n readonly tabStop: Signal<ToggleButtonGroupItem<T> | undefined>;\n select(value: T): void;\n}\n\nexport const TOGGLE_BUTTON_GROUP = new InjectionToken<ToggleButtonGroupHost>('ToggleButtonGroup');\n\n/**\n * What every choice of an `hk-tgb` is, whichever shape it wears — the\n * group finds its items by this class. Use one of the shapes:\n * `hkTgbIcon` (round) or `hkTgbText` (a pill).\n *\n * An item makes its element — a `<button>`, an `<a>`, anything — a `radio`\n * that carries a `value` and is `aria-checked` while the group holds it.\n * The element and its content stay the host's: a font icon, a `mat-icon`,\n * an SVG, a word. A click picks it, and Space and Enter do too (the\n * group's keys), so a `<div>` works as well as a button. A `<button>`\n * without a `type` gets `type=\"button\"`, so an item in a form never\n * submits it. `aria-disabled` takes it out of the choices and the arrow\n * keys' way — it stays in the track, so the others keep their places —\n * and stops a disabled link from being followed.\n */\n@Directive({\n host: {\n role: 'radio',\n '[attr.type]': 'type',\n '[attr.aria-checked]': 'checked()',\n '[attr.aria-disabled]': 'blocked() || null',\n '[tabIndex]': 'group.tabStop() === this ? 0 : -1',\n '(click)': 'onClick($event)',\n },\n})\nexport abstract class ToggleButtonGroupItem<T = unknown> {\n /** What the group's value is while this item is picked. */\n readonly value = input.required<T>();\n\n /** This one can't be picked. */\n readonly disabled = input(false, { alias: 'aria-disabled', transform: booleanAttribute });\n\n protected readonly group = inject(TOGGLE_BUTTON_GROUP) as ToggleButtonGroupHost<T>;\n\n readonly element = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** `button` for a `<button>` the host gave no type; the host's own type\n otherwise, and none on any other element. */\n protected readonly type =\n this.element.tagName === 'BUTTON'\n ? (inject(new HostAttributeToken('type'), { optional: true }) ?? 'button')\n : null;\n\n /** The group holds this item's value. */\n readonly checked = computed(() => this.group.value() === this.value());\n\n /** Out of the choices: its own `aria-disabled`, or the group's. */\n readonly blocked = computed(() => this.disabled() || this.group.disabled());\n\n focus(options?: FocusOptions): void {\n this.element.focus(options);\n }\n\n protected onClick(event: MouseEvent): void {\n if (this.blocked()) {\n event.preventDefault();\n return;\n }\n this.group.select(this.value());\n }\n}\n\n/**\n * A round choice holding an icon: a circle as wide as the track is tall\n * (`--hk-tgb-size`). The icon sits in a box of the item's own\n * (`[data-slot=icon]`, `--hk-tgb-icon-size`), whose font size and colour it\n * takes: a `mat-icon` marked `inline`, a font icon's `<i>`, or an SVG sized\n * `1em` fits with no rule of the host's. It has no words, so name it:\n * `aria-label` (and a `title` for a tooltip).\n *\n * ```html\n * <button hkTgbIcon value=\"light\" aria-label=\"Light\"><mat-icon inline>light_mode</mat-icon></button>\n * ```\n *\n * A component on the host's element, not a directive: it brings its own\n * encapsulated look (`:host`), which nothing outside could give an element\n * the host wrote.\n */\n@Component({\n selector: '[hkTgbIcon]',\n exportAs: 'hkTgbIcon',\n templateUrl: './toggle-button-group-icon.html',\n styleUrls: ['./toggle-button-group-item.scss', './toggle-button-group-icon.scss'],\n providers: [\n { provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupIcon) },\n ],\n})\nexport class ToggleButtonGroupIcon<T = unknown> extends ToggleButtonGroupItem<T> {}\n\n/**\n * A pill-shaped choice holding words — as wide as its text, padded by\n * `--hk-tgb-padding`.\n *\n * ```html\n * <button hkTgbText value=\"auto\">Auto</button>\n * ```\n */\n@Component({\n selector: '[hkTgbText]',\n exportAs: 'hkTgbText',\n templateUrl: './toggle-button-group-text.html',\n styleUrls: ['./toggle-button-group-item.scss', './toggle-button-group-text.scss'],\n providers: [\n { provide: ToggleButtonGroupItem, useExisting: forwardRef(() => ToggleButtonGroupText) },\n ],\n})\nexport class ToggleButtonGroupText<T = unknown> extends ToggleButtonGroupItem<T> {}\n","<!-- The icon box: sized by --hk-tgb-icon-size; the host's icon takes its\n font size and colour. -->\n<span data-slot=\"icon\"><ng-content /></span>\n","<ng-content />\n","import {\n Component,\n DestroyRef,\n ElementRef,\n afterRenderEffect,\n booleanAttribute,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\nimport { TOGGLE_BUTTON_GROUP, ToggleButtonGroupItem } from './toggle-button-group-item';\n\n/** The picked item's box, in the group's own coordinates. */\ninterface ThumbBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * A toggle button group: one of a few choices on a track, the picked one\n * under a thumb that slides — and changes shape — to whichever is picked\n * next. The pricing page's Monthly | Yearly; a theme's ☀ | ☾ | Auto.\n *\n * ```html\n * <hk-tgb aria-label=\"Appearance\" [(value)]=\"appearance\">\n * <button hkTgbIcon value=\"light\" aria-label=\"Light\"><i class=\"icon-sun\"></i></button>\n * <a hkTgbIcon value=\"dark\" aria-label=\"Dark\" href=\"?theme=dark\"><mat-icon inline>dark_mode</mat-icon></a>\n * <button hkTgbText value=\"auto\">Auto</button>\n * </hk-tgb>\n * ```\n *\n * **The group is the control; the items are the host's own elements.** Any\n * element becomes a choice by wearing one of two attributes — `hkTgbIcon`,\n * a circle around an icon, or `hkTgbText`, a pill around words — and\n * registers by being content, so the host writes the items as it would\n * plain buttons or links. A pick is all a click does: a group inside a\n * menu leaves the menu open.\n *\n * **A signal-forms control, optionally.** It implements `FormValueControl`:\n * bind a field with `[formField]=\"settings.theme\"` and the directive drives\n * `value` both ways, pushes `disabled`, `readonly`, `required`, `invalid`\n * and `touched` in, and hears `touch` when focus leaves the group. Without\n * a form, `[(value)]` alone is the whole contract. The value is whatever\n * the items carry — strings, an enum, objects — matched by identity; a\n * value no item carries picks none.\n *\n * **Radio semantics.** The group is a `radiogroup`, each item a `radio`\n * with `aria-checked`, and one tab stop: the picked item (the first\n * enabled one while none is). The arrow keys of the group's `orientation`\n * move the pick — Left and Right across, mirrored right to left; Up and\n * Down down a vertical group — Home and End go to the ends, Space and\n * Enter pick in place. The arrows across the other way are left alone for\n * whatever the group sits in: a vertical menu moves on with them. A key\n * the group acted on is spent: it does not travel on to a menu or toolbar\n * around it, whose Space or Enter would act a second time.\n *\n * **The thumb follows the picked item's box.** Items differ in size — a\n * circle, a pill as wide as its words — so the group reads the picked\n * item's box once per pick, and again only when an item resizes (a font\n * arriving, a language switched); the thumb slides there by CSS, its size\n * and corner easing from one shape to the other, and stands still for\n * reduced motion. It takes its first place without sliding.\n *\n * **Each part styles itself, encapsulated.** The group draws the track\n * and the thumb; each item is a component on the host's element and brings\n * its shape with it, so nothing reaches past an encapsulation boundary.\n * Every look is a `--hk-tgb-*` token, defaulting to Material's system\n * tokens, then a light-dark() pair.\n *\n * **State on the host:** `aria-disabled`, `aria-readonly`,\n * `aria-required`, `aria-invalid` (invalid *and* touched, so nothing\n * shouts early), `data-orientation`, and `data-empty` while nothing is\n * picked. No Material, no icon font, no headless library.\n */\n@Component({\n selector: 'hk-tgb',\n templateUrl: './toggle-button-group.html',\n styleUrl: './toggle-button-group.scss',\n providers: [{ provide: TOGGLE_BUTTON_GROUP, useExisting: forwardRef(() => ToggleButtonGroup) }],\n host: {\n role: 'radiogroup',\n '[attr.aria-orientation]': 'orientation()',\n '[attr.data-orientation]': 'orientation()',\n '[attr.aria-disabled]': 'disabled() || null',\n '[attr.aria-readonly]': 'readonly() || null',\n '[attr.aria-required]': 'required() || null',\n '[attr.aria-invalid]': '(invalid() && touched()) || null',\n '[attr.data-empty]': '!picked() || null',\n '(keydown)': 'onKeydown($event)',\n '(focusout)': 'onFocusout($event)',\n },\n})\nexport class ToggleButtonGroup<T = unknown> implements FormValueControl<T> {\n /** The picked item's value. Two-way; the form's value. */\n readonly value = model.required<T>();\n\n /** Which way the items line up, and so which arrow keys are the\n group's. */\n readonly orientation = input<'horizontal' | 'vertical'>('horizontal');\n\n // ── the form contract: pushed in by the field directive ───────────────\n\n readonly disabled = input(false, { transform: booleanAttribute });\n /** The pick holds: the items can be reached and read, not changed. */\n readonly readonly = input(false, { transform: booleanAttribute });\n readonly required = input(false, { transform: booleanAttribute });\n readonly invalid = input(false, { transform: booleanAttribute });\n readonly touched = input(false, { transform: booleanAttribute });\n\n /** Focus left the group. The directive marks the field touched on it. */\n readonly touch = output<void>();\n\n /** The items, in the order they stand. */\n protected readonly items = contentChildren<ToggleButtonGroupItem<T>>(ToggleButtonGroupItem, {\n descendants: true,\n });\n\n /** The item the group's value picks, if any. */\n protected readonly picked = computed(() => this.items().find((item) => item.checked()));\n\n /** The one item Tab lands on: the picked one, else the first that can be\n picked — none while every one is disabled. */\n readonly tabStop = computed(() => {\n const enabled = this.items().filter((item) => !item.blocked());\n return enabled.find((item) => item.checked()) ?? enabled[0];\n });\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Bumped when an item or the group resizes, for the thumb to read the\n picked box again. */\n readonly #resized = signal(0);\n\n constructor() {\n this.#watchSizes();\n afterRenderEffect({\n earlyRead: (): ThumbBox | null => {\n this.#resized();\n const element = this.picked()?.element;\n return element\n ? {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: element.offsetWidth,\n height: element.offsetHeight,\n }\n : null;\n },\n write: (box) => this.#place(box()),\n });\n }\n\n /** Picks a value — an item's click, a key. A no-op while disabled or\n read-only. */\n select(value: T): void {\n if (this.disabled() || this.readonly()) return;\n this.value.set(value);\n }\n\n /** Puts focus on the group's tab stop. */\n focus(options?: FocusOptions): void {\n this.tabStop()?.focus(options);\n }\n\n /** Stands the thumb on the picked box. The first place is taken without\n sliding: `data-placed` turns the motion on only after it. Nothing\n picked keeps the last place, so the thumb fades out where it stood. */\n #place(box: ThumbBox | null): void {\n if (!box) return;\n const style = this.#host.style;\n style.setProperty('--hk-tgb-thumb-x', `${box.x}px`);\n style.setProperty('--hk-tgb-thumb-y', `${box.y}px`);\n style.setProperty('--hk-tgb-thumb-width', `${box.width}px`);\n style.setProperty('--hk-tgb-thumb-height', `${box.height}px`);\n if (!this.#host.hasAttribute('data-placed') && typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this.#host.setAttribute('data-placed', ''));\n }\n }\n\n /** Watches the group and its items for size changes — the only thing\n besides a pick that moves the picked box. */\n #watchSizes(): void {\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => this.#resized.update((n) => n + 1));\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n effect((onCleanup) => {\n observer.observe(this.#host);\n const items = this.items().map((item) => item.element);\n for (const element of items) observer.observe(element);\n onCleanup(() => {\n observer.unobserve(this.#host);\n for (const element of items) observer.unobserve(element);\n });\n });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n const items = this.items().filter((item) => !item.blocked());\n const current = items.findIndex((item) => item.element === event.target);\n if (current < 0) return;\n\n const target = this.#target(event, current, items.length);\n if (target === undefined) return;\n event.preventDefault();\n event.stopPropagation();\n const item = items[target];\n item.focus();\n this.select(item.value());\n }\n\n /** The index a key moves to, or `undefined` for a key that is not the\n group's. Space and Enter pick the item in place — a button's own\n click would too, but a menu around it hears the keydown first. */\n #target(event: KeyboardEvent, current: number, count: number): number | undefined {\n const vertical = this.orientation() === 'vertical';\n const rtl = !vertical && getComputedStyle(this.#host).direction === 'rtl';\n const step = (by: number) => (current + by + count) % count;\n switch (event.key) {\n case ' ':\n case 'Enter':\n return current;\n case 'Home':\n return 0;\n case 'End':\n return count - 1;\n case 'ArrowLeft':\n return vertical ? undefined : step(rtl ? 1 : -1);\n case 'ArrowRight':\n return vertical ? undefined : step(rtl ? -1 : 1);\n case 'ArrowUp':\n return vertical ? step(-1) : undefined;\n case 'ArrowDown':\n return vertical ? step(1) : undefined;\n default:\n return undefined;\n }\n }\n\n protected onFocusout(event: FocusEvent): void {\n if (!this.#host.contains(event.relatedTarget as Node | null)) this.touch.emit();\n }\n}\n","<!-- The thumb: painted under the picked item, never laid out. The group\n places it from that item's box (`--hk-tgb-thumb-*`). -->\n<span data-slot=\"thumb\" aria-hidden=\"true\"></span>\n<ng-content />\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/toggle-button-group\n */\n\nexport * from './toggle-button-group';\nexport * from './toggle-button-group-item';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;MAuBa,mBAAmB,GAAG,IAAI,cAAc,CAAwB,mBAAmB;AAEhG;;;;;;;;;;;;;;AAcG;MAWmB,qBAAqB,CAAA;;IAEhC,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAK;;AAG3B,IAAA,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,UAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,gBAAgB,GAAG;AAEtE,IAAA,KAAK,GAAG,MAAM,CAAC,mBAAmB,CAA6B;AAEzE,IAAA,OAAO,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;AAE5E;AACgD;AAC7B,IAAA,IAAI,GACrB,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK;AACvB,WAAG,MAAM,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,QAAQ;UACvE,IAAI;;AAGD,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE;gFAAC;;AAG7D,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;gFAAC;AAE3E,IAAA,KAAK,CAAC,OAAsB,EAAA;AAC1B,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IAC7B;AAEU,IAAA,OAAO,CAAC,KAAiB,EAAA;AACjC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE;YAClB,KAAK,CAAC,cAAc,EAAE;YACtB;QACF;QACA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACjC;uGAlCoB,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAArB,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,OAAA,EAAA,EAAA,SAAA,EAAA,EAAA,OAAA,EAAA,iBAAA,EAAA,EAAA,UAAA,EAAA,EAAA,WAAA,EAAA,MAAA,EAAA,mBAAA,EAAA,WAAA,EAAA,oBAAA,EAAA,mBAAA,EAAA,UAAA,EAAA,mCAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAArB,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBAV1C,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,IAAI,EAAE;AACJ,wBAAA,IAAI,EAAE,OAAO;AACb,wBAAA,aAAa,EAAE,MAAM;AACrB,wBAAA,qBAAqB,EAAE,WAAW;AAClC,wBAAA,sBAAsB,EAAE,mBAAmB;AAC3C,wBAAA,YAAY,EAAE,mCAAmC;AACjD,wBAAA,SAAS,EAAE,iBAAiB;AAC7B,qBAAA;AACF,iBAAA;;AAsCD;;;;;;;;;;;;;;;AAeG;AAUG,MAAO,qBAAmC,SAAQ,qBAAwB,CAAA;uGAAnE,qBAAqB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAArB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,SAAA,EAJrB;AACT,YAAA,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,qBAAqB,CAAC,EAAE;AACzF,SAAA,EAAA,QAAA,EAAA,CAAA,WAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC9GH,6JAGA,EAAA,MAAA,EAAA,CAAA,8vDAAA,EAAA,qRAAA,CAAA,EAAA,CAAA;;2FD6Ga,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBATjC,SAAS;+BACE,aAAa,EAAA,QAAA,EACb,WAAW,EAAA,SAAA,EAGV;AACT,wBAAA,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,MAAK,qBAAsB,CAAC,EAAE;AACzF,qBAAA,EAAA,QAAA,EAAA,6JAAA,EAAA,MAAA,EAAA,CAAA,8vDAAA,EAAA,qRAAA,CAAA,EAAA;;AAIH;;;;;;;AAOG;AAUG,MAAO,qBAAmC,SAAQ,qBAAwB,CAAA;uGAAnE,qBAAqB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAArB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,SAAA,EAJrB;AACT,YAAA,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,qBAAqB,CAAC,EAAE;AACzF,SAAA,EAAA,QAAA,EAAA,CAAA,WAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EEjIH,kBACA,EAAA,MAAA,EAAA,CAAA,8vDAAA,EAAA,qDAAA,CAAA,EAAA,CAAA;;2FFkIa,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBATjC,SAAS;+BACE,aAAa,EAAA,QAAA,EACb,WAAW,EAAA,SAAA,EAGV;AACT,wBAAA,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,MAAK,qBAAsB,CAAC,EAAE;AACzF,qBAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,8vDAAA,EAAA,qDAAA,CAAA,EAAA;;;AGtGH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDG;MAmBU,iBAAiB,CAAA;;IAEnB,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAK;AAEpC;AACe;IACN,WAAW,GAAG,KAAK,CAA4B,YAAY;oFAAC;;IAI5D,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;IAExD,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;IACxD,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;IACxD,OAAO,GAAG,KAAK,CAAC,KAAK,+EAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;IACvD,OAAO,GAAG,KAAK,CAAC,KAAK,+EAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;IAGvD,KAAK,GAAG,MAAM,EAAQ;;IAGZ,KAAK,GAAG,eAAe,CAA2B,qBAAqB,6EACxF,WAAW,EAAE,IAAI,EAAA,CACjB;;IAGiB,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;+EAAC;AAEvF;AACiD;AACxC,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;AAC9D,QAAA,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC;gFAAC;AAEO,IAAA,KAAK,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;AAE1E;AACwB;IACf,QAAQ,GAAG,MAAM,CAAC,CAAC;iFAAC;AAE7B,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,WAAW,EAAE;AAClB,QAAA,iBAAiB,CAAC;YAChB,SAAS,EAAE,MAAsB;gBAC/B,IAAI,CAAC,QAAQ,EAAE;gBACf,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO;AACtC,gBAAA,OAAO;AACL,sBAAE;wBACE,CAAC,EAAE,OAAO,CAAC,UAAU;wBACrB,CAAC,EAAE,OAAO,CAAC,SAAS;wBACpB,KAAK,EAAE,OAAO,CAAC,WAAW;wBAC1B,MAAM,EAAE,OAAO,CAAC,YAAY;AAC7B;sBACD,IAAI;YACV,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;AACnC,SAAA,CAAC;IACJ;AAEA;AACiB;AACjB,IAAA,MAAM,CAAC,KAAQ,EAAA;QACb,IAAI,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE;AACxC,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;IACvB;;AAGA,IAAA,KAAK,CAAC,OAAsB,EAAA;QAC1B,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC;IAChC;AAEA;;AAE0E;AAC1E,IAAA,MAAM,CAAC,GAAoB,EAAA;AACzB,QAAA,IAAI,CAAC,GAAG;YAAE;AACV,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK;QAC9B,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA,EAAA,CAAI,CAAC;QACnD,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA,EAAA,CAAI,CAAC;QACnD,KAAK,CAAC,WAAW,CAAC,sBAAsB,EAAE,CAAA,EAAG,GAAG,CAAC,KAAK,CAAA,EAAA,CAAI,CAAC;QAC3D,KAAK,CAAC,WAAW,CAAC,uBAAuB,EAAE,CAAA,EAAG,GAAG,CAAC,MAAM,CAAA,EAAA,CAAI,CAAC;AAC7D,QAAA,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,OAAO,qBAAqB,KAAK,UAAU,EAAE;AAC1F,YAAA,qBAAqB,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACzE;IACF;AAEA;AACgD;IAChD,WAAW,GAAA;QACT,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE;QAC3C,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7E,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;AACzD,QAAA,MAAM,CAAC,CAAC,SAAS,KAAI;AACnB,YAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;AAC5B,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC;YACtD,KAAK,MAAM,OAAO,IAAI,KAAK;AAAE,gBAAA,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;YACtD,SAAS,CAAC,MAAK;AACb,gBAAA,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;gBAC9B,KAAK,MAAM,OAAO,IAAI,KAAK;AAAE,oBAAA,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;AAC1D,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;AAEU,IAAA,SAAS,CAAC,KAAoB,EAAA;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;AAC5D,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,CAAC;QACxE,IAAI,OAAO,GAAG,CAAC;YAAE;AAEjB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;QACzD,IAAI,MAAM,KAAK,SAAS;YAAE;QAC1B,KAAK,CAAC,cAAc,EAAE;QACtB,KAAK,CAAC,eAAe,EAAE;AACvB,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE;QACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B;AAEA;;AAEqE;AACrE,IAAA,OAAO,CAAC,KAAoB,EAAE,OAAe,EAAE,KAAa,EAAA;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU;AAClD,QAAA,MAAM,GAAG,GAAG,CAAC,QAAQ,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,KAAK;AACzE,QAAA,MAAM,IAAI,GAAG,CAAC,EAAU,KAAK,CAAC,OAAO,GAAG,EAAE,GAAG,KAAK,IAAI,KAAK;AAC3D,QAAA,QAAQ,KAAK,CAAC,GAAG;AACf,YAAA,KAAK,GAAG;AACR,YAAA,KAAK,OAAO;AACV,gBAAA,OAAO,OAAO;AAChB,YAAA,KAAK,MAAM;AACT,gBAAA,OAAO,CAAC;AACV,YAAA,KAAK,KAAK;gBACR,OAAO,KAAK,GAAG,CAAC;AAClB,YAAA,KAAK,WAAW;gBACd,OAAO,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAClD,YAAA,KAAK,YAAY;gBACf,OAAO,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AAClD,YAAA,KAAK,SAAS;AACZ,gBAAA,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;AACxC,YAAA,KAAK,WAAW;AACd,gBAAA,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS;AACvC,YAAA;AACE,gBAAA,OAAO,SAAS;;IAEtB;AAEU,IAAA,UAAU,CAAC,KAAiB,EAAA;QACpC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,aAA4B,CAAC;AAAE,YAAA,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;IACjF;uGApJW,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAjB,iBAAiB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,KAAA,EAAA,aAAA,EAAA,KAAA,EAAA,OAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,YAAA,EAAA,EAAA,SAAA,EAAA,EAAA,SAAA,EAAA,mBAAA,EAAA,UAAA,EAAA,oBAAA,EAAA,EAAA,UAAA,EAAA,EAAA,uBAAA,EAAA,eAAA,EAAA,uBAAA,EAAA,eAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,mBAAA,EAAA,kCAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,EAAA,EAAA,SAAA,EAdjB,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,iBAAiB,CAAC,EAAE,CAAC,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,OAAA,EAAA,SAAA,EAmC1B,qBAAqB,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC1H5F,mNAIA,EAAA,MAAA,EAAA,CAAA,8hDAAA,CAAA,EAAA,CAAA;;2FDiGa,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAlB7B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,QAAQ,aAGP,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,UAAU,CAAC,uBAAuB,CAAC,EAAE,CAAC,EAAA,IAAA,EACzF;AACJ,wBAAA,IAAI,EAAE,YAAY;AAClB,wBAAA,yBAAyB,EAAE,eAAe;AAC1C,wBAAA,yBAAyB,EAAE,eAAe;AAC1C,wBAAA,sBAAsB,EAAE,oBAAoB;AAC5C,wBAAA,sBAAsB,EAAE,oBAAoB;AAC5C,wBAAA,sBAAsB,EAAE,oBAAoB;AAC5C,wBAAA,qBAAqB,EAAE,kCAAkC;AACzD,wBAAA,mBAAmB,EAAE,mBAAmB;AACxC,wBAAA,WAAW,EAAE,mBAAmB;AAChC,wBAAA,YAAY,EAAE,oBAAoB;AACnC,qBAAA,EAAA,QAAA,EAAA,mNAAA,EAAA,MAAA,EAAA,CAAA,8hDAAA,CAAA,EAAA;AAuBoE,SAAA,CAAA,EAAA,cAAA,EAAA,MAAA,EAAA,EAAA,cAAA,EAAA,EAAA,KAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,CAAA,MAAA,EAAA,IAAA,EAAA,CAAA,aAAA,CAAA,EAAA,CAAA,EAAA,WAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,aAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,UAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,UAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,UAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,SAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,SAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,KAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,MAAA,EAAA,IAAA,EAAA,CAAA,OAAA,CAAA,EAAA,CAAA,EAAA,KAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,UAAA,CAAA,MAAA,qBAAqB,CAAA,EAAA,EAAA,GAAE;AAC1F,4BAAA,WAAW,EAAE,IAAI;AAClB,yBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AE5HH;;AAEG;;ACFH;;AAEG;;;;"}
|