@letsprogram/ng-oat 0.1.1 → 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 +717 -75
- package/package.json +13 -4
- package/types/letsprogram-ng-oat.d.ts +213 -25
- 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,35 +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:
|
|
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.
|
|
25
|
+
*
|
|
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.
|
|
26
29
|
*
|
|
27
|
-
* Include in your app config providers array:
|
|
28
30
|
* ```ts
|
|
29
|
-
* provideNgOat()
|
|
30
|
-
* provideNgOat({ assets: { css:
|
|
31
|
-
* provideNgOat({ basePath: '/custom/oat' }) // custom asset location
|
|
31
|
+
* provideNgOat() // recommended (CSS via angular.json / styles.css)
|
|
32
|
+
* provideNgOat({ assets: { css: 'link' } }) // opt-in runtime CSS injection *
|
|
32
33
|
* ```
|
|
34
|
+
*
|
|
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' }`
|
|
33
37
|
*/
|
|
34
38
|
function provideNgOat(options) {
|
|
35
39
|
const opts = {
|
|
@@ -45,19 +49,12 @@ function provideNgOat(options) {
|
|
|
45
49
|
return;
|
|
46
50
|
const base = opts.basePath.replace(/\/+$/, '');
|
|
47
51
|
const promises = [];
|
|
48
|
-
// CSS
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
promises.push(injectTag(doc,
|
|
52
|
-
// Also inject the token layer CSS
|
|
53
|
-
promises.push(injectTag(doc, 'link', { rel: 'stylesheet', href: `${base}/tokens.css` }, 'ng-oat-tokens-css'));
|
|
52
|
+
// CSS — only when explicitly opted in
|
|
53
|
+
if (opts.assets.css === 'link') {
|
|
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'));
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
const jsMode = opts.assets.js === 'auto' ? 'script' : opts.assets.js;
|
|
57
|
-
if (jsMode === 'script') {
|
|
58
|
-
promises.push(injectTag(doc, 'script', { src: `${base}/oat.min.js`, defer: '' }, 'ng-oat-js'));
|
|
59
|
-
}
|
|
60
|
-
return Promise.all(promises).then(() => { });
|
|
57
|
+
return promises.length ? Promise.all(promises).then(() => { }) : undefined;
|
|
61
58
|
}),
|
|
62
59
|
]);
|
|
63
60
|
}
|
|
@@ -679,7 +676,7 @@ class NgOatSidebar {
|
|
|
679
676
|
this.close();
|
|
680
677
|
}
|
|
681
678
|
});
|
|
682
|
-
// Listen for toggle clicks within our layout
|
|
679
|
+
// Listen for toggle clicks within our layout
|
|
683
680
|
this.listen(host, 'click', (e) => {
|
|
684
681
|
const toggle = e.target.closest('[data-sidebar-toggle]');
|
|
685
682
|
if (toggle) {
|
|
@@ -747,10 +744,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
747
744
|
}], ctorParameters: () => [], propDecorators: { ngOatSidebarScrollLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngOatSidebarScrollLock", required: false }] }], ngOatSidebarChange: [{ type: i0.Output, args: ["ngOatSidebarChange"] }] } });
|
|
748
745
|
|
|
749
746
|
/**
|
|
750
|
-
* 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.
|
|
751
750
|
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
751
|
+
* Works without oat.js — handles tab activation, ARIA attributes,
|
|
752
|
+
* panel visibility and full keyboard navigation internally.
|
|
754
753
|
*
|
|
755
754
|
* Usage:
|
|
756
755
|
* ```html
|
|
@@ -775,36 +774,98 @@ class NgOatTabs {
|
|
|
775
774
|
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
|
|
776
775
|
/** Emits on tab change with { index, tab } */
|
|
777
776
|
ngOatTabChange = output();
|
|
778
|
-
|
|
777
|
+
tabEls = [];
|
|
778
|
+
panelEls = [];
|
|
779
|
+
cleanupFns = [];
|
|
779
780
|
constructor() {
|
|
780
781
|
afterNextRender(() => {
|
|
781
782
|
if (!isPlatformBrowser(this.platformId))
|
|
782
783
|
return;
|
|
783
784
|
const host = this.el.nativeElement;
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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();
|
|
790
841
|
}
|
|
791
842
|
};
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
this.activeIndex.set(host.activeIndex);
|
|
796
|
-
}
|
|
843
|
+
tablist?.addEventListener('click', onClick);
|
|
844
|
+
tablist?.addEventListener('keydown', onKeydown);
|
|
845
|
+
this.cleanupFns.push(() => tablist?.removeEventListener('click', onClick), () => tablist?.removeEventListener('keydown', onKeydown));
|
|
797
846
|
this.destroyRef.onDestroy(() => {
|
|
798
|
-
|
|
847
|
+
this.cleanupFns.forEach(fn => fn());
|
|
848
|
+
this.cleanupFns = [];
|
|
799
849
|
});
|
|
800
850
|
});
|
|
801
851
|
}
|
|
802
852
|
/** Programmatically select a tab by index */
|
|
803
853
|
selectTab(index) {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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] });
|
|
808
869
|
}
|
|
809
870
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatTabs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
810
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 });
|
|
@@ -920,10 +981,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
920
981
|
}], ctorParameters: () => [], propDecorators: { ngOatDialogClose: [{ type: i0.Output, args: ["ngOatDialogClose"] }] } });
|
|
921
982
|
|
|
922
983
|
/**
|
|
923
|
-
* Angular service
|
|
984
|
+
* Angular service for Oat-styled toast notifications.
|
|
924
985
|
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
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.
|
|
927
989
|
*
|
|
928
990
|
* Usage:
|
|
929
991
|
* ```ts
|
|
@@ -937,11 +999,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
937
999
|
class NgOatToast {
|
|
938
1000
|
platformId = inject(PLATFORM_ID);
|
|
939
1001
|
doc = inject(DOCUMENT);
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
return this.doc.defaultView?.ot ?? null;
|
|
944
|
-
}
|
|
1002
|
+
/** Cache of toast containers keyed by placement */
|
|
1003
|
+
containers = new Map();
|
|
1004
|
+
// ── Convenience methods ─────────────────────────────────────────────
|
|
945
1005
|
success(message, title, options) {
|
|
946
1006
|
this.show(message, title, { ...options, variant: 'success' });
|
|
947
1007
|
}
|
|
@@ -954,44 +1014,62 @@ class NgOatToast {
|
|
|
954
1014
|
error(message, title, options) {
|
|
955
1015
|
this.show(message, title, { ...options, variant: 'danger' });
|
|
956
1016
|
}
|
|
1017
|
+
// ── Core show method ────────────────────────────────────────────────
|
|
957
1018
|
show(message, title, options = {}) {
|
|
958
|
-
|
|
959
|
-
if (!ot?.toast) {
|
|
960
|
-
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))
|
|
961
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);
|
|
962
1029
|
}
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
1030
|
+
const msgEl = this.doc.createElement('div');
|
|
1031
|
+
msgEl.className = 'toast-message';
|
|
1032
|
+
msgEl.textContent = message;
|
|
1033
|
+
el.appendChild(msgEl);
|
|
1034
|
+
if (dismissible) {
|
|
966
1035
|
this.addCloseButton(el);
|
|
967
1036
|
}
|
|
968
|
-
return el;
|
|
1037
|
+
return this.showEl(el, rest);
|
|
969
1038
|
}
|
|
970
1039
|
/**
|
|
971
1040
|
* Show a toast from a DOM element or template element.
|
|
972
|
-
* For Angular TemplateRef, use showTemplate() instead.
|
|
973
1041
|
*/
|
|
974
1042
|
showElement(element, options = {}) {
|
|
975
|
-
|
|
976
|
-
if (!ot?.toast?.el)
|
|
1043
|
+
if (!isPlatformBrowser(this.platformId))
|
|
977
1044
|
return;
|
|
978
|
-
|
|
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);
|
|
979
1056
|
}
|
|
980
1057
|
/**
|
|
981
1058
|
* Show a toast from an Angular TemplateRef.
|
|
982
1059
|
* Requires a ViewContainerRef to render the template.
|
|
983
1060
|
*/
|
|
984
1061
|
showTemplate(templateRef, vcr, options = {}) {
|
|
985
|
-
|
|
986
|
-
if (!ot?.toast?.el)
|
|
1062
|
+
if (!isPlatformBrowser(this.platformId))
|
|
987
1063
|
return;
|
|
988
1064
|
const view = vcr.createEmbeddedView(templateRef);
|
|
989
1065
|
view.detectChanges();
|
|
990
|
-
// Wrap template nodes in a container
|
|
991
1066
|
const container = this.doc.createElement('div');
|
|
992
1067
|
view.rootNodes.forEach((node) => container.appendChild(node));
|
|
993
|
-
|
|
994
|
-
|
|
1068
|
+
const { dismissible, ...rest } = options;
|
|
1069
|
+
if (dismissible) {
|
|
1070
|
+
this.addCloseButton(container);
|
|
1071
|
+
}
|
|
1072
|
+
this.showEl(container, rest);
|
|
995
1073
|
const duration = options.duration ?? 4000;
|
|
996
1074
|
if (duration > 0) {
|
|
997
1075
|
setTimeout(() => view.destroy(), duration + 500);
|
|
@@ -999,7 +1077,82 @@ class NgOatToast {
|
|
|
999
1077
|
}
|
|
1000
1078
|
/** Dismiss toasts. If placement given, only that position; otherwise all. */
|
|
1001
1079
|
dismiss(placement) {
|
|
1002
|
-
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);
|
|
1003
1156
|
}
|
|
1004
1157
|
/** Add a close button to a toast element */
|
|
1005
1158
|
addCloseButton(el) {
|
|
@@ -1011,10 +1164,8 @@ class NgOatToast {
|
|
|
1011
1164
|
btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
|
|
1012
1165
|
btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.6'; });
|
|
1013
1166
|
btn.addEventListener('click', () => {
|
|
1014
|
-
el.
|
|
1015
|
-
|
|
1016
|
-
el.addEventListener('transitionend', cleanup, { once: true });
|
|
1017
|
-
setTimeout(cleanup, 350);
|
|
1167
|
+
const container = el.parentElement;
|
|
1168
|
+
this.removeToast(el, container);
|
|
1018
1169
|
});
|
|
1019
1170
|
el.style.position = 'relative';
|
|
1020
1171
|
el.style.paddingRight = '2rem';
|
|
@@ -1973,17 +2124,21 @@ class NgOatDropdownComponent {
|
|
|
1973
2124
|
const items = host.querySelectorAll('[role="menuitem"]');
|
|
1974
2125
|
items[0]?.focus();
|
|
1975
2126
|
this.triggerEl?.setAttribute('aria-expanded', 'true');
|
|
2127
|
+
// Add keyboard navigation
|
|
2128
|
+
this.popoverEl?.addEventListener('keydown', this.onKeydown);
|
|
1976
2129
|
}
|
|
1977
2130
|
else {
|
|
1978
2131
|
window.removeEventListener('scroll', this.positionFn, true);
|
|
1979
2132
|
window.removeEventListener('resize', this.positionFn);
|
|
1980
2133
|
this.triggerEl?.setAttribute('aria-expanded', 'false');
|
|
1981
2134
|
this.triggerEl?.focus();
|
|
2135
|
+
this.popoverEl?.removeEventListener('keydown', this.onKeydown);
|
|
1982
2136
|
}
|
|
1983
2137
|
};
|
|
1984
2138
|
this.popoverEl.addEventListener('toggle', onToggle);
|
|
1985
2139
|
this.destroyRef.onDestroy(() => {
|
|
1986
2140
|
this.popoverEl?.removeEventListener('toggle', onToggle);
|
|
2141
|
+
this.popoverEl?.removeEventListener('keydown', this.onKeydown);
|
|
1987
2142
|
window.removeEventListener('scroll', this.positionFn, true);
|
|
1988
2143
|
window.removeEventListener('resize', this.positionFn);
|
|
1989
2144
|
});
|
|
@@ -1998,6 +2153,44 @@ class NgOatDropdownComponent {
|
|
|
1998
2153
|
toggle() {
|
|
1999
2154
|
this.popoverEl?.togglePopover?.();
|
|
2000
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);
|
|
2001
2194
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgOatDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2002
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: `
|
|
2003
2196
|
<ot-dropdown>
|
|
@@ -4324,6 +4517,455 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
4324
4517
|
`, styles: [":host{display:block}\n"] }]
|
|
4325
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 }] }] } });
|
|
4326
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
|
+
|
|
4327
4969
|
/**
|
|
4328
4970
|
* Oat-styled text input implementing `FormValueControl<string>`.
|
|
4329
4971
|
* Works seamlessly with Signal Forms `[formField]` — no CVA needed.
|
|
@@ -4868,5 +5510,5 @@ const OAT_VERSION_TOKEN = new InjectionToken('OAT_VERSION', {
|
|
|
4868
5510
|
* Generated bundle index. Do not edit.
|
|
4869
5511
|
*/
|
|
4870
5512
|
|
|
4871
|
-
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 };
|
|
4872
5514
|
//# sourceMappingURL=letsprogram-ng-oat.mjs.map
|