@letsprogram/ng-oat 0.1.2 → 0.1.3
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 +5 -7
- package/assets/oat/css/utilities.css +4 -0
- package/assets/oat/oat.min.css +16 -0
- package/fesm2022/letsprogram-ng-oat.mjs +714 -74
- package/package.json +1 -1
- package/types/letsprogram-ng-oat.d.ts +202 -32
- package/assets/oat/js/base.js +0 -107
- package/assets/oat/js/dropdown.js +0 -74
- package/assets/oat/js/index.js +0 -12
- package/assets/oat/js/sidebar.js +0 -22
- package/assets/oat/js/tabs.js +0 -94
- package/assets/oat/js/toast.js +0 -144
- package/assets/oat/js/tooltip.js +0 -36
- package/assets/oat/oat.js +0 -342
- package/assets/oat/oat.min.js +0 -267
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { makeEnvironmentProviders, provideAppInitializer, inject, PLATFORM_ID, Injectable, ElementRef, DestroyRef, signal, output, afterNextRender, Directive, InjectionToken, Renderer2, ViewContainerRef, input, TemplateRef, effect, computed, Component, model, CUSTOM_ELEMENTS_SCHEMA, viewChild, viewChildren, contentChildren } from '@angular/core';
|
|
3
|
-
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
2
|
+
import { makeEnvironmentProviders, provideAppInitializer, inject, PLATFORM_ID, Injectable, ElementRef, DestroyRef, signal, output, afterNextRender, Directive, InjectionToken, Renderer2, ViewContainerRef, input, TemplateRef, effect, computed, Component, model, CUSTOM_ELEMENTS_SCHEMA, viewChild, viewChildren, contentChildren, contentChild } from '@angular/core';
|
|
3
|
+
import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_OPTIONS = {
|
|
6
|
-
assets: { css: false
|
|
7
|
-
registerWebComponents: true,
|
|
6
|
+
assets: { css: false },
|
|
8
7
|
basePath: 'assets/oat',
|
|
9
8
|
};
|
|
10
|
-
function injectTag(doc,
|
|
9
|
+
function injectTag(doc, attrs, id) {
|
|
11
10
|
return new Promise((resolve, reject) => {
|
|
12
11
|
if (doc.getElementById(id)) {
|
|
13
12
|
resolve();
|
|
14
13
|
return;
|
|
15
14
|
}
|
|
16
|
-
const el = doc.createElement(
|
|
15
|
+
const el = doc.createElement('link');
|
|
17
16
|
el.id = id;
|
|
18
17
|
Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
|
|
19
18
|
el.addEventListener('load', () => resolve());
|
|
20
|
-
el.addEventListener('error', () => reject(new Error(`Failed to load
|
|
19
|
+
el.addEventListener('error', () => reject(new Error(`Failed to load link: ${attrs['href']}`)));
|
|
21
20
|
doc.head.appendChild(el);
|
|
22
21
|
});
|
|
23
22
|
}
|
|
24
23
|
/**
|
|
25
|
-
* Provides
|
|
24
|
+
* Provides ng-oat core initialisation.
|
|
26
25
|
*
|
|
27
|
-
* By default,
|
|
28
|
-
* `angular.json` `styles`
|
|
26
|
+
* By default, no runtime asset injection occurs — add CSS via
|
|
27
|
+
* `angular.json` `styles` array or `@import` in `styles.css` (recommended).
|
|
28
|
+
* Oat JS is no longer needed; all behavior is handled natively by Angular components.
|
|
29
29
|
*
|
|
30
30
|
* ```ts
|
|
31
|
-
* provideNgOat()
|
|
32
|
-
* provideNgOat({ assets: { css: 'link'
|
|
31
|
+
* provideNgOat() // recommended (CSS via angular.json / styles.css)
|
|
32
|
+
* provideNgOat({ assets: { css: 'link' } }) // opt-in runtime CSS injection *
|
|
33
33
|
* ```
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
* `{ glob: '
|
|
35
|
+
* \\* Runtime injection requires an assets glob in angular.json to copy files:
|
|
36
|
+
* `{ glob: '**\\/*', input: 'node_modules/@letsprogram/ng-oat/assets/oat', output: '/assets/oat' }`
|
|
37
37
|
*/
|
|
38
38
|
function provideNgOat(options) {
|
|
39
39
|
const opts = {
|
|
@@ -49,17 +49,12 @@ function provideNgOat(options) {
|
|
|
49
49
|
return;
|
|
50
50
|
const base = opts.basePath.replace(/\/+$/, '');
|
|
51
51
|
const promises = [];
|
|
52
|
-
// CSS
|
|
52
|
+
// CSS — only when explicitly opted in
|
|
53
53
|
if (opts.assets.css === 'link') {
|
|
54
|
-
promises.push(injectTag(doc,
|
|
55
|
-
|
|
56
|
-
promises.push(injectTag(doc, 'link', { rel: 'stylesheet', href: `${base}/tokens.css` }, 'ng-oat-tokens-css'));
|
|
54
|
+
promises.push(injectTag(doc, { rel: 'stylesheet', href: `${base}/oat.min.css` }, 'ng-oat-css'));
|
|
55
|
+
promises.push(injectTag(doc, { rel: 'stylesheet', href: `${base}/tokens.css` }, 'ng-oat-tokens-css'));
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
if (opts.assets.js === 'script') {
|
|
60
|
-
promises.push(injectTag(doc, 'script', { src: `${base}/oat.min.js`, defer: '' }, 'ng-oat-js'));
|
|
61
|
-
}
|
|
62
|
-
return Promise.all(promises).then(() => { });
|
|
57
|
+
return promises.length ? Promise.all(promises).then(() => { }) : undefined;
|
|
63
58
|
}),
|
|
64
59
|
]);
|
|
65
60
|
}
|
|
@@ -681,7 +676,7 @@ class NgOatSidebar {
|
|
|
681
676
|
this.close();
|
|
682
677
|
}
|
|
683
678
|
});
|
|
684
|
-
// Listen for toggle clicks within our layout
|
|
679
|
+
// Listen for toggle clicks within our layout
|
|
685
680
|
this.listen(host, 'click', (e) => {
|
|
686
681
|
const toggle = e.target.closest('[data-sidebar-toggle]');
|
|
687
682
|
if (toggle) {
|
|
@@ -749,10 +744,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
749
744
|
}], ctorParameters: () => [], propDecorators: { ngOatSidebarScrollLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngOatSidebarScrollLock", required: false }] }], ngOatSidebarChange: [{ type: i0.Output, args: ["ngOatSidebarChange"] }] } });
|
|
750
745
|
|
|
751
746
|
/**
|
|
752
|
-
* Angular
|
|
747
|
+
* Angular directive that provides native tab behaviour on any element
|
|
748
|
+
* containing `role="tablist"` > `role="tab"` buttons and sibling
|
|
749
|
+
* `role="tabpanel"` containers.
|
|
753
750
|
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
751
|
+
* Works without oat.js — handles tab activation, ARIA attributes,
|
|
752
|
+
* panel visibility and full keyboard navigation internally.
|
|
756
753
|
*
|
|
757
754
|
* Usage:
|
|
758
755
|
* ```html
|
|
@@ -777,36 +774,98 @@ class NgOatTabs {
|
|
|
777
774
|
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
|
|
778
775
|
/** Emits on tab change with { index, tab } */
|
|
779
776
|
ngOatTabChange = output();
|
|
780
|
-
|
|
777
|
+
tabEls = [];
|
|
778
|
+
panelEls = [];
|
|
779
|
+
cleanupFns = [];
|
|
781
780
|
constructor() {
|
|
782
781
|
afterNextRender(() => {
|
|
783
782
|
if (!isPlatformBrowser(this.platformId))
|
|
784
783
|
return;
|
|
785
784
|
const host = this.el.nativeElement;
|
|
786
|
-
//
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
785
|
+
// Discover tabs and panels
|
|
786
|
+
const tablist = host.querySelector(':scope > [role="tablist"]');
|
|
787
|
+
this.tabEls = tablist
|
|
788
|
+
? Array.from(tablist.querySelectorAll('[role="tab"]'))
|
|
789
|
+
: [];
|
|
790
|
+
this.panelEls = Array.from(host.querySelectorAll(':scope > [role="tabpanel"]'));
|
|
791
|
+
// Wire ARIA ids
|
|
792
|
+
this.tabEls.forEach((tab, i) => {
|
|
793
|
+
const panel = this.panelEls[i];
|
|
794
|
+
if (!panel)
|
|
795
|
+
return;
|
|
796
|
+
const tabId = tab.id || `ng-oat-tab-${crypto.randomUUID().slice(0, 8)}`;
|
|
797
|
+
const panelId = panel.id || `ng-oat-panel-${crypto.randomUUID().slice(0, 8)}`;
|
|
798
|
+
tab.id = tabId;
|
|
799
|
+
panel.id = panelId;
|
|
800
|
+
tab.setAttribute('aria-controls', panelId);
|
|
801
|
+
panel.setAttribute('aria-labelledby', tabId);
|
|
802
|
+
});
|
|
803
|
+
// Activate the initial tab
|
|
804
|
+
this.activate(this.activeIndex());
|
|
805
|
+
// Click handler
|
|
806
|
+
const onClick = (e) => {
|
|
807
|
+
const btn = e.target.closest?.('[role="tab"]');
|
|
808
|
+
if (!btn)
|
|
809
|
+
return;
|
|
810
|
+
const idx = this.tabEls.indexOf(btn);
|
|
811
|
+
if (idx >= 0 && !btn.hasAttribute('disabled'))
|
|
812
|
+
this.activate(idx);
|
|
813
|
+
};
|
|
814
|
+
// Keyboard navigation (ArrowLeft / ArrowRight / Home / End)
|
|
815
|
+
const onKeydown = (e) => {
|
|
816
|
+
if (!e.target.closest?.('[role="tab"]'))
|
|
817
|
+
return;
|
|
818
|
+
const len = this.tabEls.length;
|
|
819
|
+
const current = this.activeIndex();
|
|
820
|
+
let next = -1;
|
|
821
|
+
if (e.key === 'ArrowRight')
|
|
822
|
+
next = (current + 1) % len;
|
|
823
|
+
else if (e.key === 'ArrowLeft')
|
|
824
|
+
next = (current - 1 + len) % len;
|
|
825
|
+
else if (e.key === 'Home')
|
|
826
|
+
next = 0;
|
|
827
|
+
else if (e.key === 'End')
|
|
828
|
+
next = len - 1;
|
|
829
|
+
if (next >= 0) {
|
|
830
|
+
e.preventDefault();
|
|
831
|
+
// Skip disabled tabs
|
|
832
|
+
const orig = next;
|
|
833
|
+
const dir = e.key === 'ArrowLeft' || e.key === 'End' ? -1 : 1;
|
|
834
|
+
while (this.tabEls[next]?.hasAttribute('disabled')) {
|
|
835
|
+
next = (next + dir + len) % len;
|
|
836
|
+
if (next === orig)
|
|
837
|
+
return; // all disabled
|
|
838
|
+
}
|
|
839
|
+
this.activate(next);
|
|
840
|
+
this.tabEls[next]?.focus();
|
|
792
841
|
}
|
|
793
842
|
};
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
this.activeIndex.set(host.activeIndex);
|
|
798
|
-
}
|
|
843
|
+
tablist?.addEventListener('click', onClick);
|
|
844
|
+
tablist?.addEventListener('keydown', onKeydown);
|
|
845
|
+
this.cleanupFns.push(() => tablist?.removeEventListener('click', onClick), () => tablist?.removeEventListener('keydown', onKeydown));
|
|
799
846
|
this.destroyRef.onDestroy(() => {
|
|
800
|
-
|
|
847
|
+
this.cleanupFns.forEach(fn => fn());
|
|
848
|
+
this.cleanupFns = [];
|
|
801
849
|
});
|
|
802
850
|
});
|
|
803
851
|
}
|
|
804
852
|
/** Programmatically select a tab by index */
|
|
805
853
|
selectTab(index) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
854
|
+
this.activate(index);
|
|
855
|
+
}
|
|
856
|
+
activate(idx) {
|
|
857
|
+
if (idx < 0 || idx >= this.tabEls.length)
|
|
858
|
+
return;
|
|
859
|
+
this.tabEls.forEach((tab, i) => {
|
|
860
|
+
const isActive = i === idx;
|
|
861
|
+
tab.setAttribute('aria-selected', String(isActive));
|
|
862
|
+
tab.tabIndex = isActive ? 0 : -1;
|
|
863
|
+
});
|
|
864
|
+
this.panelEls.forEach((panel, i) => {
|
|
865
|
+
panel.hidden = i !== idx;
|
|
866
|
+
});
|
|
867
|
+
this.activeIndex.set(idx);
|
|
868
|
+
this.ngOatTabChange.emit({ index: idx, tab: this.tabEls[idx] });
|
|
810
869
|
}
|
|
811
870
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatTabs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
812
871
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: NgOatTabs, isStandalone: true, selector: "ot-tabs[ngOatTabs], [ngOatTabs]", outputs: { ngOatTabChange: "ngOatTabChange" }, exportAs: ["ngOatTabs"], ngImport: i0 });
|
|
@@ -922,10 +981,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
922
981
|
}], ctorParameters: () => [], propDecorators: { ngOatDialogClose: [{ type: i0.Output, args: ["ngOatDialogClose"] }] } });
|
|
923
982
|
|
|
924
983
|
/**
|
|
925
|
-
* Angular service
|
|
984
|
+
* Angular service for Oat-styled toast notifications.
|
|
926
985
|
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
986
|
+
* Fully native implementation — no dependency on oat.js.
|
|
987
|
+
* Uses the Oat CSS `.toast`, `.toast-container`, `data-entering/data-exiting`
|
|
988
|
+
* animation patterns, and popover API for stacking.
|
|
929
989
|
*
|
|
930
990
|
* Usage:
|
|
931
991
|
* ```ts
|
|
@@ -939,11 +999,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
939
999
|
class NgOatToast {
|
|
940
1000
|
platformId = inject(PLATFORM_ID);
|
|
941
1001
|
doc = inject(DOCUMENT);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
return this.doc.defaultView?.ot ?? null;
|
|
946
|
-
}
|
|
1002
|
+
/** Cache of toast containers keyed by placement */
|
|
1003
|
+
containers = new Map();
|
|
1004
|
+
// ── Convenience methods ─────────────────────────────────────────────
|
|
947
1005
|
success(message, title, options) {
|
|
948
1006
|
this.show(message, title, { ...options, variant: 'success' });
|
|
949
1007
|
}
|
|
@@ -956,44 +1014,62 @@ class NgOatToast {
|
|
|
956
1014
|
error(message, title, options) {
|
|
957
1015
|
this.show(message, title, { ...options, variant: 'danger' });
|
|
958
1016
|
}
|
|
1017
|
+
// ── Core show method ────────────────────────────────────────────────
|
|
959
1018
|
show(message, title, options = {}) {
|
|
960
|
-
|
|
961
|
-
if (!ot?.toast) {
|
|
962
|
-
console.warn('ng-oat: Oat JS not loaded. Toast not shown. Ensure oat.min.js is loaded via provideNgOat() or angular.json scripts.');
|
|
1019
|
+
if (!isPlatformBrowser(this.platformId))
|
|
963
1020
|
return;
|
|
1021
|
+
const { variant = 'info', dismissible, ...rest } = options;
|
|
1022
|
+
const el = this.doc.createElement('output');
|
|
1023
|
+
el.setAttribute('data-variant', variant);
|
|
1024
|
+
if (title) {
|
|
1025
|
+
const titleEl = this.doc.createElement('h6');
|
|
1026
|
+
titleEl.className = 'toast-title';
|
|
1027
|
+
titleEl.textContent = title;
|
|
1028
|
+
el.appendChild(titleEl);
|
|
964
1029
|
}
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
1030
|
+
const msgEl = this.doc.createElement('div');
|
|
1031
|
+
msgEl.className = 'toast-message';
|
|
1032
|
+
msgEl.textContent = message;
|
|
1033
|
+
el.appendChild(msgEl);
|
|
1034
|
+
if (dismissible) {
|
|
968
1035
|
this.addCloseButton(el);
|
|
969
1036
|
}
|
|
970
|
-
return el;
|
|
1037
|
+
return this.showEl(el, rest);
|
|
971
1038
|
}
|
|
972
1039
|
/**
|
|
973
1040
|
* Show a toast from a DOM element or template element.
|
|
974
|
-
* For Angular TemplateRef, use showTemplate() instead.
|
|
975
1041
|
*/
|
|
976
1042
|
showElement(element, options = {}) {
|
|
977
|
-
|
|
978
|
-
if (!ot?.toast?.el)
|
|
1043
|
+
if (!isPlatformBrowser(this.platformId))
|
|
979
1044
|
return;
|
|
980
|
-
|
|
1045
|
+
let target;
|
|
1046
|
+
if (element instanceof HTMLTemplateElement) {
|
|
1047
|
+
target = element.content.firstElementChild?.cloneNode(true);
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
target = element.cloneNode(true);
|
|
1051
|
+
}
|
|
1052
|
+
if (!target)
|
|
1053
|
+
return;
|
|
1054
|
+
target.removeAttribute('id');
|
|
1055
|
+
this.showEl(target, options);
|
|
981
1056
|
}
|
|
982
1057
|
/**
|
|
983
1058
|
* Show a toast from an Angular TemplateRef.
|
|
984
1059
|
* Requires a ViewContainerRef to render the template.
|
|
985
1060
|
*/
|
|
986
1061
|
showTemplate(templateRef, vcr, options = {}) {
|
|
987
|
-
|
|
988
|
-
if (!ot?.toast?.el)
|
|
1062
|
+
if (!isPlatformBrowser(this.platformId))
|
|
989
1063
|
return;
|
|
990
1064
|
const view = vcr.createEmbeddedView(templateRef);
|
|
991
1065
|
view.detectChanges();
|
|
992
|
-
// Wrap template nodes in a container
|
|
993
1066
|
const container = this.doc.createElement('div');
|
|
994
1067
|
view.rootNodes.forEach((node) => container.appendChild(node));
|
|
995
|
-
|
|
996
|
-
|
|
1068
|
+
const { dismissible, ...rest } = options;
|
|
1069
|
+
if (dismissible) {
|
|
1070
|
+
this.addCloseButton(container);
|
|
1071
|
+
}
|
|
1072
|
+
this.showEl(container, rest);
|
|
997
1073
|
const duration = options.duration ?? 4000;
|
|
998
1074
|
if (duration > 0) {
|
|
999
1075
|
setTimeout(() => view.destroy(), duration + 500);
|
|
@@ -1001,7 +1077,82 @@ class NgOatToast {
|
|
|
1001
1077
|
}
|
|
1002
1078
|
/** Dismiss toasts. If placement given, only that position; otherwise all. */
|
|
1003
1079
|
dismiss(placement) {
|
|
1004
|
-
this.
|
|
1080
|
+
if (!isPlatformBrowser(this.platformId))
|
|
1081
|
+
return;
|
|
1082
|
+
if (placement) {
|
|
1083
|
+
const c = this.containers.get(placement);
|
|
1084
|
+
if (c) {
|
|
1085
|
+
c.innerHTML = '';
|
|
1086
|
+
c.hidePopover();
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
this.containers.forEach(c => {
|
|
1091
|
+
c.innerHTML = '';
|
|
1092
|
+
c.hidePopover();
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
// ── Internal helpers ────────────────────────────────────────────────
|
|
1097
|
+
/** Get or create a toast container for the given placement */
|
|
1098
|
+
getContainer(placement) {
|
|
1099
|
+
let c = this.containers.get(placement);
|
|
1100
|
+
if (!c) {
|
|
1101
|
+
c = this.doc.createElement('div');
|
|
1102
|
+
c.className = 'toast-container';
|
|
1103
|
+
c.setAttribute('popover', 'manual');
|
|
1104
|
+
c.setAttribute('data-placement', placement);
|
|
1105
|
+
this.doc.body.appendChild(c);
|
|
1106
|
+
this.containers.set(placement, c);
|
|
1107
|
+
}
|
|
1108
|
+
return c;
|
|
1109
|
+
}
|
|
1110
|
+
/** Show a prepared element as a toast */
|
|
1111
|
+
showEl(el, options = {}) {
|
|
1112
|
+
const { placement = 'top-right', duration = 4000 } = options;
|
|
1113
|
+
const container = this.getContainer(placement);
|
|
1114
|
+
el.classList.add('toast');
|
|
1115
|
+
let timeout;
|
|
1116
|
+
// Pause auto-dismiss on hover
|
|
1117
|
+
el.onmouseenter = () => clearTimeout(timeout);
|
|
1118
|
+
el.onmouseleave = () => {
|
|
1119
|
+
if (duration > 0) {
|
|
1120
|
+
timeout = setTimeout(() => this.removeToast(el, container), duration);
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
// Show with enter animation
|
|
1124
|
+
el.setAttribute('data-entering', '');
|
|
1125
|
+
container.appendChild(el);
|
|
1126
|
+
container.showPopover();
|
|
1127
|
+
// Double rAF so computed styles apply before transition starts
|
|
1128
|
+
requestAnimationFrame(() => {
|
|
1129
|
+
requestAnimationFrame(() => {
|
|
1130
|
+
el.removeAttribute('data-entering');
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
// Auto-dismiss
|
|
1134
|
+
if (duration > 0) {
|
|
1135
|
+
timeout = setTimeout(() => this.removeToast(el, container), duration);
|
|
1136
|
+
}
|
|
1137
|
+
return el;
|
|
1138
|
+
}
|
|
1139
|
+
/** Remove a toast with exit animation */
|
|
1140
|
+
removeToast(el, container) {
|
|
1141
|
+
if (el.hasAttribute('data-exiting'))
|
|
1142
|
+
return;
|
|
1143
|
+
el.setAttribute('data-exiting', '');
|
|
1144
|
+
const cleanup = () => {
|
|
1145
|
+
el.remove();
|
|
1146
|
+
if (!container.children.length) {
|
|
1147
|
+
container.hidePopover();
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
el.addEventListener('transitionend', cleanup, { once: true });
|
|
1151
|
+
// Fallback timeout for clients that disable animations
|
|
1152
|
+
const t = getComputedStyle(el).getPropertyValue('--transition').trim();
|
|
1153
|
+
const val = parseFloat(t) || 300;
|
|
1154
|
+
const ms = t.endsWith('ms') ? val : val * 1000;
|
|
1155
|
+
setTimeout(cleanup, ms > 0 ? ms : 350);
|
|
1005
1156
|
}
|
|
1006
1157
|
/** Add a close button to a toast element */
|
|
1007
1158
|
addCloseButton(el) {
|
|
@@ -1013,10 +1164,8 @@ class NgOatToast {
|
|
|
1013
1164
|
btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
|
|
1014
1165
|
btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.6'; });
|
|
1015
1166
|
btn.addEventListener('click', () => {
|
|
1016
|
-
el.
|
|
1017
|
-
|
|
1018
|
-
el.addEventListener('transitionend', cleanup, { once: true });
|
|
1019
|
-
setTimeout(cleanup, 350);
|
|
1167
|
+
const container = el.parentElement;
|
|
1168
|
+
this.removeToast(el, container);
|
|
1020
1169
|
});
|
|
1021
1170
|
el.style.position = 'relative';
|
|
1022
1171
|
el.style.paddingRight = '2rem';
|
|
@@ -1975,17 +2124,21 @@ class NgOatDropdownComponent {
|
|
|
1975
2124
|
const items = host.querySelectorAll('[role="menuitem"]');
|
|
1976
2125
|
items[0]?.focus();
|
|
1977
2126
|
this.triggerEl?.setAttribute('aria-expanded', 'true');
|
|
2127
|
+
// Add keyboard navigation
|
|
2128
|
+
this.popoverEl?.addEventListener('keydown', this.onKeydown);
|
|
1978
2129
|
}
|
|
1979
2130
|
else {
|
|
1980
2131
|
window.removeEventListener('scroll', this.positionFn, true);
|
|
1981
2132
|
window.removeEventListener('resize', this.positionFn);
|
|
1982
2133
|
this.triggerEl?.setAttribute('aria-expanded', 'false');
|
|
1983
2134
|
this.triggerEl?.focus();
|
|
2135
|
+
this.popoverEl?.removeEventListener('keydown', this.onKeydown);
|
|
1984
2136
|
}
|
|
1985
2137
|
};
|
|
1986
2138
|
this.popoverEl.addEventListener('toggle', onToggle);
|
|
1987
2139
|
this.destroyRef.onDestroy(() => {
|
|
1988
2140
|
this.popoverEl?.removeEventListener('toggle', onToggle);
|
|
2141
|
+
this.popoverEl?.removeEventListener('keydown', this.onKeydown);
|
|
1989
2142
|
window.removeEventListener('scroll', this.positionFn, true);
|
|
1990
2143
|
window.removeEventListener('resize', this.positionFn);
|
|
1991
2144
|
});
|
|
@@ -2000,6 +2153,44 @@ class NgOatDropdownComponent {
|
|
|
2000
2153
|
toggle() {
|
|
2001
2154
|
this.popoverEl?.togglePopover?.();
|
|
2002
2155
|
}
|
|
2156
|
+
/** Keyboard navigation for menu items (ArrowDown/Up/Home/End/Escape) */
|
|
2157
|
+
onKeydown = (e) => {
|
|
2158
|
+
const host = this.elRef.nativeElement;
|
|
2159
|
+
const items = Array.from(host.querySelectorAll('[role="menuitem"]:not([disabled])'));
|
|
2160
|
+
if (!items.length)
|
|
2161
|
+
return;
|
|
2162
|
+
const current = items.indexOf(this.doc.activeElement);
|
|
2163
|
+
switch (e.key) {
|
|
2164
|
+
case 'ArrowDown': {
|
|
2165
|
+
e.preventDefault();
|
|
2166
|
+
const next = current < items.length - 1 ? current + 1 : 0;
|
|
2167
|
+
items[next]?.focus();
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
case 'ArrowUp': {
|
|
2171
|
+
e.preventDefault();
|
|
2172
|
+
const prev = current > 0 ? current - 1 : items.length - 1;
|
|
2173
|
+
items[prev]?.focus();
|
|
2174
|
+
break;
|
|
2175
|
+
}
|
|
2176
|
+
case 'Home': {
|
|
2177
|
+
e.preventDefault();
|
|
2178
|
+
items[0]?.focus();
|
|
2179
|
+
break;
|
|
2180
|
+
}
|
|
2181
|
+
case 'End': {
|
|
2182
|
+
e.preventDefault();
|
|
2183
|
+
items[items.length - 1]?.focus();
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
case 'Escape': {
|
|
2187
|
+
e.preventDefault();
|
|
2188
|
+
this.close();
|
|
2189
|
+
break;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
doc = inject(DOCUMENT);
|
|
2003
2194
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2004
2195
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: NgOatDropdownComponent, isStandalone: true, selector: "ng-oat-dropdown", outputs: { openChange: "openChange" }, ngImport: i0, template: `
|
|
2005
2196
|
<ot-dropdown>
|
|
@@ -4326,6 +4517,455 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
4326
4517
|
`, styles: [":host{display:block}\n"] }]
|
|
4327
4518
|
}], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], showSeeAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeeAll", required: false }] }], scrollAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollAmount", required: false }] }], seeAllClick: [{ type: i0.Output, args: ["seeAllClick"] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }], trackRef: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
|
|
4328
4519
|
|
|
4520
|
+
/**
|
|
4521
|
+
* Angular toolbar component — like mat-toolbar, built on Oat CSS.
|
|
4522
|
+
*
|
|
4523
|
+
* Renders a fixed-position `<nav data-topnav>` that leverages Oat's built-in
|
|
4524
|
+
* topnav styling (flex, border, shadow). Content-projected slots let you
|
|
4525
|
+
* arrange Logo / nav-links / actions however you like.
|
|
4526
|
+
*
|
|
4527
|
+
* ## Slots
|
|
4528
|
+
* - **`[toolbarStart]`** — Left-aligned content (logo, brand, hamburger)
|
|
4529
|
+
* - **Default `<ng-content>`** — Center / free-form content (nav links, search)
|
|
4530
|
+
* - **`[toolbarEnd]`** — Right-aligned content (user menu, theme toggle, actions)
|
|
4531
|
+
*
|
|
4532
|
+
* ## Layout
|
|
4533
|
+
* The toolbar uses `display:flex; align-items:center` with a spacer between
|
|
4534
|
+
* the default content and the end slot, so start items anchor left and end
|
|
4535
|
+
* items anchor right automatically.
|
|
4536
|
+
*
|
|
4537
|
+
* Usage:
|
|
4538
|
+
* ```html
|
|
4539
|
+
* <ng-oat-toolbar>
|
|
4540
|
+
* <a toolbarStart routerLink="/" class="brand">🌾 MyApp</a>
|
|
4541
|
+
* <nav>
|
|
4542
|
+
* <a routerLink="/home">Home</a>
|
|
4543
|
+
* <a routerLink="/about">About</a>
|
|
4544
|
+
* </nav>
|
|
4545
|
+
* <ng-oat-dropdown toolbarEnd>
|
|
4546
|
+
* <button trigger class="ghost">👤 User ▾</button>
|
|
4547
|
+
* <a role="menuitem">Profile</a>
|
|
4548
|
+
* <a role="menuitem">Settings</a>
|
|
4549
|
+
* <hr />
|
|
4550
|
+
* <a role="menuitem">Logout</a>
|
|
4551
|
+
* </ng-oat-dropdown>
|
|
4552
|
+
* </ng-oat-toolbar>
|
|
4553
|
+
* ```
|
|
4554
|
+
*/
|
|
4555
|
+
class NgOatToolbar {
|
|
4556
|
+
color = input('default', ...(ngDevMode ? [{ debugName: "color" }] : []));
|
|
4557
|
+
dense = input(false, ...(ngDevMode ? [{ debugName: "dense" }] : []));
|
|
4558
|
+
fixed = input(true, ...(ngDevMode ? [{ debugName: "fixed" }] : []));
|
|
4559
|
+
attrVariant = computed(() => {
|
|
4560
|
+
const c = this.color();
|
|
4561
|
+
return c === 'default' ? null : c;
|
|
4562
|
+
}, ...(ngDevMode ? [{ debugName: "attrVariant" }] : []));
|
|
4563
|
+
navClass = computed(() => {
|
|
4564
|
+
const classes = [];
|
|
4565
|
+
const c = this.color();
|
|
4566
|
+
if (c !== 'default')
|
|
4567
|
+
classes.push(`oat-toolbar-${c}`);
|
|
4568
|
+
if (this.dense())
|
|
4569
|
+
classes.push('oat-toolbar-dense');
|
|
4570
|
+
if (!this.fixed())
|
|
4571
|
+
classes.push('oat-toolbar-static');
|
|
4572
|
+
return classes.join(' ');
|
|
4573
|
+
}, ...(ngDevMode ? [{ debugName: "navClass" }] : []));
|
|
4574
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4575
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: NgOatToolbar, isStandalone: true, selector: "ng-oat-toolbar", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, fixed: { classPropertyName: "fixed", publicName: "fixed", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar" }, properties: { "attr.data-variant": "attrVariant()" } }, ngImport: i0, template: `
|
|
4576
|
+
<nav data-topnav [class]="navClass()">
|
|
4577
|
+
<ng-content select="[toolbarStart]" />
|
|
4578
|
+
<ng-content />
|
|
4579
|
+
<span class="oat-toolbar-spacer"></span>
|
|
4580
|
+
<ng-content select="[toolbarEnd]" />
|
|
4581
|
+
</nav>
|
|
4582
|
+
`, isInline: true, styles: [":host{display:block}.oat-toolbar-spacer{flex:1 1 auto}nav[data-topnav].oat-toolbar-static{position:relative;inset:unset;z-index:auto}nav[data-topnav].oat-toolbar-dense{min-height:var(--space-10);padding-block:var(--space-1)}nav[data-topnav].oat-toolbar-primary{background-color:var(--primary);color:var(--primary-foreground);border-bottom-color:var(--primary)}nav[data-topnav].oat-toolbar-primary a{color:var(--primary-foreground)}nav[data-topnav].oat-toolbar-accent{background-color:var(--accent);color:var(--foreground);border-bottom-color:var(--accent)}\n"] });
|
|
4583
|
+
}
|
|
4584
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatToolbar, decorators: [{
|
|
4585
|
+
type: Component,
|
|
4586
|
+
args: [{ selector: 'ng-oat-toolbar', host: {
|
|
4587
|
+
'role': 'toolbar',
|
|
4588
|
+
'[attr.data-variant]': 'attrVariant()',
|
|
4589
|
+
}, template: `
|
|
4590
|
+
<nav data-topnav [class]="navClass()">
|
|
4591
|
+
<ng-content select="[toolbarStart]" />
|
|
4592
|
+
<ng-content />
|
|
4593
|
+
<span class="oat-toolbar-spacer"></span>
|
|
4594
|
+
<ng-content select="[toolbarEnd]" />
|
|
4595
|
+
</nav>
|
|
4596
|
+
`, styles: [":host{display:block}.oat-toolbar-spacer{flex:1 1 auto}nav[data-topnav].oat-toolbar-static{position:relative;inset:unset;z-index:auto}nav[data-topnav].oat-toolbar-dense{min-height:var(--space-10);padding-block:var(--space-1)}nav[data-topnav].oat-toolbar-primary{background-color:var(--primary);color:var(--primary-foreground);border-bottom-color:var(--primary)}nav[data-topnav].oat-toolbar-primary a{color:var(--primary-foreground)}nav[data-topnav].oat-toolbar-accent{background-color:var(--accent);color:var(--foreground);border-bottom-color:var(--accent)}\n"] }]
|
|
4597
|
+
}], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], dense: [{ type: i0.Input, args: [{ isSignal: true, alias: "dense", required: false }] }], fixed: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixed", required: false }] }] } });
|
|
4598
|
+
/**
|
|
4599
|
+
* Toolbar row — use multiple rows stacked inside a toolbar.
|
|
4600
|
+
*
|
|
4601
|
+
* Usage:
|
|
4602
|
+
* ```html
|
|
4603
|
+
* <ng-oat-toolbar>
|
|
4604
|
+
* <ng-oat-toolbar-row>
|
|
4605
|
+
* <a toolbarStart>Brand</a>
|
|
4606
|
+
* <span toolbarEnd>Actions</span>
|
|
4607
|
+
* </ng-oat-toolbar-row>
|
|
4608
|
+
* <ng-oat-toolbar-row dense>
|
|
4609
|
+
* <nav>Sub-navigation tabs</nav>
|
|
4610
|
+
* </ng-oat-toolbar-row>
|
|
4611
|
+
* </ng-oat-toolbar>
|
|
4612
|
+
* ```
|
|
4613
|
+
*/
|
|
4614
|
+
class NgOatToolbarRow {
|
|
4615
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatToolbarRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4616
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: NgOatToolbarRow, isStandalone: true, selector: "ng-oat-toolbar-row", host: { classAttribute: "oat-toolbar-row" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:flex;align-items:center;gap:var(--space-3);width:100%}\n"] });
|
|
4617
|
+
}
|
|
4618
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatToolbarRow, decorators: [{
|
|
4619
|
+
type: Component,
|
|
4620
|
+
args: [{ selector: 'ng-oat-toolbar-row', host: { class: 'oat-toolbar-row' }, template: `<ng-content />`, styles: [":host{display:flex;align-items:center;gap:var(--space-3);width:100%}\n"] }]
|
|
4621
|
+
}] });
|
|
4622
|
+
|
|
4623
|
+
const DEFAULT_THEMES = [
|
|
4624
|
+
{ value: 'light', label: 'Light' },
|
|
4625
|
+
{ value: 'dark', label: 'Dark' },
|
|
4626
|
+
{ value: 'system', label: 'System' },
|
|
4627
|
+
];
|
|
4628
|
+
const STORAGE_KEY = 'ng-oat-theme';
|
|
4629
|
+
/**
|
|
4630
|
+
* Structural directive for fully-custom icon rendering.
|
|
4631
|
+
*
|
|
4632
|
+
* Place inside `<ng-oat-theme-selector>` to replace the built-in SVG icons.
|
|
4633
|
+
* The template context receives the current `NgOatThemeOption` as `$implicit`.
|
|
4634
|
+
*
|
|
4635
|
+
* ```html
|
|
4636
|
+
* <ng-oat-theme-selector>
|
|
4637
|
+
* <ng-template ngOatThemeSelectorIcon let-opt>
|
|
4638
|
+
* <my-icon [name]="opt.value" />
|
|
4639
|
+
* </ng-template>
|
|
4640
|
+
* </ng-oat-theme-selector>
|
|
4641
|
+
* ```
|
|
4642
|
+
*/
|
|
4643
|
+
class NgOatThemeSelectorIcon {
|
|
4644
|
+
tpl = inject(TemplateRef);
|
|
4645
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatThemeSelectorIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4646
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: NgOatThemeSelectorIcon, isStandalone: true, selector: "ng-template[ngOatThemeSelectorIcon]", ngImport: i0 });
|
|
4647
|
+
}
|
|
4648
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatThemeSelectorIcon, decorators: [{
|
|
4649
|
+
type: Directive,
|
|
4650
|
+
args: [{ selector: 'ng-template[ngOatThemeSelectorIcon]' }]
|
|
4651
|
+
}] });
|
|
4652
|
+
/**
|
|
4653
|
+
* Theme selector — shadcn-inspired light / dark / system toggle.
|
|
4654
|
+
*
|
|
4655
|
+
* Applies `colorScheme` on `<html>` and persists the choice to `localStorage`.
|
|
4656
|
+
* When "system" is selected it respects `prefers-color-scheme`.
|
|
4657
|
+
*
|
|
4658
|
+
* Usage:
|
|
4659
|
+
* ```html
|
|
4660
|
+
* <!-- Dropdown-style (default) -->
|
|
4661
|
+
* <ng-oat-theme-selector />
|
|
4662
|
+
*
|
|
4663
|
+
* <!-- Inline toggle group style -->
|
|
4664
|
+
* <ng-oat-theme-selector mode="toggle" />
|
|
4665
|
+
*
|
|
4666
|
+
* <!-- Listen for changes -->
|
|
4667
|
+
* <ng-oat-theme-selector (themeChange)="onTheme($event)" />
|
|
4668
|
+
* ```
|
|
4669
|
+
*/
|
|
4670
|
+
class NgOatThemeSelector {
|
|
4671
|
+
/** Display mode: dropdown menu or inline toggle group */
|
|
4672
|
+
mode = input('dropdown', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
4673
|
+
/** Initial theme (overrides localStorage if set) */
|
|
4674
|
+
initialTheme = input(undefined, ...(ngDevMode ? [{ debugName: "initialTheme" }] : []));
|
|
4675
|
+
/**
|
|
4676
|
+
* Custom theme options. Override labels, provide emoji icons, or
|
|
4677
|
+
* change the set entirely.
|
|
4678
|
+
*
|
|
4679
|
+
* ```html
|
|
4680
|
+
* <ng-oat-theme-selector
|
|
4681
|
+
* [themes]="[
|
|
4682
|
+
* { value: 'light', label: 'Day', icon: '🌅' },
|
|
4683
|
+
* { value: 'dark', label: 'Night', icon: '🌃' },
|
|
4684
|
+
* { value: 'system', label: 'Auto', icon: '🖥️' },
|
|
4685
|
+
* ]" />
|
|
4686
|
+
* ```
|
|
4687
|
+
*/
|
|
4688
|
+
themes = input(DEFAULT_THEMES, ...(ngDevMode ? [{ debugName: "themes" }] : []));
|
|
4689
|
+
/** Emits when the user picks a theme */
|
|
4690
|
+
themeChange = output();
|
|
4691
|
+
/** Current active theme */
|
|
4692
|
+
current = signal('system', ...(ngDevMode ? [{ debugName: "current" }] : []));
|
|
4693
|
+
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
4694
|
+
/** Content-projected custom icon template */
|
|
4695
|
+
iconTpl = contentChild(NgOatThemeSelectorIcon, ...(ngDevMode ? [{ debugName: "iconTpl" }] : []));
|
|
4696
|
+
/** Resolved themes (input or defaults) */
|
|
4697
|
+
resolvedThemes = computed(() => this.themes(), ...(ngDevMode ? [{ debugName: "resolvedThemes" }] : []));
|
|
4698
|
+
/** The currently active option object */
|
|
4699
|
+
activeOption = computed(() => this.resolvedThemes().find(t => t.value === this.current()) ?? this.resolvedThemes()[0], ...(ngDevMode ? [{ debugName: "activeOption" }] : []));
|
|
4700
|
+
doc = inject(DOCUMENT);
|
|
4701
|
+
platformId = inject(PLATFORM_ID);
|
|
4702
|
+
mediaQuery = null;
|
|
4703
|
+
mediaListener = (e) => this.applyResolved(e.matches ? 'dark' : 'light');
|
|
4704
|
+
/** Toggle the dropdown open/closed */
|
|
4705
|
+
toggleOpen() {
|
|
4706
|
+
this.open.update(v => !v);
|
|
4707
|
+
}
|
|
4708
|
+
/** Pick a theme and close the dropdown */
|
|
4709
|
+
pick(theme) {
|
|
4710
|
+
this.setTheme(theme);
|
|
4711
|
+
this.open.set(false);
|
|
4712
|
+
}
|
|
4713
|
+
/** Keyboard handler for Escape and arrow-key navigation inside the menu */
|
|
4714
|
+
onHostKey(e) {
|
|
4715
|
+
if (e.key === 'Escape' && this.open()) {
|
|
4716
|
+
e.preventDefault();
|
|
4717
|
+
this.open.set(false);
|
|
4718
|
+
// Return focus to trigger
|
|
4719
|
+
const trigger = e.target?.closest('.oat-ts-dropdown')?.querySelector('.oat-ts-trigger');
|
|
4720
|
+
trigger?.focus();
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
if (!this.open())
|
|
4724
|
+
return;
|
|
4725
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
4726
|
+
e.preventDefault();
|
|
4727
|
+
const menu = e.target?.closest('.oat-ts-dropdown')?.querySelector('.oat-ts-menu');
|
|
4728
|
+
if (!menu)
|
|
4729
|
+
return;
|
|
4730
|
+
const items = Array.from(menu.querySelectorAll('button[role="option"]'));
|
|
4731
|
+
const idx = items.indexOf(e.target);
|
|
4732
|
+
const next = e.key === 'ArrowDown'
|
|
4733
|
+
? items[(idx + 1) % items.length]
|
|
4734
|
+
: items[(idx - 1 + items.length) % items.length];
|
|
4735
|
+
next?.focus();
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
ngOnInit() {
|
|
4739
|
+
if (!isPlatformBrowser(this.platformId))
|
|
4740
|
+
return;
|
|
4741
|
+
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
4742
|
+
// Determine starting theme: explicit input > localStorage > system
|
|
4743
|
+
const init = this.initialTheme();
|
|
4744
|
+
if (init) {
|
|
4745
|
+
this.setTheme(init);
|
|
4746
|
+
}
|
|
4747
|
+
else {
|
|
4748
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
4749
|
+
this.setTheme(stored ?? 'system');
|
|
4750
|
+
}
|
|
4751
|
+
// Close dropdown on outside click
|
|
4752
|
+
this.doc.addEventListener('click', this.onDocClick);
|
|
4753
|
+
}
|
|
4754
|
+
setTheme(theme) {
|
|
4755
|
+
this.current.set(theme);
|
|
4756
|
+
this.themeChange.emit(theme);
|
|
4757
|
+
if (!isPlatformBrowser(this.platformId))
|
|
4758
|
+
return;
|
|
4759
|
+
localStorage.setItem(STORAGE_KEY, theme);
|
|
4760
|
+
// Unsubscribe from previous media listener
|
|
4761
|
+
this.mediaQuery?.removeEventListener('change', this.mediaListener);
|
|
4762
|
+
if (theme === 'system') {
|
|
4763
|
+
this.applyResolved(this.mediaQuery?.matches ? 'dark' : 'light');
|
|
4764
|
+
this.mediaQuery?.addEventListener('change', this.mediaListener);
|
|
4765
|
+
}
|
|
4766
|
+
else {
|
|
4767
|
+
this.applyResolved(theme);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
applyResolved(resolved) {
|
|
4771
|
+
this.doc.documentElement.style.colorScheme = resolved;
|
|
4772
|
+
}
|
|
4773
|
+
onDocClick = (e) => {
|
|
4774
|
+
if (!e.target?.closest?.('.oat-ts-dropdown')) {
|
|
4775
|
+
this.open.set(false);
|
|
4776
|
+
}
|
|
4777
|
+
};
|
|
4778
|
+
/** @internal */
|
|
4779
|
+
ngOnDestroy() {
|
|
4780
|
+
this.mediaQuery?.removeEventListener('change', this.mediaListener);
|
|
4781
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
4782
|
+
this.doc.removeEventListener('click', this.onDocClick);
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatThemeSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4786
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: NgOatThemeSelector, isStandalone: true, selector: "ng-oat-theme-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, initialTheme: { classPropertyName: "initialTheme", publicName: "initialTheme", isSignal: true, isRequired: false, transformFunction: null }, themes: { classPropertyName: "themes", publicName: "themes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { themeChange: "themeChange" }, host: { listeners: { "keydown": "onHostKey($event)" }, properties: { "attr.data-mode": "mode()" }, classAttribute: "oat-theme-selector" }, queries: [{ propertyName: "iconTpl", first: true, predicate: NgOatThemeSelectorIcon, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4787
|
+
@if (mode() === 'dropdown') {
|
|
4788
|
+
<div class="oat-ts-dropdown">
|
|
4789
|
+
<button
|
|
4790
|
+
class="oat-ts-trigger"
|
|
4791
|
+
type="button"
|
|
4792
|
+
aria-haspopup="listbox"
|
|
4793
|
+
[attr.aria-expanded]="open()"
|
|
4794
|
+
aria-label="Theme: {{ current() }}"
|
|
4795
|
+
(click)="toggleOpen()">
|
|
4796
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: activeOption() }" />
|
|
4797
|
+
@if (!iconTpl()) {
|
|
4798
|
+
@if (activeOption().icon) {
|
|
4799
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ activeOption().icon }}</span>
|
|
4800
|
+
} @else {
|
|
4801
|
+
<ng-container [ngSwitch]="current()">
|
|
4802
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4803
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4804
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4805
|
+
</ng-container>
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
<svg class="oat-ts-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
4809
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4810
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
4811
|
+
</svg>
|
|
4812
|
+
</button>
|
|
4813
|
+
@if (open()) {
|
|
4814
|
+
<div class="oat-ts-menu" role="listbox" aria-label="Select theme">
|
|
4815
|
+
@for (t of resolvedThemes(); track t.value) {
|
|
4816
|
+
<button
|
|
4817
|
+
role="option"
|
|
4818
|
+
type="button"
|
|
4819
|
+
[attr.aria-selected]="current() === t.value"
|
|
4820
|
+
[class.active]="current() === t.value"
|
|
4821
|
+
(click)="pick(t.value)">
|
|
4822
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: t }" />
|
|
4823
|
+
@if (!iconTpl()) {
|
|
4824
|
+
@if (t.icon) {
|
|
4825
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ t.icon }}</span>
|
|
4826
|
+
} @else {
|
|
4827
|
+
<ng-container [ngSwitch]="t.value">
|
|
4828
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4829
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4830
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4831
|
+
</ng-container>
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
<span>{{ t.label }}</span>
|
|
4835
|
+
@if (current() === t.value) {
|
|
4836
|
+
<svg class="oat-ts-check" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
4837
|
+
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4838
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
4839
|
+
</svg>
|
|
4840
|
+
}
|
|
4841
|
+
</button>
|
|
4842
|
+
}
|
|
4843
|
+
</div>
|
|
4844
|
+
}
|
|
4845
|
+
</div>
|
|
4846
|
+
} @else {
|
|
4847
|
+
<div class="oat-ts-toggle" role="radiogroup" aria-label="Theme">
|
|
4848
|
+
@for (t of resolvedThemes(); track t.value) {
|
|
4849
|
+
<button
|
|
4850
|
+
type="button"
|
|
4851
|
+
role="radio"
|
|
4852
|
+
[attr.aria-checked]="current() === t.value"
|
|
4853
|
+
[attr.aria-label]="t.label"
|
|
4854
|
+
[class.active]="current() === t.value"
|
|
4855
|
+
(click)="setTheme(t.value)">
|
|
4856
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: t }" />
|
|
4857
|
+
@if (!iconTpl()) {
|
|
4858
|
+
@if (t.icon) {
|
|
4859
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ t.icon }}</span>
|
|
4860
|
+
} @else {
|
|
4861
|
+
<ng-container [ngSwitch]="t.value">
|
|
4862
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4863
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4864
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4865
|
+
</ng-container>
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
</button>
|
|
4869
|
+
}
|
|
4870
|
+
</div>
|
|
4871
|
+
}
|
|
4872
|
+
`, isInline: true, styles: [":host{display:inline-flex}.oat-ts-svg{width:1rem;height:1rem;flex-shrink:0}.oat-ts-emoji{font-size:1rem;line-height:1;flex-shrink:0}.oat-ts-dropdown{position:relative}.oat-ts-trigger{display:inline-flex;align-items:center;gap:var(--space-1);cursor:pointer;padding:var(--space-1) var(--space-2);border-radius:var(--radius-medium);border:1px solid var(--border);background:transparent;color:var(--foreground);transition:background .15s,border-color .15s;line-height:1}.oat-ts-trigger:hover{background:var(--accent)}.oat-ts-trigger:focus-visible{outline:2px solid var(--ring);outline-offset:2px}.oat-ts-chevron{width:.75rem;height:.75rem;opacity:.5}.oat-ts-menu{position:absolute;top:calc(100% + var(--space-1));right:0;z-index:50;min-width:8rem;border:1px solid var(--border);border-radius:var(--radius-medium);background:var(--background);box-shadow:var(--shadow-small);padding:var(--space-1);display:flex;flex-direction:column;animation:oat-ts-fade-in .12s ease-out}@keyframes oat-ts-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.oat-ts-menu button{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-2);border-radius:var(--radius-small);cursor:pointer;font-size:var(--text-6);line-height:1.25;color:var(--foreground);transition:background .1s}.oat-ts-menu button:hover,.oat-ts-menu button:focus-visible{background:var(--accent)}.oat-ts-menu button:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}.oat-ts-menu button.active{font-weight:500}.oat-ts-check{width:.875rem;height:.875rem;margin-inline-start:auto;opacity:.7}.oat-ts-toggle{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-medium);overflow:hidden}.oat-ts-toggle button{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;padding:var(--space-1) var(--space-2);cursor:pointer;transition:background .15s;color:var(--muted-foreground, var(--foreground))}.oat-ts-toggle button:hover{background:var(--accent)}.oat-ts-toggle button:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}.oat-ts-toggle button.active{background:var(--accent);color:var(--foreground)}.oat-ts-toggle button+button{border-left:1px solid var(--border)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: NgSwitchDefault, selector: "[ngSwitchDefault]" }] });
|
|
4873
|
+
}
|
|
4874
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatThemeSelector, decorators: [{
|
|
4875
|
+
type: Component,
|
|
4876
|
+
args: [{ selector: 'ng-oat-theme-selector', imports: [NgTemplateOutlet, NgSwitch, NgSwitchCase, NgSwitchDefault], host: {
|
|
4877
|
+
'class': 'oat-theme-selector',
|
|
4878
|
+
'[attr.data-mode]': 'mode()',
|
|
4879
|
+
'(keydown)': 'onHostKey($event)',
|
|
4880
|
+
}, template: `
|
|
4881
|
+
@if (mode() === 'dropdown') {
|
|
4882
|
+
<div class="oat-ts-dropdown">
|
|
4883
|
+
<button
|
|
4884
|
+
class="oat-ts-trigger"
|
|
4885
|
+
type="button"
|
|
4886
|
+
aria-haspopup="listbox"
|
|
4887
|
+
[attr.aria-expanded]="open()"
|
|
4888
|
+
aria-label="Theme: {{ current() }}"
|
|
4889
|
+
(click)="toggleOpen()">
|
|
4890
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: activeOption() }" />
|
|
4891
|
+
@if (!iconTpl()) {
|
|
4892
|
+
@if (activeOption().icon) {
|
|
4893
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ activeOption().icon }}</span>
|
|
4894
|
+
} @else {
|
|
4895
|
+
<ng-container [ngSwitch]="current()">
|
|
4896
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4897
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4898
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4899
|
+
</ng-container>
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
<svg class="oat-ts-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
4903
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4904
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
4905
|
+
</svg>
|
|
4906
|
+
</button>
|
|
4907
|
+
@if (open()) {
|
|
4908
|
+
<div class="oat-ts-menu" role="listbox" aria-label="Select theme">
|
|
4909
|
+
@for (t of resolvedThemes(); track t.value) {
|
|
4910
|
+
<button
|
|
4911
|
+
role="option"
|
|
4912
|
+
type="button"
|
|
4913
|
+
[attr.aria-selected]="current() === t.value"
|
|
4914
|
+
[class.active]="current() === t.value"
|
|
4915
|
+
(click)="pick(t.value)">
|
|
4916
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: t }" />
|
|
4917
|
+
@if (!iconTpl()) {
|
|
4918
|
+
@if (t.icon) {
|
|
4919
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ t.icon }}</span>
|
|
4920
|
+
} @else {
|
|
4921
|
+
<ng-container [ngSwitch]="t.value">
|
|
4922
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4923
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4924
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4925
|
+
</ng-container>
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
<span>{{ t.label }}</span>
|
|
4929
|
+
@if (current() === t.value) {
|
|
4930
|
+
<svg class="oat-ts-check" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
4931
|
+
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4932
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
4933
|
+
</svg>
|
|
4934
|
+
}
|
|
4935
|
+
</button>
|
|
4936
|
+
}
|
|
4937
|
+
</div>
|
|
4938
|
+
}
|
|
4939
|
+
</div>
|
|
4940
|
+
} @else {
|
|
4941
|
+
<div class="oat-ts-toggle" role="radiogroup" aria-label="Theme">
|
|
4942
|
+
@for (t of resolvedThemes(); track t.value) {
|
|
4943
|
+
<button
|
|
4944
|
+
type="button"
|
|
4945
|
+
role="radio"
|
|
4946
|
+
[attr.aria-checked]="current() === t.value"
|
|
4947
|
+
[attr.aria-label]="t.label"
|
|
4948
|
+
[class.active]="current() === t.value"
|
|
4949
|
+
(click)="setTheme(t.value)">
|
|
4950
|
+
<ng-container *ngTemplateOutlet="iconTpl()?.tpl ?? null; context: { $implicit: t }" />
|
|
4951
|
+
@if (!iconTpl()) {
|
|
4952
|
+
@if (t.icon) {
|
|
4953
|
+
<span class="oat-ts-emoji" aria-hidden="true">{{ t.icon }}</span>
|
|
4954
|
+
} @else {
|
|
4955
|
+
<ng-container [ngSwitch]="t.value">
|
|
4956
|
+
<svg *ngSwitchCase="'light'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
4957
|
+
<svg *ngSwitchCase="'dark'" class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
4958
|
+
<svg *ngSwitchDefault class="oat-ts-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
4959
|
+
</ng-container>
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
</button>
|
|
4963
|
+
}
|
|
4964
|
+
</div>
|
|
4965
|
+
}
|
|
4966
|
+
`, styles: [":host{display:inline-flex}.oat-ts-svg{width:1rem;height:1rem;flex-shrink:0}.oat-ts-emoji{font-size:1rem;line-height:1;flex-shrink:0}.oat-ts-dropdown{position:relative}.oat-ts-trigger{display:inline-flex;align-items:center;gap:var(--space-1);cursor:pointer;padding:var(--space-1) var(--space-2);border-radius:var(--radius-medium);border:1px solid var(--border);background:transparent;color:var(--foreground);transition:background .15s,border-color .15s;line-height:1}.oat-ts-trigger:hover{background:var(--accent)}.oat-ts-trigger:focus-visible{outline:2px solid var(--ring);outline-offset:2px}.oat-ts-chevron{width:.75rem;height:.75rem;opacity:.5}.oat-ts-menu{position:absolute;top:calc(100% + var(--space-1));right:0;z-index:50;min-width:8rem;border:1px solid var(--border);border-radius:var(--radius-medium);background:var(--background);box-shadow:var(--shadow-small);padding:var(--space-1);display:flex;flex-direction:column;animation:oat-ts-fade-in .12s ease-out}@keyframes oat-ts-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.oat-ts-menu button{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-2);border-radius:var(--radius-small);cursor:pointer;font-size:var(--text-6);line-height:1.25;color:var(--foreground);transition:background .1s}.oat-ts-menu button:hover,.oat-ts-menu button:focus-visible{background:var(--accent)}.oat-ts-menu button:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}.oat-ts-menu button.active{font-weight:500}.oat-ts-check{width:.875rem;height:.875rem;margin-inline-start:auto;opacity:.7}.oat-ts-toggle{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-medium);overflow:hidden}.oat-ts-toggle button{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;padding:var(--space-1) var(--space-2);cursor:pointer;transition:background .15s;color:var(--muted-foreground, var(--foreground))}.oat-ts-toggle button:hover{background:var(--accent)}.oat-ts-toggle button:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}.oat-ts-toggle button.active{background:var(--accent);color:var(--foreground)}.oat-ts-toggle button+button{border-left:1px solid var(--border)}\n"] }]
|
|
4967
|
+
}], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], initialTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialTheme", required: false }] }], themes: [{ type: i0.Input, args: [{ isSignal: true, alias: "themes", required: false }] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], iconTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgOatThemeSelectorIcon), { isSignal: true }] }] } });
|
|
4968
|
+
|
|
4329
4969
|
/**
|
|
4330
4970
|
* Oat-styled text input implementing `FormValueControl<string>`.
|
|
4331
4971
|
* Works seamlessly with Signal Forms `[formField]` — no CVA needed.
|
|
@@ -4870,5 +5510,5 @@ const OAT_VERSION_TOKEN = new InjectionToken('OAT_VERSION', {
|
|
|
4870
5510
|
* Generated bundle index. Do not edit.
|
|
4871
5511
|
*/
|
|
4872
5512
|
|
|
4873
|
-
export { NG_OAT_CHIP_GROUP, NG_OAT_TOGGLE_GROUP, NgOatAccordion, NgOatAlert, NgOatAvatar, NgOatBadge, NgOatBreadcrumb, NgOatButton, NgOatCard, NgOatCardCarousel, NgOatCardFooter, NgOatCardHeader, NgOatCarousel, NgOatCheckbox, NgOatChip, NgOatChipGroup, NgOatChipInput, NgOatDialog, NgOatDialogComponent, NgOatDropdown, NgOatDropdownComponent, NgOatFileUpload, NgOatFormError, NgOatInput, NgOatInputOtp, NgOatMeter, NgOatPagination, NgOatProgress, NgOatRadioGroup, NgOatSearchInput, NgOatSelect, NgOatSeparator, NgOatSidebar, NgOatSidebarComponent, NgOatSkeleton, NgOatSpinner, NgOatSplitButton, NgOatSwitch, NgOatTable, NgOatTabs, NgOatTabsComponent, NgOatTextarea, NgOatThemeRef, NgOatToast, NgOatToggle, NgOatToggleGroup, NgOatTooltip, NgOatTooltipComponent, OAT_TOKEN_MAP, OAT_VERSION, OAT_VERSION_TOKEN, TOOLTIP_POSITIONER, provideNgOat, provideNgOatTheme };
|
|
5513
|
+
export { NG_OAT_CHIP_GROUP, NG_OAT_TOGGLE_GROUP, NgOatAccordion, NgOatAlert, NgOatAvatar, NgOatBadge, NgOatBreadcrumb, NgOatButton, NgOatCard, NgOatCardCarousel, NgOatCardFooter, NgOatCardHeader, NgOatCarousel, NgOatCheckbox, NgOatChip, NgOatChipGroup, NgOatChipInput, NgOatDialog, NgOatDialogComponent, NgOatDropdown, NgOatDropdownComponent, NgOatFileUpload, NgOatFormError, NgOatInput, NgOatInputOtp, NgOatMeter, NgOatPagination, NgOatProgress, NgOatRadioGroup, NgOatSearchInput, NgOatSelect, NgOatSeparator, NgOatSidebar, NgOatSidebarComponent, NgOatSkeleton, NgOatSpinner, NgOatSplitButton, NgOatSwitch, NgOatTable, NgOatTabs, NgOatTabsComponent, NgOatTextarea, NgOatThemeRef, NgOatThemeSelector, NgOatThemeSelectorIcon, NgOatToast, NgOatToggle, NgOatToggleGroup, NgOatToolbar, NgOatToolbarRow, NgOatTooltip, NgOatTooltipComponent, OAT_TOKEN_MAP, OAT_VERSION, OAT_VERSION_TOKEN, TOOLTIP_POSITIONER, provideNgOat, provideNgOatTheme };
|
|
4874
5514
|
//# sourceMappingURL=letsprogram-ng-oat.mjs.map
|