@smuikit/angular 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1054 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, ViewChild, Output, Input, ChangeDetectionStrategy, Component, HostListener, NgModule, Inject, Injectable } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule, DOCUMENT } from '@angular/common';
5
+ import { createButtonMachine, connectButton, createInputMachine, connectInput, createModalMachine, connectModal, connectTypography, connectCard } from '@smui/core';
6
+
7
+ class MachineAdapter {
8
+ ngZone;
9
+ machine;
10
+ unsubscribe = null;
11
+ snapshot;
12
+ constructor(ngZone, machine) {
13
+ this.ngZone = ngZone;
14
+ this.machine = machine;
15
+ this.snapshot = this.machine.getSnapshot();
16
+ this.machine.start();
17
+ this.unsubscribe = this.machine.subscribe(() => {
18
+ this.ngZone.run(() => {
19
+ this.snapshot = this.machine.getSnapshot();
20
+ });
21
+ });
22
+ }
23
+ static create(ngZone, machine) {
24
+ return new MachineAdapter(ngZone, machine);
25
+ }
26
+ get state() {
27
+ return this.snapshot.state;
28
+ }
29
+ get context() {
30
+ return this.snapshot.context;
31
+ }
32
+ send(event) {
33
+ this.machine.send(event);
34
+ }
35
+ destroy() {
36
+ this.unsubscribe?.();
37
+ this.unsubscribe = null;
38
+ this.machine.stop();
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Map of React-style event-handler names to their Angular attribute-binding
44
+ * equivalents. Angular templates bind DOM events with `(eventname)`, but
45
+ * when we spread props programmatically in `ngOnChanges` / renderers we
46
+ * need the raw DOM event name so we can call `addEventListener` / set the
47
+ * property on the native element.
48
+ */
49
+ const EVENT_HANDLER_MAP = {
50
+ onClick: 'click',
51
+ onPointerDown: 'pointerdown',
52
+ onPointerUp: 'pointerup',
53
+ onFocus: 'focus',
54
+ onBlur: 'blur',
55
+ onKeyDown: 'keydown',
56
+ onChange: 'input', // DOM `input` event is the Angular equivalent of React `onChange`
57
+ };
58
+ /** Property names that are boolean HTML attributes. */
59
+ const BOOLEAN_ATTRS = new Set([
60
+ 'disabled',
61
+ 'readOnly',
62
+ 'required',
63
+ 'hidden',
64
+ ]);
65
+ /**
66
+ * Takes an `ElementProps` object produced by a `connect*` function from
67
+ * @smui/core and splits it into three buckets that Angular can consume:
68
+ * attributes, styles, and event listeners.
69
+ *
70
+ * - `undefined` values are filtered out.
71
+ * - React-style event handlers (`onClick`, `onFocus`, etc.) are mapped to
72
+ * their DOM event names (`click`, `focus`, ...).
73
+ */
74
+ function normalizeProps(props) {
75
+ const attrs = {};
76
+ const styles = {};
77
+ const listeners = {};
78
+ if (!props) {
79
+ return { attrs, styles, listeners };
80
+ }
81
+ for (const [key, value] of Object.entries(props)) {
82
+ // Skip undefined values
83
+ if (value === undefined)
84
+ continue;
85
+ // Style object
86
+ if (key === 'style' && typeof value === 'object' && value !== null) {
87
+ Object.assign(styles, value);
88
+ continue;
89
+ }
90
+ // Event handlers
91
+ const eventName = EVENT_HANDLER_MAP[key];
92
+ if (eventName && typeof value === 'function') {
93
+ listeners[eventName] = value;
94
+ continue;
95
+ }
96
+ // Regular attributes
97
+ attrs[key] = value;
98
+ }
99
+ return { attrs, styles, listeners };
100
+ }
101
+ /**
102
+ * Applies NormalizedProps to a native DOM element.
103
+ * Returns an array of cleanup functions (for removing event listeners).
104
+ */
105
+ function applyProps(element, normalized) {
106
+ const cleanups = [];
107
+ // Set attributes
108
+ for (const [key, value] of Object.entries(normalized.attrs)) {
109
+ if (value === undefined || value === false) {
110
+ element.removeAttribute(key);
111
+ }
112
+ else if (value === true) {
113
+ element.setAttribute(key, '');
114
+ }
115
+ else {
116
+ element.setAttribute(key, String(value));
117
+ }
118
+ }
119
+ // Set styles
120
+ for (const [key, value] of Object.entries(normalized.styles)) {
121
+ element.style[key] = String(value);
122
+ }
123
+ // Add event listeners
124
+ for (const [eventName, handler] of Object.entries(normalized.listeners)) {
125
+ element.addEventListener(eventName, handler);
126
+ cleanups.push(() => element.removeEventListener(eventName, handler));
127
+ }
128
+ return cleanups;
129
+ }
130
+
131
+ class ButtonComponent {
132
+ ngZone;
133
+ cdr;
134
+ variant = 'primary';
135
+ size = 'md';
136
+ disabled = false;
137
+ loading = false;
138
+ buttonClick = new EventEmitter();
139
+ buttonElRef;
140
+ api;
141
+ adapter;
142
+ listenerCleanups = [];
143
+ get hostClass() {
144
+ return `smui-button smui-button--${this.variant} smui-button--${this.size}`;
145
+ }
146
+ constructor(ngZone, cdr) {
147
+ this.ngZone = ngZone;
148
+ this.cdr = cdr;
149
+ }
150
+ ngOnInit() {
151
+ this.adapter = MachineAdapter.create(this.ngZone, createButtonMachine({
152
+ variant: this.variant,
153
+ size: this.size,
154
+ disabled: this.disabled,
155
+ loading: this.loading,
156
+ }));
157
+ this.updateApi();
158
+ this.applyPropsToElement();
159
+ }
160
+ ngOnChanges(changes) {
161
+ if (!this.adapter)
162
+ return;
163
+ if (changes['disabled']) {
164
+ this.adapter.send({ type: 'SET_DISABLED', disabled: this.disabled });
165
+ }
166
+ if (changes['loading']) {
167
+ this.adapter.send({ type: 'SET_LOADING', loading: this.loading });
168
+ }
169
+ this.updateApi();
170
+ this.applyPropsToElement();
171
+ }
172
+ ngOnDestroy() {
173
+ this.cleanupListeners();
174
+ this.adapter?.destroy();
175
+ }
176
+ updateApi() {
177
+ this.api = connectButton({
178
+ state: this.adapter.state,
179
+ context: this.adapter.context,
180
+ send: (event) => {
181
+ this.adapter.send(event);
182
+ this.updateApi();
183
+ this.applyPropsToElement();
184
+ this.cdr.markForCheck();
185
+ },
186
+ onClick: (event) => {
187
+ this.buttonClick.emit(event);
188
+ },
189
+ });
190
+ }
191
+ applyPropsToElement() {
192
+ if (!this.buttonElRef?.nativeElement)
193
+ return;
194
+ this.cleanupListeners();
195
+ const normalized = normalizeProps(this.api.rootProps);
196
+ this.listenerCleanups = applyProps(this.buttonElRef.nativeElement, normalized);
197
+ }
198
+ cleanupListeners() {
199
+ for (const cleanup of this.listenerCleanups) {
200
+ cleanup();
201
+ }
202
+ this.listenerCleanups = [];
203
+ }
204
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ButtonComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
205
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ButtonComponent, isStandalone: true, selector: "smui-button", inputs: { variant: "variant", size: "size", disabled: "disabled", loading: "loading" }, outputs: { buttonClick: "buttonClick" }, viewQueries: [{ propertyName: "buttonElRef", first: true, predicate: ["buttonEl"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
206
+ <button
207
+ #buttonEl
208
+ [class]="hostClass"
209
+ [attr.data-state]="api.state"
210
+ [attr.data-variant]="variant"
211
+ [attr.data-size]="size"
212
+ >
213
+ <span
214
+ *ngIf="api.isLoading"
215
+ class="smui-button__loader"
216
+ aria-hidden="true"
217
+ >
218
+ <span class="smui-button__spinner"></span>
219
+ </span>
220
+ <span
221
+ class="smui-button__content"
222
+ [style.visibility]="api.isLoading ? 'hidden' : 'visible'"
223
+ >
224
+ <ng-content></ng-content>
225
+ </span>
226
+ </button>
227
+ `, isInline: true, styles: [":host{display:inline-block}.smui-button__loader{position:absolute;display:inline-flex;align-items:center;justify-content:center}.smui-button__content{display:inline-flex;align-items:center;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
228
+ }
229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ButtonComponent, decorators: [{
230
+ type: Component,
231
+ args: [{ selector: 'smui-button', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
232
+ <button
233
+ #buttonEl
234
+ [class]="hostClass"
235
+ [attr.data-state]="api.state"
236
+ [attr.data-variant]="variant"
237
+ [attr.data-size]="size"
238
+ >
239
+ <span
240
+ *ngIf="api.isLoading"
241
+ class="smui-button__loader"
242
+ aria-hidden="true"
243
+ >
244
+ <span class="smui-button__spinner"></span>
245
+ </span>
246
+ <span
247
+ class="smui-button__content"
248
+ [style.visibility]="api.isLoading ? 'hidden' : 'visible'"
249
+ >
250
+ <ng-content></ng-content>
251
+ </span>
252
+ </button>
253
+ `, styles: [":host{display:inline-block}.smui-button__loader{position:absolute;display:inline-flex;align-items:center;justify-content:center}.smui-button__content{display:inline-flex;align-items:center;gap:8px}\n"] }]
254
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { variant: [{
255
+ type: Input
256
+ }], size: [{
257
+ type: Input
258
+ }], disabled: [{
259
+ type: Input
260
+ }], loading: [{
261
+ type: Input
262
+ }], buttonClick: [{
263
+ type: Output
264
+ }], buttonElRef: [{
265
+ type: ViewChild,
266
+ args: ['buttonEl', { static: true }]
267
+ }] } });
268
+
269
+ class InputComponent {
270
+ ngZone;
271
+ cdr;
272
+ label = '';
273
+ placeholder = '';
274
+ disabled = false;
275
+ error = null;
276
+ required = false;
277
+ value = '';
278
+ inputId = `smui-input-${Date.now()}`;
279
+ valueChange = new EventEmitter();
280
+ inputElRef;
281
+ labelElRef;
282
+ errorElRef;
283
+ api;
284
+ adapter;
285
+ listenerCleanups = [];
286
+ constructor(ngZone, cdr) {
287
+ this.ngZone = ngZone;
288
+ this.cdr = cdr;
289
+ }
290
+ ngOnInit() {
291
+ this.adapter = MachineAdapter.create(this.ngZone, createInputMachine({
292
+ value: this.value,
293
+ disabled: this.disabled,
294
+ required: this.required,
295
+ error: this.error,
296
+ placeholder: this.placeholder,
297
+ label: this.label,
298
+ id: this.inputId,
299
+ }));
300
+ this.updateApi();
301
+ this.applyPropsToInput();
302
+ }
303
+ ngOnChanges(changes) {
304
+ if (!this.adapter)
305
+ return;
306
+ if (changes['error']) {
307
+ this.adapter.send({ type: 'SET_ERROR', error: this.error });
308
+ }
309
+ if (changes['value'] && !changes['value'].firstChange) {
310
+ this.adapter.send({ type: 'CHANGE', value: this.value });
311
+ }
312
+ this.updateApi();
313
+ this.applyPropsToInput();
314
+ }
315
+ ngOnDestroy() {
316
+ this.cleanupListeners();
317
+ this.adapter?.destroy();
318
+ }
319
+ updateApi() {
320
+ this.api = connectInput({
321
+ state: this.adapter.state,
322
+ context: this.adapter.context,
323
+ send: (event) => {
324
+ this.adapter.send(event);
325
+ this.updateApi();
326
+ this.applyPropsToInput();
327
+ this.cdr.markForCheck();
328
+ },
329
+ onChange: (val) => {
330
+ this.valueChange.emit(val);
331
+ },
332
+ });
333
+ }
334
+ applyPropsToInput() {
335
+ if (!this.inputElRef?.nativeElement)
336
+ return;
337
+ this.cleanupListeners();
338
+ const normalized = normalizeProps(this.api.inputProps);
339
+ this.listenerCleanups = applyProps(this.inputElRef.nativeElement, normalized);
340
+ // Apply label props
341
+ if (this.labelElRef?.nativeElement) {
342
+ const labelNorm = normalizeProps(this.api.labelProps);
343
+ this.listenerCleanups.push(...applyProps(this.labelElRef.nativeElement, labelNorm));
344
+ }
345
+ // Apply error props
346
+ if (this.errorElRef?.nativeElement) {
347
+ const errorNorm = normalizeProps(this.api.errorProps);
348
+ this.listenerCleanups.push(...applyProps(this.errorElRef.nativeElement, errorNorm));
349
+ }
350
+ }
351
+ cleanupListeners() {
352
+ for (const cleanup of this.listenerCleanups) {
353
+ cleanup();
354
+ }
355
+ this.listenerCleanups = [];
356
+ }
357
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: InputComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
358
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: InputComponent, isStandalone: true, selector: "smui-input", inputs: { label: "label", placeholder: "placeholder", disabled: "disabled", error: "error", required: "required", value: "value", inputId: "inputId" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "inputElRef", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "labelElRef", first: true, predicate: ["labelEl"], descendants: true }, { propertyName: "errorElRef", first: true, predicate: ["errorEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
359
+ <div
360
+ class="smui-input"
361
+ [attr.data-state]="api.state"
362
+ [attr.data-disabled]="disabled ? '' : null"
363
+ >
364
+ <label
365
+ *ngIf="label"
366
+ #labelEl
367
+ class="smui-input__label"
368
+ [attr.for]="inputId"
369
+ >
370
+ {{ label }}
371
+ <span *ngIf="required" class="smui-input__required" aria-hidden="true">*</span>
372
+ </label>
373
+
374
+ <input
375
+ #inputEl
376
+ class="smui-input__field"
377
+ [attr.id]="inputId"
378
+ [attr.placeholder]="placeholder"
379
+ [value]="api.value"
380
+ />
381
+
382
+ <div
383
+ *ngIf="api.hasError && error"
384
+ #errorEl
385
+ class="smui-input__error"
386
+ role="alert"
387
+ aria-live="polite"
388
+ >
389
+ {{ error }}
390
+ </div>
391
+ </div>
392
+ `, isInline: true, styles: [":host{display:block}.smui-input__label{display:block;margin-bottom:4px}.smui-input__required{color:var(--smui-color-danger, #dc2626);margin-left:2px}.smui-input__field{display:block;width:100%;box-sizing:border-box}.smui-input__error{color:var(--smui-color-danger, #dc2626);font-size:.875rem;margin-top:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
393
+ }
394
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: InputComponent, decorators: [{
395
+ type: Component,
396
+ args: [{ selector: 'smui-input', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
397
+ <div
398
+ class="smui-input"
399
+ [attr.data-state]="api.state"
400
+ [attr.data-disabled]="disabled ? '' : null"
401
+ >
402
+ <label
403
+ *ngIf="label"
404
+ #labelEl
405
+ class="smui-input__label"
406
+ [attr.for]="inputId"
407
+ >
408
+ {{ label }}
409
+ <span *ngIf="required" class="smui-input__required" aria-hidden="true">*</span>
410
+ </label>
411
+
412
+ <input
413
+ #inputEl
414
+ class="smui-input__field"
415
+ [attr.id]="inputId"
416
+ [attr.placeholder]="placeholder"
417
+ [value]="api.value"
418
+ />
419
+
420
+ <div
421
+ *ngIf="api.hasError && error"
422
+ #errorEl
423
+ class="smui-input__error"
424
+ role="alert"
425
+ aria-live="polite"
426
+ >
427
+ {{ error }}
428
+ </div>
429
+ </div>
430
+ `, styles: [":host{display:block}.smui-input__label{display:block;margin-bottom:4px}.smui-input__required{color:var(--smui-color-danger, #dc2626);margin-left:2px}.smui-input__field{display:block;width:100%;box-sizing:border-box}.smui-input__error{color:var(--smui-color-danger, #dc2626);font-size:.875rem;margin-top:4px}\n"] }]
431
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { label: [{
432
+ type: Input
433
+ }], placeholder: [{
434
+ type: Input
435
+ }], disabled: [{
436
+ type: Input
437
+ }], error: [{
438
+ type: Input
439
+ }], required: [{
440
+ type: Input
441
+ }], value: [{
442
+ type: Input
443
+ }], inputId: [{
444
+ type: Input
445
+ }], valueChange: [{
446
+ type: Output
447
+ }], inputElRef: [{
448
+ type: ViewChild,
449
+ args: ['inputEl', { static: true }]
450
+ }], labelElRef: [{
451
+ type: ViewChild,
452
+ args: ['labelEl']
453
+ }], errorElRef: [{
454
+ type: ViewChild,
455
+ args: ['errorEl']
456
+ }] } });
457
+
458
+ class ModalComponent {
459
+ ngZone;
460
+ cdr;
461
+ closeOnOverlayClick = true;
462
+ closeOnEscape = true;
463
+ openChange = new EventEmitter();
464
+ triggerElRef;
465
+ contentElRef;
466
+ overlayElRef;
467
+ closeElRef;
468
+ api;
469
+ titleId = `smui-modal-title-${Date.now()}`;
470
+ adapter;
471
+ constructor(ngZone, cdr) {
472
+ this.ngZone = ngZone;
473
+ this.cdr = cdr;
474
+ }
475
+ ngOnInit() {
476
+ this.adapter = MachineAdapter.create(this.ngZone, createModalMachine({
477
+ closeOnOverlayClick: this.closeOnOverlayClick,
478
+ closeOnEscape: this.closeOnEscape,
479
+ }));
480
+ this.updateApi();
481
+ }
482
+ ngOnChanges(changes) {
483
+ // Modal machine doesn't have dynamic context updates for these,
484
+ // but we store them for local use in event handlers.
485
+ }
486
+ ngOnDestroy() {
487
+ this.adapter?.destroy();
488
+ }
489
+ /** Open the modal programmatically. */
490
+ open() {
491
+ this.adapter.send({ type: 'OPEN' });
492
+ this.updateApi();
493
+ this.openChange.emit(true);
494
+ this.cdr.markForCheck();
495
+ }
496
+ /** Close the modal programmatically. */
497
+ close() {
498
+ this.adapter.send({ type: 'CLOSE' });
499
+ this.updateApi();
500
+ this.openChange.emit(false);
501
+ this.cdr.markForCheck();
502
+ }
503
+ onOverlayClick(event) {
504
+ if (this.closeOnOverlayClick) {
505
+ this.close();
506
+ }
507
+ }
508
+ handleKeydown(event) {
509
+ if (this.closeOnEscape && event.key === 'Escape' && this.api.isOpen) {
510
+ this.close();
511
+ }
512
+ }
513
+ updateApi() {
514
+ this.api = connectModal({
515
+ state: this.adapter.state,
516
+ context: this.adapter.context,
517
+ send: (event) => {
518
+ this.adapter.send(event);
519
+ this.updateApi();
520
+ this.cdr.markForCheck();
521
+ },
522
+ onOpenChange: (isOpen) => {
523
+ this.openChange.emit(isOpen);
524
+ },
525
+ });
526
+ }
527
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ModalComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
528
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ModalComponent, isStandalone: true, selector: "smui-modal", inputs: { closeOnOverlayClick: "closeOnOverlayClick", closeOnEscape: "closeOnEscape" }, outputs: { openChange: "openChange" }, host: { listeners: { "document:keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "triggerElRef", first: true, predicate: ["triggerEl"], descendants: true }, { propertyName: "contentElRef", first: true, predicate: ["contentEl"], descendants: true }, { propertyName: "overlayElRef", first: true, predicate: ["overlayEl"], descendants: true }, { propertyName: "closeElRef", first: true, predicate: ["closeEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
529
+ <!-- Trigger slot -->
530
+ <div #triggerEl class="smui-modal__trigger" (click)="open()">
531
+ <ng-content select="[smuiModalTrigger]"></ng-content>
532
+ </div>
533
+
534
+ <!-- Overlay + Content (portal-like fixed overlay) -->
535
+ <ng-container *ngIf="api.isOpen">
536
+ <div
537
+ #overlayEl
538
+ class="smui-modal__overlay"
539
+ [attr.data-state]="api.state"
540
+ (click)="onOverlayClick($event)"
541
+ ></div>
542
+ <div
543
+ #contentEl
544
+ class="smui-modal__content"
545
+ [attr.data-state]="api.state"
546
+ role="dialog"
547
+ aria-modal="true"
548
+ [attr.aria-labelledby]="titleId"
549
+ tabindex="-1"
550
+ >
551
+ <div class="smui-modal__header">
552
+ <div [attr.id]="titleId" class="smui-modal__title">
553
+ <ng-content select="[smuiModalTitle]"></ng-content>
554
+ </div>
555
+ <button
556
+ #closeEl
557
+ class="smui-modal__close"
558
+ aria-label="Close"
559
+ (click)="close()"
560
+ type="button"
561
+ >
562
+ &times;
563
+ </button>
564
+ </div>
565
+ <div class="smui-modal__body">
566
+ <ng-content></ng-content>
567
+ </div>
568
+ </div>
569
+ </ng-container>
570
+ `, isInline: true, styles: [":host{display:inline-block}.smui-modal__overlay{position:fixed;inset:0;background:#00000080;z-index:999}.smui-modal__content{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--smui-color-surface, #ffffff);border-radius:8px;box-shadow:0 20px 60px #0000004d;z-index:1000;min-width:320px;max-width:90vw;max-height:90vh;overflow-y:auto;outline:none}.smui-modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--smui-color-border, #e5e7eb)}.smui-modal__title{font-weight:600;font-size:1.125rem}.smui-modal__close{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:4px 8px;line-height:1;border-radius:4px}.smui-modal__close:hover{background:var(--smui-color-hover, #f3f4f6)}.smui-modal__body{padding:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
571
+ }
572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ModalComponent, decorators: [{
573
+ type: Component,
574
+ args: [{ selector: 'smui-modal', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
575
+ <!-- Trigger slot -->
576
+ <div #triggerEl class="smui-modal__trigger" (click)="open()">
577
+ <ng-content select="[smuiModalTrigger]"></ng-content>
578
+ </div>
579
+
580
+ <!-- Overlay + Content (portal-like fixed overlay) -->
581
+ <ng-container *ngIf="api.isOpen">
582
+ <div
583
+ #overlayEl
584
+ class="smui-modal__overlay"
585
+ [attr.data-state]="api.state"
586
+ (click)="onOverlayClick($event)"
587
+ ></div>
588
+ <div
589
+ #contentEl
590
+ class="smui-modal__content"
591
+ [attr.data-state]="api.state"
592
+ role="dialog"
593
+ aria-modal="true"
594
+ [attr.aria-labelledby]="titleId"
595
+ tabindex="-1"
596
+ >
597
+ <div class="smui-modal__header">
598
+ <div [attr.id]="titleId" class="smui-modal__title">
599
+ <ng-content select="[smuiModalTitle]"></ng-content>
600
+ </div>
601
+ <button
602
+ #closeEl
603
+ class="smui-modal__close"
604
+ aria-label="Close"
605
+ (click)="close()"
606
+ type="button"
607
+ >
608
+ &times;
609
+ </button>
610
+ </div>
611
+ <div class="smui-modal__body">
612
+ <ng-content></ng-content>
613
+ </div>
614
+ </div>
615
+ </ng-container>
616
+ `, styles: [":host{display:inline-block}.smui-modal__overlay{position:fixed;inset:0;background:#00000080;z-index:999}.smui-modal__content{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--smui-color-surface, #ffffff);border-radius:8px;box-shadow:0 20px 60px #0000004d;z-index:1000;min-width:320px;max-width:90vw;max-height:90vh;overflow-y:auto;outline:none}.smui-modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--smui-color-border, #e5e7eb)}.smui-modal__title{font-weight:600;font-size:1.125rem}.smui-modal__close{background:none;border:none;font-size:1.5rem;cursor:pointer;padding:4px 8px;line-height:1;border-radius:4px}.smui-modal__close:hover{background:var(--smui-color-hover, #f3f4f6)}.smui-modal__body{padding:24px}\n"] }]
617
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { closeOnOverlayClick: [{
618
+ type: Input
619
+ }], closeOnEscape: [{
620
+ type: Input
621
+ }], openChange: [{
622
+ type: Output
623
+ }], triggerElRef: [{
624
+ type: ViewChild,
625
+ args: ['triggerEl']
626
+ }], contentElRef: [{
627
+ type: ViewChild,
628
+ args: ['contentEl']
629
+ }], overlayElRef: [{
630
+ type: ViewChild,
631
+ args: ['overlayEl']
632
+ }], closeElRef: [{
633
+ type: ViewChild,
634
+ args: ['closeEl']
635
+ }], handleKeydown: [{
636
+ type: HostListener,
637
+ args: ['document:keydown', ['$event']]
638
+ }] } });
639
+
640
+ class TypographyComponent {
641
+ renderer;
642
+ variant = 'body1';
643
+ align;
644
+ truncate = false;
645
+ color;
646
+ element = 'p';
647
+ api;
648
+ set textElRef(ref) {
649
+ if (ref) {
650
+ this.applyStyles(ref.nativeElement);
651
+ }
652
+ }
653
+ constructor(renderer) {
654
+ this.renderer = renderer;
655
+ }
656
+ ngOnInit() {
657
+ this.updateApi();
658
+ }
659
+ ngOnChanges(changes) {
660
+ this.updateApi();
661
+ }
662
+ updateApi() {
663
+ this.api = connectTypography({
664
+ variant: this.variant,
665
+ align: this.align,
666
+ truncate: this.truncate,
667
+ color: this.color,
668
+ });
669
+ this.element = this.api.element;
670
+ }
671
+ applyStyles(el) {
672
+ if (!this.api)
673
+ return;
674
+ const rootProps = this.api.rootProps;
675
+ // Apply data attributes
676
+ for (const [key, value] of Object.entries(rootProps)) {
677
+ if (key === 'style' || value === undefined)
678
+ continue;
679
+ this.renderer.setAttribute(el, key, String(value));
680
+ }
681
+ // Apply styles
682
+ if (rootProps.style && typeof rootProps.style === 'object') {
683
+ for (const [prop, value] of Object.entries(rootProps.style)) {
684
+ this.renderer.setStyle(el, prop, String(value));
685
+ }
686
+ }
687
+ }
688
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: TypographyComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
689
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: TypographyComponent, isStandalone: true, selector: "smui-typography", inputs: { variant: "variant", align: "align", truncate: "truncate", color: "color" }, viewQueries: [{ propertyName: "textElRef", first: true, predicate: ["textEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
690
+ <ng-container [ngSwitch]="element">
691
+ <h1 *ngSwitchCase="'h1'" #textEl class="smui-typography" [attr.data-variant]="variant">
692
+ <ng-container *ngTemplateOutlet="content"></ng-container>
693
+ </h1>
694
+ <h2 *ngSwitchCase="'h2'" #textEl class="smui-typography" [attr.data-variant]="variant">
695
+ <ng-container *ngTemplateOutlet="content"></ng-container>
696
+ </h2>
697
+ <h3 *ngSwitchCase="'h3'" #textEl class="smui-typography" [attr.data-variant]="variant">
698
+ <ng-container *ngTemplateOutlet="content"></ng-container>
699
+ </h3>
700
+ <h4 *ngSwitchCase="'h4'" #textEl class="smui-typography" [attr.data-variant]="variant">
701
+ <ng-container *ngTemplateOutlet="content"></ng-container>
702
+ </h4>
703
+ <h5 *ngSwitchCase="'h5'" #textEl class="smui-typography" [attr.data-variant]="variant">
704
+ <ng-container *ngTemplateOutlet="content"></ng-container>
705
+ </h5>
706
+ <h6 *ngSwitchCase="'h6'" #textEl class="smui-typography" [attr.data-variant]="variant">
707
+ <ng-container *ngTemplateOutlet="content"></ng-container>
708
+ </h6>
709
+ <span *ngSwitchCase="'span'" #textEl class="smui-typography" [attr.data-variant]="variant">
710
+ <ng-container *ngTemplateOutlet="content"></ng-container>
711
+ </span>
712
+ <p *ngSwitchDefault #textEl class="smui-typography" [attr.data-variant]="variant">
713
+ <ng-container *ngTemplateOutlet="content"></ng-container>
714
+ </p>
715
+ </ng-container>
716
+
717
+ <ng-template #content>
718
+ <ng-content></ng-content>
719
+ </ng-template>
720
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
721
+ }
722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: TypographyComponent, decorators: [{
723
+ type: Component,
724
+ args: [{ selector: 'smui-typography', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
725
+ <ng-container [ngSwitch]="element">
726
+ <h1 *ngSwitchCase="'h1'" #textEl class="smui-typography" [attr.data-variant]="variant">
727
+ <ng-container *ngTemplateOutlet="content"></ng-container>
728
+ </h1>
729
+ <h2 *ngSwitchCase="'h2'" #textEl class="smui-typography" [attr.data-variant]="variant">
730
+ <ng-container *ngTemplateOutlet="content"></ng-container>
731
+ </h2>
732
+ <h3 *ngSwitchCase="'h3'" #textEl class="smui-typography" [attr.data-variant]="variant">
733
+ <ng-container *ngTemplateOutlet="content"></ng-container>
734
+ </h3>
735
+ <h4 *ngSwitchCase="'h4'" #textEl class="smui-typography" [attr.data-variant]="variant">
736
+ <ng-container *ngTemplateOutlet="content"></ng-container>
737
+ </h4>
738
+ <h5 *ngSwitchCase="'h5'" #textEl class="smui-typography" [attr.data-variant]="variant">
739
+ <ng-container *ngTemplateOutlet="content"></ng-container>
740
+ </h5>
741
+ <h6 *ngSwitchCase="'h6'" #textEl class="smui-typography" [attr.data-variant]="variant">
742
+ <ng-container *ngTemplateOutlet="content"></ng-container>
743
+ </h6>
744
+ <span *ngSwitchCase="'span'" #textEl class="smui-typography" [attr.data-variant]="variant">
745
+ <ng-container *ngTemplateOutlet="content"></ng-container>
746
+ </span>
747
+ <p *ngSwitchDefault #textEl class="smui-typography" [attr.data-variant]="variant">
748
+ <ng-container *ngTemplateOutlet="content"></ng-container>
749
+ </p>
750
+ </ng-container>
751
+
752
+ <ng-template #content>
753
+ <ng-content></ng-content>
754
+ </ng-template>
755
+ `, styles: [":host{display:block}\n"] }]
756
+ }], ctorParameters: () => [{ type: i0.Renderer2 }], propDecorators: { variant: [{
757
+ type: Input
758
+ }], align: [{
759
+ type: Input
760
+ }], truncate: [{
761
+ type: Input
762
+ }], color: [{
763
+ type: Input
764
+ }], textElRef: [{
765
+ type: ViewChild,
766
+ args: ['textEl', { static: false }]
767
+ }] } });
768
+
769
+ // ---------------------------------------------------------------------------
770
+ // CardHeaderComponent
771
+ // ---------------------------------------------------------------------------
772
+ class CardHeaderComponent {
773
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
774
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CardHeaderComponent, isStandalone: true, selector: "smui-card-header", ngImport: i0, template: `
775
+ <div #headerEl class="smui-card__header" data-part="header">
776
+ <ng-content></ng-content>
777
+ </div>
778
+ `, isInline: true, styles: [":host{display:block}.smui-card__header{padding:var(--smui-card-header-padding, 16px 24px);border-bottom:1px solid var(--smui-color-border, #e5e7eb)}\n"] });
779
+ }
780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardHeaderComponent, decorators: [{
781
+ type: Component,
782
+ args: [{ selector: 'smui-card-header', standalone: true, template: `
783
+ <div #headerEl class="smui-card__header" data-part="header">
784
+ <ng-content></ng-content>
785
+ </div>
786
+ `, styles: [":host{display:block}.smui-card__header{padding:var(--smui-card-header-padding, 16px 24px);border-bottom:1px solid var(--smui-color-border, #e5e7eb)}\n"] }]
787
+ }] });
788
+ // ---------------------------------------------------------------------------
789
+ // CardBodyComponent
790
+ // ---------------------------------------------------------------------------
791
+ class CardBodyComponent {
792
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
793
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CardBodyComponent, isStandalone: true, selector: "smui-card-body", ngImport: i0, template: `
794
+ <div #bodyEl class="smui-card__body" data-part="body">
795
+ <ng-content></ng-content>
796
+ </div>
797
+ `, isInline: true, styles: [":host{display:block}.smui-card__body{padding:var(--smui-card-body-padding, 24px)}\n"] });
798
+ }
799
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardBodyComponent, decorators: [{
800
+ type: Component,
801
+ args: [{ selector: 'smui-card-body', standalone: true, template: `
802
+ <div #bodyEl class="smui-card__body" data-part="body">
803
+ <ng-content></ng-content>
804
+ </div>
805
+ `, styles: [":host{display:block}.smui-card__body{padding:var(--smui-card-body-padding, 24px)}\n"] }]
806
+ }] });
807
+ // ---------------------------------------------------------------------------
808
+ // CardFooterComponent
809
+ // ---------------------------------------------------------------------------
810
+ class CardFooterComponent {
811
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
812
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CardFooterComponent, isStandalone: true, selector: "smui-card-footer", ngImport: i0, template: `
813
+ <div #footerEl class="smui-card__footer" data-part="footer">
814
+ <ng-content></ng-content>
815
+ </div>
816
+ `, isInline: true, styles: [":host{display:block}.smui-card__footer{padding:var(--smui-card-footer-padding, 16px 24px);border-top:1px solid var(--smui-color-border, #e5e7eb)}\n"] });
817
+ }
818
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardFooterComponent, decorators: [{
819
+ type: Component,
820
+ args: [{ selector: 'smui-card-footer', standalone: true, template: `
821
+ <div #footerEl class="smui-card__footer" data-part="footer">
822
+ <ng-content></ng-content>
823
+ </div>
824
+ `, styles: [":host{display:block}.smui-card__footer{padding:var(--smui-card-footer-padding, 16px 24px);border-top:1px solid var(--smui-color-border, #e5e7eb)}\n"] }]
825
+ }] });
826
+ // ---------------------------------------------------------------------------
827
+ // CardComponent
828
+ // ---------------------------------------------------------------------------
829
+ class CardComponent {
830
+ variant = 'elevated';
831
+ padding = 'md';
832
+ interactive = false;
833
+ cardClick = new EventEmitter();
834
+ cardElRef;
835
+ api;
836
+ listenerCleanups = [];
837
+ ngOnInit() {
838
+ this.updateApi();
839
+ }
840
+ ngOnChanges(changes) {
841
+ this.updateApi();
842
+ }
843
+ onCardClick(event) {
844
+ if (this.interactive) {
845
+ this.cardClick.emit(event);
846
+ }
847
+ }
848
+ onCardKeydown(event) {
849
+ if (this.interactive && (event.key === 'Enter' || event.key === ' ')) {
850
+ event.preventDefault();
851
+ this.cardClick.emit(event);
852
+ }
853
+ }
854
+ updateApi() {
855
+ this.api = connectCard({
856
+ variant: this.variant,
857
+ padding: this.padding,
858
+ interactive: this.interactive,
859
+ onClick: (event) => {
860
+ this.cardClick.emit(event);
861
+ },
862
+ });
863
+ }
864
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
865
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CardComponent, isStandalone: true, selector: "smui-card", inputs: { variant: "variant", padding: "padding", interactive: "interactive" }, outputs: { cardClick: "cardClick" }, viewQueries: [{ propertyName: "cardElRef", first: true, predicate: ["cardEl"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
866
+ <div
867
+ #cardEl
868
+ class="smui-card"
869
+ [class.smui-card--interactive]="interactive"
870
+ [attr.data-variant]="variant"
871
+ [attr.data-padding]="padding"
872
+ [attr.role]="interactive ? 'button' : null"
873
+ [attr.tabindex]="interactive ? 0 : null"
874
+ (click)="onCardClick($event)"
875
+ (keydown)="onCardKeydown($event)"
876
+ >
877
+ <ng-content></ng-content>
878
+ </div>
879
+ `, isInline: true, styles: [":host{display:block}.smui-card{border-radius:var(--smui-card-radius, 8px);overflow:hidden}.smui-card[data-variant=elevated]{box-shadow:var( --smui-card-shadow, 0 1px 3px rgba(0, 0, 0, .1), 0 1px 2px rgba(0, 0, 0, .06) )}.smui-card[data-variant=outlined]{border:1px solid var(--smui-color-border, #e5e7eb)}.smui-card[data-variant=filled]{background:var(--smui-color-surface, #f9fafb)}.smui-card--interactive{cursor:pointer;transition:box-shadow .2s ease}.smui-card--interactive:hover{box-shadow:0 4px 12px #00000026}.smui-card--interactive:focus-visible{outline:2px solid var(--smui-color-primary, #6200ee);outline-offset:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
880
+ }
881
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CardComponent, decorators: [{
882
+ type: Component,
883
+ args: [{ selector: 'smui-card', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
884
+ <div
885
+ #cardEl
886
+ class="smui-card"
887
+ [class.smui-card--interactive]="interactive"
888
+ [attr.data-variant]="variant"
889
+ [attr.data-padding]="padding"
890
+ [attr.role]="interactive ? 'button' : null"
891
+ [attr.tabindex]="interactive ? 0 : null"
892
+ (click)="onCardClick($event)"
893
+ (keydown)="onCardKeydown($event)"
894
+ >
895
+ <ng-content></ng-content>
896
+ </div>
897
+ `, styles: [":host{display:block}.smui-card{border-radius:var(--smui-card-radius, 8px);overflow:hidden}.smui-card[data-variant=elevated]{box-shadow:var( --smui-card-shadow, 0 1px 3px rgba(0, 0, 0, .1), 0 1px 2px rgba(0, 0, 0, .06) )}.smui-card[data-variant=outlined]{border:1px solid var(--smui-color-border, #e5e7eb)}.smui-card[data-variant=filled]{background:var(--smui-color-surface, #f9fafb)}.smui-card--interactive{cursor:pointer;transition:box-shadow .2s ease}.smui-card--interactive:hover{box-shadow:0 4px 12px #00000026}.smui-card--interactive:focus-visible{outline:2px solid var(--smui-color-primary, #6200ee);outline-offset:2px}\n"] }]
898
+ }], propDecorators: { variant: [{
899
+ type: Input
900
+ }], padding: [{
901
+ type: Input
902
+ }], interactive: [{
903
+ type: Input
904
+ }], cardClick: [{
905
+ type: Output
906
+ }], cardElRef: [{
907
+ type: ViewChild,
908
+ args: ['cardEl', { static: true }]
909
+ }] } });
910
+
911
+ /**
912
+ * SmuiModule re-exports every standalone component so consumers who prefer
913
+ * the traditional NgModule approach can simply import this single module.
914
+ *
915
+ * For tree-shakeable imports, use the standalone components directly:
916
+ * `imports: [ButtonComponent]`
917
+ */
918
+ class SmuiModule {
919
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SmuiModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
920
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: SmuiModule, imports: [ButtonComponent,
921
+ InputComponent,
922
+ ModalComponent,
923
+ TypographyComponent,
924
+ CardComponent,
925
+ CardHeaderComponent,
926
+ CardBodyComponent,
927
+ CardFooterComponent], exports: [ButtonComponent,
928
+ InputComponent,
929
+ ModalComponent,
930
+ TypographyComponent,
931
+ CardComponent,
932
+ CardHeaderComponent,
933
+ CardBodyComponent,
934
+ CardFooterComponent] });
935
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SmuiModule, imports: [ButtonComponent,
936
+ InputComponent,
937
+ ModalComponent,
938
+ TypographyComponent,
939
+ CardComponent] });
940
+ }
941
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SmuiModule, decorators: [{
942
+ type: NgModule,
943
+ args: [{
944
+ imports: [
945
+ ButtonComponent,
946
+ InputComponent,
947
+ ModalComponent,
948
+ TypographyComponent,
949
+ CardComponent,
950
+ CardHeaderComponent,
951
+ CardBodyComponent,
952
+ CardFooterComponent,
953
+ ],
954
+ exports: [
955
+ ButtonComponent,
956
+ InputComponent,
957
+ ModalComponent,
958
+ TypographyComponent,
959
+ CardComponent,
960
+ CardHeaderComponent,
961
+ CardBodyComponent,
962
+ CardFooterComponent,
963
+ ],
964
+ }]
965
+ }] });
966
+
967
+ /**
968
+ * ThemeService manages design tokens as CSS custom properties on the
969
+ * document's root element (`<html>`). Consumers inject it and call
970
+ * `setTokens()` or `setToken()` to apply a theme at runtime.
971
+ *
972
+ * It is provided in 'root' so there is a single, application-wide instance.
973
+ */
974
+ class ThemeService {
975
+ document;
976
+ tokens = new Map();
977
+ rootElement = null;
978
+ constructor(document) {
979
+ this.document = document;
980
+ this.rootElement = this.document.documentElement;
981
+ }
982
+ /**
983
+ * Bulk-set multiple design tokens.
984
+ *
985
+ * ```ts
986
+ * themeService.setTokens({
987
+ * 'smui-color-primary': '#6200ee',
988
+ * 'smui-color-secondary': '#03dac6',
989
+ * 'smui-spacing-md': '16px',
990
+ * });
991
+ * ```
992
+ */
993
+ setTokens(tokens) {
994
+ for (const [name, value] of Object.entries(tokens)) {
995
+ this.setToken(name, value);
996
+ }
997
+ }
998
+ /** Set a single design token. */
999
+ setToken(name, value) {
1000
+ this.tokens.set(name, value);
1001
+ this.applyToken(name, value);
1002
+ }
1003
+ /** Remove a single token override, reverting to the default value. */
1004
+ removeToken(name) {
1005
+ this.tokens.delete(name);
1006
+ if (this.rootElement) {
1007
+ this.rootElement.style.removeProperty(`--${name}`);
1008
+ }
1009
+ }
1010
+ /** Remove all token overrides. */
1011
+ resetTokens() {
1012
+ for (const name of this.tokens.keys()) {
1013
+ if (this.rootElement) {
1014
+ this.rootElement.style.removeProperty(`--${name}`);
1015
+ }
1016
+ }
1017
+ this.tokens.clear();
1018
+ }
1019
+ /** Get the current value of a token (returns the override, not the computed). */
1020
+ getToken(name) {
1021
+ return this.tokens.get(name);
1022
+ }
1023
+ /** Get all currently applied token overrides. */
1024
+ getAllTokens() {
1025
+ const result = {};
1026
+ for (const [name, value] of this.tokens.entries()) {
1027
+ result[name] = value;
1028
+ }
1029
+ return result;
1030
+ }
1031
+ applyToken(name, value) {
1032
+ if (this.rootElement) {
1033
+ this.rootElement.style.setProperty(`--${name}`, value);
1034
+ }
1035
+ }
1036
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ThemeService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
1037
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ThemeService, providedIn: 'root' });
1038
+ }
1039
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ThemeService, decorators: [{
1040
+ type: Injectable,
1041
+ args: [{ providedIn: 'root' }]
1042
+ }], ctorParameters: () => [{ type: Document, decorators: [{
1043
+ type: Inject,
1044
+ args: [DOCUMENT]
1045
+ }] }] });
1046
+
1047
+ // Module
1048
+
1049
+ /**
1050
+ * Generated bundle index. Do not edit.
1051
+ */
1052
+
1053
+ export { ButtonComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, InputComponent, MachineAdapter, ModalComponent, SmuiModule, ThemeService, TypographyComponent, applyProps, normalizeProps };
1054
+ //# sourceMappingURL=smui-angular.mjs.map