@nanahoshi/mona-ui 0.12.0 → 0.13.1
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.
|
@@ -1,11 +1,33 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { computed, input, output, model, Component } from '@angular/core';
|
|
3
|
+
import { inject, TemplateRef, Directive, DestroyRef, ElementRef, computed, signal, contentChild, viewChildren, input, output, model, afterRenderEffect, afterNextRender, Component } from '@angular/core';
|
|
3
4
|
import { themeControlSurfaceClasses, createElementControlId } from '@nanahoshi/mona-ui/internal';
|
|
4
5
|
import { twMerge } from 'tailwind-merge';
|
|
5
6
|
import { cva } from 'class-variance-authority';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @description Renders the visual content of every segmented option. Overrides the default label
|
|
10
|
+
* text of each option.
|
|
11
|
+
*/
|
|
12
|
+
class SegmentedItemTemplateDirective {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.templateRef = inject(TemplateRef);
|
|
15
|
+
}
|
|
16
|
+
static ngTemplateContextGuard(_directive, context) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SegmentedItemTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
20
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SegmentedItemTemplateDirective, isStandalone: true, selector: "ng-template[monaSegmentedItemTemplate]", ngImport: i0 }); }
|
|
21
|
+
}
|
|
22
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SegmentedItemTemplateDirective, decorators: [{
|
|
23
|
+
type: Directive,
|
|
24
|
+
args: [{
|
|
25
|
+
selector: "ng-template[monaSegmentedItemTemplate]"
|
|
26
|
+
}]
|
|
27
|
+
}] });
|
|
28
|
+
|
|
7
29
|
const segmentedContainerThemeVariants = cva(`
|
|
8
|
-
inline-flex items-center gap-0.5
|
|
30
|
+
relative inline-flex items-center gap-0.5
|
|
9
31
|
${themeControlSurfaceClasses}
|
|
10
32
|
border border-input-border
|
|
11
33
|
p-1
|
|
@@ -34,11 +56,11 @@ const segmentedOptionThemeVariants = cva(`
|
|
|
34
56
|
data-[disabled='true']:cursor-not-allowed
|
|
35
57
|
data-[disabled='true']:opacity-50
|
|
36
58
|
data-[disabled='true']:text-disabled-foreground
|
|
37
|
-
data-[selected='true']:
|
|
38
|
-
data-[selected='true']:shadow-sm data-[selected='true']:ring-1 data-[selected='true']:ring-selected-border
|
|
59
|
+
data-[selected='true']:text-primary-foreground
|
|
39
60
|
data-[selected='false']:text-muted-foreground
|
|
40
61
|
data-[selected='false']:hover:bg-hover data-[selected='false']:hover:text-foreground
|
|
41
62
|
data-[disabled='true']:data-[selected='false']:text-disabled-foreground
|
|
63
|
+
data-[selected='true']:transition-none
|
|
42
64
|
`, {
|
|
43
65
|
variants: {
|
|
44
66
|
rounded: {
|
|
@@ -59,6 +81,30 @@ const segmentedOptionThemeVariants = cva(`
|
|
|
59
81
|
size: "medium"
|
|
60
82
|
}
|
|
61
83
|
});
|
|
84
|
+
const segmentedIndicatorThemeVariants = cva(`
|
|
85
|
+
pointer-events-none absolute top-0 left-0
|
|
86
|
+
bg-primary shadow-sm ring-1 ring-selected-border
|
|
87
|
+
data-[disabled='true']:opacity-50
|
|
88
|
+
motion-reduce:transition-none
|
|
89
|
+
`, {
|
|
90
|
+
variants: {
|
|
91
|
+
animate: {
|
|
92
|
+
true: "transition-[transform,width,height] duration-(--mona-motion-fast) ease-out",
|
|
93
|
+
false: "transition-none"
|
|
94
|
+
},
|
|
95
|
+
rounded: {
|
|
96
|
+
none: "rounded-none",
|
|
97
|
+
small: "rounded-xs",
|
|
98
|
+
medium: "rounded-sm",
|
|
99
|
+
large: "rounded-md",
|
|
100
|
+
full: "rounded-full"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
defaultVariants: {
|
|
104
|
+
animate: true,
|
|
105
|
+
rounded: "medium"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
62
108
|
const segmentedInputThemeVariants = cva(`
|
|
63
109
|
peer absolute inset-0 z-10 h-full w-full
|
|
64
110
|
cursor-pointer appearance-none opacity-0
|
|
@@ -67,23 +113,71 @@ const segmentedInputThemeVariants = cva(`
|
|
|
67
113
|
`);
|
|
68
114
|
|
|
69
115
|
class SegmentedComponent {
|
|
116
|
+
#destroyRef;
|
|
117
|
+
#hostElementRef;
|
|
118
|
+
#lastAnimatedIndex;
|
|
119
|
+
#lastOptions;
|
|
120
|
+
#lastSize;
|
|
121
|
+
#resizeObserver;
|
|
70
122
|
constructor() {
|
|
123
|
+
this.#destroyRef = inject(DestroyRef);
|
|
124
|
+
this.#hostElementRef = inject(ElementRef);
|
|
125
|
+
this.#lastAnimatedIndex = null;
|
|
126
|
+
this.#lastOptions = null;
|
|
127
|
+
this.#lastSize = null;
|
|
128
|
+
this.#resizeObserver = null;
|
|
71
129
|
this.containerClasses = computed(() => {
|
|
72
130
|
const classes = segmentedContainerThemeVariants({ rounded: this.rounded() });
|
|
73
131
|
return twMerge(classes, this.userClass());
|
|
74
132
|
}, /* @ts-ignore */
|
|
75
133
|
...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
|
|
76
134
|
this.groupName = createElementControlId();
|
|
135
|
+
this.indicatorClasses = computed(() => {
|
|
136
|
+
const animate = this.animate() && this.indicatorTransitionEnabled();
|
|
137
|
+
const rounded = this.rounded();
|
|
138
|
+
return segmentedIndicatorThemeVariants({ animate, rounded });
|
|
139
|
+
}, /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "indicatorClasses" }] : /* istanbul ignore next */ []));
|
|
141
|
+
this.indicatorGeometry = signal(null, /* @ts-ignore */
|
|
142
|
+
...(ngDevMode ? [{ debugName: "indicatorGeometry" }] : /* istanbul ignore next */ []));
|
|
143
|
+
this.indicatorInitialized = signal(false, /* @ts-ignore */
|
|
144
|
+
...(ngDevMode ? [{ debugName: "indicatorInitialized" }] : /* istanbul ignore next */ []));
|
|
145
|
+
this.indicatorTransitionEnabled = signal(false, /* @ts-ignore */
|
|
146
|
+
...(ngDevMode ? [{ debugName: "indicatorTransitionEnabled" }] : /* istanbul ignore next */ []));
|
|
77
147
|
this.inputClasses = computed(() => segmentedInputThemeVariants(), /* @ts-ignore */
|
|
78
148
|
...(ngDevMode ? [{ debugName: "inputClasses" }] : /* istanbul ignore next */ []));
|
|
79
149
|
this.invalidState = computed(() => this.touched() && this.invalid(), /* @ts-ignore */
|
|
80
150
|
...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
151
|
+
this.itemTemplate = contentChild(SegmentedItemTemplateDirective, { ...(ngDevMode ? { debugName: "itemTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
|
|
81
152
|
this.optionClasses = computed(() => {
|
|
82
153
|
const rounded = this.rounded();
|
|
83
154
|
const size = this.size();
|
|
84
155
|
return segmentedOptionThemeVariants({ rounded, size });
|
|
85
156
|
}, /* @ts-ignore */
|
|
86
157
|
...(ngDevMode ? [{ debugName: "optionClasses" }] : /* istanbul ignore next */ []));
|
|
158
|
+
this.optionElements = viewChildren("optionElement", /* @ts-ignore */
|
|
159
|
+
...(ngDevMode ? [{ debugName: "optionElements" }] : /* istanbul ignore next */ []));
|
|
160
|
+
this.selectedIndex = computed(() => {
|
|
161
|
+
const value = this.value();
|
|
162
|
+
return this.options().findIndex(option => option.value === value);
|
|
163
|
+
}, /* @ts-ignore */
|
|
164
|
+
...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
|
|
165
|
+
this.selectedOptionDisabled = computed(() => {
|
|
166
|
+
const selectedIndex = this.selectedIndex();
|
|
167
|
+
if (selectedIndex === -1) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const option = this.options()[selectedIndex];
|
|
171
|
+
return this.disabled() || !!option?.disabled;
|
|
172
|
+
}, /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "selectedOptionDisabled" }] : /* istanbul ignore next */ []));
|
|
174
|
+
/**
|
|
175
|
+
* @description Controls whether the selection indicator animates between selected options.
|
|
176
|
+
* When false, selection changes are applied immediately without transition.
|
|
177
|
+
* @default true
|
|
178
|
+
*/
|
|
179
|
+
this.animate = input(true, /* @ts-ignore */
|
|
180
|
+
...(ngDevMode ? [{ debugName: "animate" }] : /* istanbul ignore next */ []));
|
|
87
181
|
/**
|
|
88
182
|
* @description Accessible name for the radio group. Provide either `aria-label` or `aria-labelledby`.
|
|
89
183
|
* @default null
|
|
@@ -147,6 +241,39 @@ class SegmentedComponent {
|
|
|
147
241
|
*/
|
|
148
242
|
this.value = model(null, /* @ts-ignore */
|
|
149
243
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
244
|
+
afterRenderEffect({
|
|
245
|
+
read: () => {
|
|
246
|
+
this.selectedIndex();
|
|
247
|
+
this.optionElements();
|
|
248
|
+
this.rounded();
|
|
249
|
+
this.animate();
|
|
250
|
+
const size = this.size();
|
|
251
|
+
const options = this.options();
|
|
252
|
+
const isSizeChange = this.#lastSize !== null && this.#lastSize !== size;
|
|
253
|
+
const isOptionsChange = this.#lastOptions !== null && this.#lastOptions !== options;
|
|
254
|
+
const suppressTransition = isSizeChange || isOptionsChange;
|
|
255
|
+
this.#lastSize = size;
|
|
256
|
+
this.#lastOptions = options;
|
|
257
|
+
this.updateIndicatorGeometry({ suppressTransition });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
afterNextRender({
|
|
261
|
+
read: () => {
|
|
262
|
+
this.setupResizeObserver();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.#destroyRef.onDestroy(() => {
|
|
266
|
+
this.#resizeObserver?.disconnect();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
createOptionContext(option, index) {
|
|
270
|
+
return {
|
|
271
|
+
$implicit: option,
|
|
272
|
+
disabled: this.disabled() || !!option.disabled,
|
|
273
|
+
index,
|
|
274
|
+
option,
|
|
275
|
+
selected: option.value === this.value()
|
|
276
|
+
};
|
|
150
277
|
}
|
|
151
278
|
onOptionBlur() {
|
|
152
279
|
this.touch.emit();
|
|
@@ -158,12 +285,61 @@ class SegmentedComponent {
|
|
|
158
285
|
this.value.set(option.value);
|
|
159
286
|
this.touch.emit();
|
|
160
287
|
}
|
|
288
|
+
setupResizeObserver() {
|
|
289
|
+
if (typeof ResizeObserver === "undefined") {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
293
|
+
this.updateIndicatorGeometry({ suppressTransition: true });
|
|
294
|
+
});
|
|
295
|
+
this.#resizeObserver.observe(this.#hostElementRef.nativeElement);
|
|
296
|
+
}
|
|
297
|
+
updateIndicatorGeometry(options) {
|
|
298
|
+
const selectedIndex = this.selectedIndex();
|
|
299
|
+
if (selectedIndex === -1) {
|
|
300
|
+
this.#lastAnimatedIndex = null;
|
|
301
|
+
this.indicatorGeometry.set(null);
|
|
302
|
+
this.indicatorTransitionEnabled.set(false);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const optionElements = this.optionElements();
|
|
306
|
+
const selectedElementRef = optionElements[selectedIndex];
|
|
307
|
+
if (!selectedElementRef?.nativeElement) {
|
|
308
|
+
this.#lastAnimatedIndex = null;
|
|
309
|
+
this.indicatorGeometry.set(null);
|
|
310
|
+
this.indicatorTransitionEnabled.set(false);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const host = this.#hostElementRef.nativeElement;
|
|
314
|
+
const option = selectedElementRef.nativeElement;
|
|
315
|
+
const hostRect = host.getBoundingClientRect();
|
|
316
|
+
const optionRect = option.getBoundingClientRect();
|
|
317
|
+
const x = optionRect.left - hostRect.left - (host.clientLeft || 0);
|
|
318
|
+
const y = optionRect.top - hostRect.top - (host.clientTop || 0);
|
|
319
|
+
const width = optionRect.width;
|
|
320
|
+
const height = optionRect.height;
|
|
321
|
+
const shouldAnimate = !options?.suppressTransition &&
|
|
322
|
+
this.animate() &&
|
|
323
|
+
this.indicatorInitialized() &&
|
|
324
|
+
this.#lastAnimatedIndex !== null &&
|
|
325
|
+
this.#lastAnimatedIndex !== selectedIndex;
|
|
326
|
+
this.indicatorTransitionEnabled.set(shouldAnimate);
|
|
327
|
+
this.indicatorGeometry.set({
|
|
328
|
+
height,
|
|
329
|
+
transform: `translate3d(${x}px, ${y}px, 0)`,
|
|
330
|
+
width
|
|
331
|
+
});
|
|
332
|
+
this.#lastAnimatedIndex = selectedIndex;
|
|
333
|
+
if (!this.indicatorInitialized()) {
|
|
334
|
+
this.indicatorInitialized.set(true);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
161
337
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
162
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SegmentedComponent, isStandalone: true, selector: "mona-segmented", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touch: "touch", value: "valueChange" }, host: { properties: { "attr.aria-disabled": "disabled() || undefined", "attr.aria-invalid": "invalidState() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.data-disabled": "disabled()", "attr.data-invalid": "invalidState() || null", "attr.role": "'radiogroup'", "class": "containerClasses()" } }, ngImport: i0, template: "@for (option of options(); track option.value) {\n @let selected = option.value === value();\n @let optionDisabled = disabled() || option.disabled;\n\n <label
|
|
338
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SegmentedComponent, isStandalone: true, selector: "mona-segmented", inputs: { animate: { classPropertyName: "animate", publicName: "animate", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touch: "touch", value: "valueChange" }, host: { properties: { "attr.aria-disabled": "disabled() || undefined", "attr.aria-invalid": "invalidState() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.data-disabled": "disabled()", "attr.data-invalid": "invalidState() || null", "attr.role": "'radiogroup'", "class": "containerClasses()" } }, queries: [{ propertyName: "itemTemplate", first: true, predicate: SegmentedItemTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "optionElements", predicate: ["optionElement"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (indicatorGeometry(); as geometry) {\n <div\n aria-hidden=\"true\"\n [class]=\"indicatorClasses()\"\n [attr.data-disabled]=\"selectedOptionDisabled() ? 'true' : null\"\n [style.transform]=\"geometry.transform\"\n [style.width.px]=\"geometry.width\"\n [style.height.px]=\"geometry.height\"></div>\n}\n\n@for (option of options(); track option.value) {\n @let selected = option.value === value();\n @let optionDisabled = disabled() || option.disabled;\n\n <label\n #optionElement\n [class]=\"optionClasses()\"\n [attr.data-selected]=\"selected\"\n [attr.data-disabled]=\"optionDisabled\">\n <input\n type=\"radio\"\n [name]=\"groupName\"\n [value]=\"option.value\"\n [checked]=\"selected\"\n [disabled]=\"optionDisabled\"\n [class]=\"inputClasses()\"\n (change)=\"onOptionChange(option)\"\n (blur)=\"onOptionBlur()\" />\n\n <span\n class=\"pointer-events-none flex flex-1 items-center justify-center gap-1\n [border-radius:inherit]\n peer-focus-visible:ring-2\n peer-focus-visible:ring-focus-indicator/35\">\n @if (itemTemplate(); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"createOptionContext(option, $index)\"></ng-container>\n } @else {\n {{ option.label }}\n }\n </span>\n </label>\n}\n\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
|
|
163
339
|
}
|
|
164
340
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SegmentedComponent, decorators: [{
|
|
165
341
|
type: Component,
|
|
166
|
-
args: [{ selector: "mona-segmented", host: {
|
|
342
|
+
args: [{ selector: "mona-segmented", imports: [NgTemplateOutlet], host: {
|
|
167
343
|
"[attr.aria-disabled]": "disabled() || undefined",
|
|
168
344
|
"[attr.aria-invalid]": "invalidState() ? 'true' : null",
|
|
169
345
|
"[attr.aria-label]": "ariaLabel()",
|
|
@@ -172,8 +348,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
172
348
|
"[attr.data-invalid]": "invalidState() || null",
|
|
173
349
|
"[attr.role]": "'radiogroup'",
|
|
174
350
|
"[class]": "containerClasses()"
|
|
175
|
-
}, template: "@for (option of options(); track option.value) {\n @let selected = option.value === value();\n @let optionDisabled = disabled() || option.disabled;\n\n <label
|
|
176
|
-
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
|
|
351
|
+
}, template: "@if (indicatorGeometry(); as geometry) {\n <div\n aria-hidden=\"true\"\n [class]=\"indicatorClasses()\"\n [attr.data-disabled]=\"selectedOptionDisabled() ? 'true' : null\"\n [style.transform]=\"geometry.transform\"\n [style.width.px]=\"geometry.width\"\n [style.height.px]=\"geometry.height\"></div>\n}\n\n@for (option of options(); track option.value) {\n @let selected = option.value === value();\n @let optionDisabled = disabled() || option.disabled;\n\n <label\n #optionElement\n [class]=\"optionClasses()\"\n [attr.data-selected]=\"selected\"\n [attr.data-disabled]=\"optionDisabled\">\n <input\n type=\"radio\"\n [name]=\"groupName\"\n [value]=\"option.value\"\n [checked]=\"selected\"\n [disabled]=\"optionDisabled\"\n [class]=\"inputClasses()\"\n (change)=\"onOptionChange(option)\"\n (blur)=\"onOptionBlur()\" />\n\n <span\n class=\"pointer-events-none flex flex-1 items-center justify-center gap-1\n [border-radius:inherit]\n peer-focus-visible:ring-2\n peer-focus-visible:ring-focus-indicator/35\">\n @if (itemTemplate(); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"createOptionContext(option, $index)\"></ng-container>\n } @else {\n {{ option.label }}\n }\n </span>\n </label>\n}\n\n" }]
|
|
352
|
+
}], ctorParameters: () => [], propDecorators: { itemTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SegmentedItemTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], optionElements: [{ type: i0.ViewChildren, args: ["optionElement", { isSignal: true }] }], animate: [{ type: i0.Input, args: [{ isSignal: true, alias: "animate", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
|
|
177
353
|
|
|
178
354
|
/*
|
|
179
355
|
* Public API Surface of @nanahoshi/mona-ui/segmented
|
|
@@ -183,5 +359,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
183
359
|
* Generated bundle index. Do not edit.
|
|
184
360
|
*/
|
|
185
361
|
|
|
186
|
-
export { SegmentedComponent };
|
|
362
|
+
export { SegmentedComponent, SegmentedItemTemplateDirective };
|
|
187
363
|
//# sourceMappingURL=nanahoshi-mona-ui-segmented.mjs.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { TemplateRef, ElementRef } from '@angular/core';
|
|
2
3
|
import { FormValueControl } from '@angular/forms/signals';
|
|
3
4
|
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
4
5
|
import { VariantProps } from 'class-variance-authority';
|
|
5
6
|
import { VariantInputs } from '@nanahoshi/mona-ui/internal';
|
|
7
|
+
import * as _nanahoshi_mona_ui_segmented from '@nanahoshi/mona-ui/segmented';
|
|
8
|
+
|
|
9
|
+
interface SegmentedIndicatorGeometry {
|
|
10
|
+
height: number;
|
|
11
|
+
transform: string;
|
|
12
|
+
width: number;
|
|
13
|
+
}
|
|
6
14
|
|
|
7
15
|
type SegmentedValue = string | number;
|
|
8
16
|
|
|
@@ -12,6 +20,32 @@ interface SegmentedOption<T extends SegmentedValue = SegmentedValue> {
|
|
|
12
20
|
readonly disabled?: boolean;
|
|
13
21
|
}
|
|
14
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @description Rendering context passed to the segmented item template.
|
|
25
|
+
*/
|
|
26
|
+
interface SegmentedItemTemplateContext<T extends SegmentedValue = SegmentedValue> {
|
|
27
|
+
/**
|
|
28
|
+
* The option rendered by this item.
|
|
29
|
+
*/
|
|
30
|
+
readonly $implicit: SegmentedOption<T>;
|
|
31
|
+
/**
|
|
32
|
+
* The option rendered by this item.
|
|
33
|
+
*/
|
|
34
|
+
readonly option: SegmentedOption<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Zero-based option index.
|
|
37
|
+
*/
|
|
38
|
+
readonly index: number;
|
|
39
|
+
/**
|
|
40
|
+
* Whether this option is currently selected.
|
|
41
|
+
*/
|
|
42
|
+
readonly selected: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this option is disabled, either individually or via the group.
|
|
45
|
+
*/
|
|
46
|
+
readonly disabled: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
declare const segmentedContainerThemeVariants: (props?: ({
|
|
16
50
|
rounded?: "none" | "small" | "medium" | "large" | "full" | null | undefined;
|
|
17
51
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
@@ -27,11 +61,26 @@ type SegmentedVariantProps = SegmentedContainerVariantProps & SegmentedOptionVar
|
|
|
27
61
|
type SegmentedVariantInput = SegmentedContainerVariantInput & SegmentedOptionVariantInput;
|
|
28
62
|
|
|
29
63
|
declare class SegmentedComponent<T extends SegmentedValue = SegmentedValue> implements SegmentedVariantInput, FormValueControl<T | null> {
|
|
64
|
+
#private;
|
|
30
65
|
protected readonly containerClasses: _angular_core.Signal<string>;
|
|
31
66
|
protected readonly groupName: string;
|
|
67
|
+
protected readonly indicatorClasses: _angular_core.Signal<string>;
|
|
68
|
+
protected readonly indicatorGeometry: _angular_core.WritableSignal<SegmentedIndicatorGeometry | null>;
|
|
69
|
+
protected readonly indicatorInitialized: _angular_core.WritableSignal<boolean>;
|
|
70
|
+
protected readonly indicatorTransitionEnabled: _angular_core.WritableSignal<boolean>;
|
|
32
71
|
protected readonly inputClasses: _angular_core.Signal<string>;
|
|
33
72
|
protected readonly invalidState: _angular_core.Signal<boolean>;
|
|
73
|
+
protected readonly itemTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
|
|
34
74
|
protected readonly optionClasses: _angular_core.Signal<string>;
|
|
75
|
+
protected readonly optionElements: _angular_core.Signal<readonly ElementRef<HTMLLabelElement>[]>;
|
|
76
|
+
protected readonly selectedIndex: _angular_core.Signal<number>;
|
|
77
|
+
protected readonly selectedOptionDisabled: _angular_core.Signal<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* @description Controls whether the selection indicator animates between selected options.
|
|
80
|
+
* When false, selection changes are applied immediately without transition.
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
readonly animate: _angular_core.InputSignal<boolean>;
|
|
35
84
|
/**
|
|
36
85
|
* @description Accessible name for the radio group. Provide either `aria-label` or `aria-labelledby`.
|
|
37
86
|
* @default null
|
|
@@ -88,11 +137,26 @@ declare class SegmentedComponent<T extends SegmentedValue = SegmentedValue> impl
|
|
|
88
137
|
* @default null
|
|
89
138
|
*/
|
|
90
139
|
readonly value: _angular_core.ModelSignal<T | null>;
|
|
140
|
+
constructor();
|
|
141
|
+
protected createOptionContext(option: SegmentedOption<T>, index: number): SegmentedItemTemplateContext<T>;
|
|
91
142
|
protected onOptionBlur(): void;
|
|
92
143
|
protected onOptionChange(option: SegmentedOption<T>): void;
|
|
144
|
+
private setupResizeObserver;
|
|
145
|
+
private updateIndicatorGeometry;
|
|
93
146
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SegmentedComponent<any>, never>;
|
|
94
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SegmentedComponent<any>, "mona-segmented", never, { "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledBy": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "touch": "touch"; "value": "valueChange"; },
|
|
147
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SegmentedComponent<any>, "mona-segmented", never, { "animate": { "alias": "animate"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledBy": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "touch": "touch"; "value": "valueChange"; }, ["itemTemplate"], never, true, never>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @description Renders the visual content of every segmented option. Overrides the default label
|
|
152
|
+
* text of each option.
|
|
153
|
+
*/
|
|
154
|
+
declare class SegmentedItemTemplateDirective {
|
|
155
|
+
readonly templateRef: TemplateRef<SegmentedItemTemplateContext<_nanahoshi_mona_ui_segmented.SegmentedValue>>;
|
|
156
|
+
static ngTemplateContextGuard(_directive: SegmentedItemTemplateDirective, context: unknown): context is SegmentedItemTemplateContext;
|
|
157
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SegmentedItemTemplateDirective, never>;
|
|
158
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SegmentedItemTemplateDirective, "ng-template[monaSegmentedItemTemplate]", never, {}, {}, never, never, true, never>;
|
|
95
159
|
}
|
|
96
160
|
|
|
97
|
-
export { SegmentedComponent };
|
|
98
|
-
export type { SegmentedOption, SegmentedValue, SegmentedVariantProps };
|
|
161
|
+
export { SegmentedComponent, SegmentedItemTemplateDirective };
|
|
162
|
+
export type { SegmentedItemTemplateContext, SegmentedOption, SegmentedValue, SegmentedVariantProps };
|