@besaitech/ng-design-system 0.0.81 → 0.0.82
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.
|
@@ -5826,6 +5826,127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
5826
5826
|
}]
|
|
5827
5827
|
}], propDecorators: { level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
5828
5828
|
|
|
5829
|
+
/**
|
|
5830
|
+
* Where a floating panel goes, and the plumbing that keeps it there.
|
|
5831
|
+
*
|
|
5832
|
+
* <p><b>Why this exists.</b> Ten components in this library append a panel to `<body>` and position
|
|
5833
|
+
* it against their trigger, and nine of them had their own copy of the same twenty-odd lines. The
|
|
5834
|
+
* copies had already drifted: some clamp the panel inside the viewport vertically and some only
|
|
5835
|
+
* flip it, some match the trigger's width and some do not. Worse, the *lifecycle* was duplicated
|
|
5836
|
+
* too — and `sds-autocomplete` shipped a version that removed the panel through a `viewChild`
|
|
5837
|
+
* already cleared by `ngOnDestroy`, orphaning it in `<body>` for ever. That bug could only exist in
|
|
5838
|
+
* one copy of nine.
|
|
5839
|
+
*/
|
|
5840
|
+
/**
|
|
5841
|
+
* The arithmetic, with no DOM in it.
|
|
5842
|
+
*
|
|
5843
|
+
* <p>Vertical: below the trigger, flipped above when it does not fit below and fits better above —
|
|
5844
|
+
* then <b>clamped inside the viewport</b>. The clamp is not decoration: without it a panel taller
|
|
5845
|
+
* than the space either side is pushed off the top edge and its first rows become unreachable, and
|
|
5846
|
+
* four of the nine copies were missing it.
|
|
5847
|
+
*
|
|
5848
|
+
* <p>Horizontal: the panel's start (or end) edge meets the trigger's, then clamped the same way. A
|
|
5849
|
+
* panel wider than the viewport pins to the left margin rather than running off both sides.
|
|
5850
|
+
*/
|
|
5851
|
+
function placePanel(trigger, panel, viewport, options = {}) {
|
|
5852
|
+
const gap = options.gap ?? 4;
|
|
5853
|
+
const margin = options.margin ?? 8;
|
|
5854
|
+
const roomBelow = viewport.height - trigger.bottom - gap;
|
|
5855
|
+
const roomAbove = trigger.top - gap;
|
|
5856
|
+
const above = options.prefer === 'above'
|
|
5857
|
+
? roomAbove >= panel.height || roomAbove > roomBelow
|
|
5858
|
+
: roomBelow < panel.height && roomAbove > roomBelow;
|
|
5859
|
+
const top = clamp(above ? trigger.top - gap - panel.height : trigger.bottom + gap, margin, viewport.height - margin - panel.height);
|
|
5860
|
+
const left = clamp(options.align === 'end' ? trigger.right - panel.width : trigger.left, margin, viewport.width - margin - panel.width);
|
|
5861
|
+
return { top: Math.round(top), left: Math.round(left) };
|
|
5862
|
+
}
|
|
5863
|
+
/**
|
|
5864
|
+
* A viewport bound that may be smaller than the lower one — when the panel is taller than the
|
|
5865
|
+
* window there is no position that satisfies both, and the top edge is the one that must win, or
|
|
5866
|
+
* the panel starts off-screen and cannot be scrolled back.
|
|
5867
|
+
*/
|
|
5868
|
+
function clamp(value, low, high) {
|
|
5869
|
+
return Math.max(low, Math.min(value, Math.max(low, high)));
|
|
5870
|
+
}
|
|
5871
|
+
/**
|
|
5872
|
+
* The DOM half: append to `<body>`, place, follow scroll and resize, and — the part that was worth
|
|
5873
|
+
* centralising — take it all down again exactly once.
|
|
5874
|
+
*
|
|
5875
|
+
* <p>Hold one of these per component and call {@link detach} from `ngOnDestroy`. It keeps its own
|
|
5876
|
+
* reference to the panel element, so it does not depend on a `viewChild` that Angular has already
|
|
5877
|
+
* cleared by the time the component is torn down.
|
|
5878
|
+
*/
|
|
5879
|
+
class SdsOverlayPanel {
|
|
5880
|
+
trigger = null;
|
|
5881
|
+
panel = null;
|
|
5882
|
+
options = {};
|
|
5883
|
+
listening = false;
|
|
5884
|
+
reflow = () => this.reposition();
|
|
5885
|
+
/** Attach (or re-attach) the panel to the body and start following the trigger. */
|
|
5886
|
+
attach(trigger, panel, options = {}) {
|
|
5887
|
+
this.trigger = trigger;
|
|
5888
|
+
this.panel = panel;
|
|
5889
|
+
this.options = options;
|
|
5890
|
+
if (panel.parentElement !== document.body) {
|
|
5891
|
+
document.body.appendChild(panel);
|
|
5892
|
+
}
|
|
5893
|
+
this.reposition();
|
|
5894
|
+
if (!this.listening) {
|
|
5895
|
+
// `true` for scroll: a panel inside a scrolling container must follow it, and scroll does
|
|
5896
|
+
// not bubble.
|
|
5897
|
+
window.addEventListener('scroll', this.reflow, true);
|
|
5898
|
+
window.addEventListener('resize', this.reflow);
|
|
5899
|
+
this.listening = true;
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
reposition() {
|
|
5903
|
+
const trigger = this.trigger;
|
|
5904
|
+
const panel = this.panel;
|
|
5905
|
+
if (!trigger || !panel) {
|
|
5906
|
+
return;
|
|
5907
|
+
}
|
|
5908
|
+
const r = trigger.getBoundingClientRect();
|
|
5909
|
+
// Before measuring, because the width we are about to impose is the width placePanel must see.
|
|
5910
|
+
if (this.options.width === 'min') {
|
|
5911
|
+
panel.style.minWidth = `${Math.round(r.width)}px`;
|
|
5912
|
+
}
|
|
5913
|
+
else if (this.options.width === 'exact') {
|
|
5914
|
+
panel.style.width = `${Math.round(r.width)}px`;
|
|
5915
|
+
}
|
|
5916
|
+
const { top, left } = placePanel(r,
|
|
5917
|
+
// offset* is 0 before the panel has laid out; the fallbacks keep the first frame sane.
|
|
5918
|
+
{ width: panel.offsetWidth || r.width, height: panel.offsetHeight || 240 }, { width: window.innerWidth, height: window.innerHeight }, this.options);
|
|
5919
|
+
panel.style.top = `${top}px`;
|
|
5920
|
+
panel.style.left = `${left}px`;
|
|
5921
|
+
}
|
|
5922
|
+
/**
|
|
5923
|
+
* The live panel element, or null while closed.
|
|
5924
|
+
*
|
|
5925
|
+
* <p>Read-only on purpose. A component that needs to reach inside the panel — to focus a row, to
|
|
5926
|
+
* trap Tab, to read its options — asks here rather than keeping its own field. A second
|
|
5927
|
+
* reference is how the autocomplete came to remove the panel through a `viewChild` that Angular
|
|
5928
|
+
* had already cleared, which is the bug this class exists to make unrepeatable.
|
|
5929
|
+
*/
|
|
5930
|
+
get element() {
|
|
5931
|
+
return this.panel;
|
|
5932
|
+
}
|
|
5933
|
+
/** Does the panel contain this node? For "did the click land outside" checks. */
|
|
5934
|
+
contains(node) {
|
|
5935
|
+
return !!node && !!this.panel?.contains(node);
|
|
5936
|
+
}
|
|
5937
|
+
/** Stop following, and remove the panel from the body. Safe to call more than once. */
|
|
5938
|
+
detach() {
|
|
5939
|
+
if (this.listening) {
|
|
5940
|
+
window.removeEventListener('scroll', this.reflow, true);
|
|
5941
|
+
window.removeEventListener('resize', this.reflow);
|
|
5942
|
+
this.listening = false;
|
|
5943
|
+
}
|
|
5944
|
+
this.panel?.remove();
|
|
5945
|
+
this.panel = null;
|
|
5946
|
+
this.trigger = null;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
|
|
5829
5950
|
let nextId$f = 0;
|
|
5830
5951
|
/**
|
|
5831
5952
|
* Marks the element inside `<sds-menu>` that opens it. It may be a native button or a component that
|
|
@@ -5896,7 +6017,8 @@ class SdsMenu {
|
|
|
5896
6017
|
/** An action was activated. */
|
|
5897
6018
|
select = output();
|
|
5898
6019
|
projectedTrigger = contentChild(SdsMenuTrigger, ...(ngDevMode ? [{ debugName: "projectedTrigger" }] : []));
|
|
5899
|
-
|
|
6020
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
6021
|
+
overlay = new SdsOverlayPanel();
|
|
5900
6022
|
/** Which item gets focus when the panel opens: 'first' | 'last' | 'none'. */
|
|
5901
6023
|
focusOnOpen = 'first';
|
|
5902
6024
|
typeBuffer = '';
|
|
@@ -5991,23 +6113,18 @@ class SdsMenu {
|
|
|
5991
6113
|
afterOpen() {
|
|
5992
6114
|
if (!this.open())
|
|
5993
6115
|
return;
|
|
5994
|
-
|
|
5995
|
-
|
|
6116
|
+
const panel = this.host.nativeElement.querySelector('[data-sds-menu-panel]');
|
|
6117
|
+
const trigger = this.triggerEl();
|
|
6118
|
+
if (!panel || !trigger)
|
|
5996
6119
|
return;
|
|
5997
|
-
if (this.panelEl.parentElement !== document.body)
|
|
5998
|
-
document.body.appendChild(this.panelEl);
|
|
5999
6120
|
// Name the menu after its trigger unless an explicit [ariaLabel] was given.
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
this.position();
|
|
6004
|
-
this.addReflowListeners();
|
|
6121
|
+
if (!this.ariaLabel() && trigger.id)
|
|
6122
|
+
panel.setAttribute('aria-labelledby', trigger.id);
|
|
6123
|
+
this.overlay.attach(trigger, panel, { align: this.align() });
|
|
6005
6124
|
this.focusItemAt(this.focusOnOpen);
|
|
6006
6125
|
}
|
|
6007
6126
|
teardown() {
|
|
6008
|
-
this.
|
|
6009
|
-
this.panelEl?.remove();
|
|
6010
|
-
this.panelEl = null;
|
|
6127
|
+
this.overlay.detach();
|
|
6011
6128
|
}
|
|
6012
6129
|
// --- trigger events -------------------------------------------------------
|
|
6013
6130
|
onTriggerClick(event) {
|
|
@@ -6094,12 +6211,12 @@ class SdsMenu {
|
|
|
6094
6211
|
this.typeTimer = setTimeout(() => (this.typeBuffer = ''), 600);
|
|
6095
6212
|
}
|
|
6096
6213
|
focusIndex(i) {
|
|
6097
|
-
this.
|
|
6214
|
+
this.overlay.element?.querySelector(`[data-index="${i}"]`)?.focus();
|
|
6098
6215
|
}
|
|
6099
6216
|
focusItemAt(where) {
|
|
6100
6217
|
const idx = this.enabledIndexes();
|
|
6101
6218
|
if (!idx.length) {
|
|
6102
|
-
this.
|
|
6219
|
+
this.overlay.element?.focus();
|
|
6103
6220
|
return;
|
|
6104
6221
|
}
|
|
6105
6222
|
this.focusIndex(where === 'first' ? idx[0] : idx[idx.length - 1]);
|
|
@@ -6119,47 +6236,10 @@ class SdsMenu {
|
|
|
6119
6236
|
const target = event.target;
|
|
6120
6237
|
if (this.host.nativeElement.contains(target))
|
|
6121
6238
|
return;
|
|
6122
|
-
if (this.
|
|
6239
|
+
if (this.overlay.contains(target))
|
|
6123
6240
|
return;
|
|
6124
6241
|
this.close(false);
|
|
6125
6242
|
}
|
|
6126
|
-
// --- positioning (body-appended, mirrors sds-select) ------------------------
|
|
6127
|
-
reflow = () => this.position();
|
|
6128
|
-
listening = false;
|
|
6129
|
-
addReflowListeners() {
|
|
6130
|
-
if (this.listening)
|
|
6131
|
-
return;
|
|
6132
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
6133
|
-
window.addEventListener('resize', this.reflow);
|
|
6134
|
-
this.listening = true;
|
|
6135
|
-
}
|
|
6136
|
-
removeReflowListeners() {
|
|
6137
|
-
if (!this.listening)
|
|
6138
|
-
return;
|
|
6139
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
6140
|
-
window.removeEventListener('resize', this.reflow);
|
|
6141
|
-
this.listening = false;
|
|
6142
|
-
}
|
|
6143
|
-
position() {
|
|
6144
|
-
const trigger = this.triggerEl();
|
|
6145
|
-
const panel = this.panelEl;
|
|
6146
|
-
if (!trigger || !panel)
|
|
6147
|
-
return;
|
|
6148
|
-
const r = trigger.getBoundingClientRect();
|
|
6149
|
-
const pw = panel.offsetWidth;
|
|
6150
|
-
const ph = panel.offsetHeight;
|
|
6151
|
-
const gap = 4;
|
|
6152
|
-
const margin = 8;
|
|
6153
|
-
let top = r.bottom + gap;
|
|
6154
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
6155
|
-
top = r.top - gap - ph;
|
|
6156
|
-
}
|
|
6157
|
-
top = Math.max(margin, Math.min(top, window.innerHeight - margin - ph));
|
|
6158
|
-
let left = this.align() === 'end' ? r.right - pw : r.left;
|
|
6159
|
-
left = Math.max(margin, Math.min(left, window.innerWidth - margin - pw));
|
|
6160
|
-
panel.style.top = `${Math.round(top)}px`;
|
|
6161
|
-
panel.style.left = `${Math.round(left)}px`;
|
|
6162
|
-
}
|
|
6163
6243
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6164
6244
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsMenu, isStandalone: true, selector: "sds-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, triggerIcon: { classPropertyName: "triggerIcon", publicName: "triggerIcon", isSignal: true, isRequired: false, transformFunction: null }, triggerText: { classPropertyName: "triggerText", publicName: "triggerText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, keepOpen: { classPropertyName: "keepOpen", publicName: "keepOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", select: "select" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "relative inline-flex" }, queries: [{ propertyName: "projectedTrigger", first: true, predicate: SdsMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
6165
6245
|
<span
|
|
@@ -9105,8 +9185,8 @@ class SdsDatePicker {
|
|
|
9105
9185
|
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
9106
9186
|
calendar = viewChild(SdsCalendar, ...(ngDevMode ? [{ debugName: "calendar" }] : []));
|
|
9107
9187
|
trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
|
|
9108
|
-
/**
|
|
9109
|
-
|
|
9188
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
9189
|
+
overlay = new SdsOverlayPanel();
|
|
9110
9190
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
9111
9191
|
displayValue = computed(() => {
|
|
9112
9192
|
const v = this.value();
|
|
@@ -9140,18 +9220,19 @@ class SdsDatePicker {
|
|
|
9140
9220
|
this.onTouched();
|
|
9141
9221
|
// Run after the overlay is rendered, then relocate it to <body>.
|
|
9142
9222
|
afterNextRender(() => {
|
|
9143
|
-
|
|
9144
|
-
this.
|
|
9145
|
-
|
|
9223
|
+
const panel = this.host.nativeElement.querySelector('[role="dialog"]');
|
|
9224
|
+
const trigger = this.trigger()?.nativeElement;
|
|
9225
|
+
if (panel && trigger) {
|
|
9226
|
+
// gap 6, as this field has always used — the shared default is 4.
|
|
9227
|
+
this.overlay.attach(trigger, panel, { gap: 6 });
|
|
9228
|
+
}
|
|
9146
9229
|
this.calendar()?.focusActiveCell();
|
|
9147
9230
|
}, { injector: this.injector });
|
|
9148
9231
|
}
|
|
9149
9232
|
close(returnFocus = false) {
|
|
9150
9233
|
if (!this.open())
|
|
9151
9234
|
return;
|
|
9152
|
-
this.
|
|
9153
|
-
this.panelEl?.remove(); // pull the body-relocated node before destroying the view
|
|
9154
|
-
this.panelEl = null;
|
|
9235
|
+
this.overlay.detach(); // pull the body-relocated node before destroying the view
|
|
9155
9236
|
this.open.set(false);
|
|
9156
9237
|
if (returnFocus) {
|
|
9157
9238
|
this.host.nativeElement.querySelector('input')?.focus();
|
|
@@ -9196,17 +9277,18 @@ class SdsDatePicker {
|
|
|
9196
9277
|
* the page behind the dialog. Keeps Tab / Shift+Tab cycling within the panel's own focusables.
|
|
9197
9278
|
*/
|
|
9198
9279
|
onPanelKeydown(event) {
|
|
9199
|
-
|
|
9280
|
+
const panel = this.overlay.element;
|
|
9281
|
+
if (event.key !== 'Tab' || !panel)
|
|
9200
9282
|
return;
|
|
9201
9283
|
const sel = 'a[href],button:not([disabled]):not([tabindex="-1"]),input:not([disabled]),' +
|
|
9202
9284
|
'[tabindex]:not([tabindex="-1"])';
|
|
9203
|
-
const items = Array.from(
|
|
9285
|
+
const items = Array.from(panel.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
|
|
9204
9286
|
if (items.length === 0)
|
|
9205
9287
|
return;
|
|
9206
9288
|
const first = items[0];
|
|
9207
9289
|
const last = items[items.length - 1];
|
|
9208
9290
|
const active = document.activeElement;
|
|
9209
|
-
if (event.shiftKey && (active === first || !
|
|
9291
|
+
if (event.shiftKey && (active === first || !panel.contains(active))) {
|
|
9210
9292
|
event.preventDefault();
|
|
9211
9293
|
last.focus();
|
|
9212
9294
|
}
|
|
@@ -9220,59 +9302,12 @@ class SdsDatePicker {
|
|
|
9220
9302
|
return;
|
|
9221
9303
|
const target = event.target;
|
|
9222
9304
|
const inField = this.host.nativeElement.contains(target);
|
|
9223
|
-
const inPanel =
|
|
9305
|
+
const inPanel = this.overlay.contains(target);
|
|
9224
9306
|
if (!inField && !inPanel)
|
|
9225
9307
|
this.close();
|
|
9226
9308
|
}
|
|
9227
|
-
// --- body-appended overlay positioning ---
|
|
9228
|
-
reflow = () => this.positionPanel();
|
|
9229
|
-
listening = false;
|
|
9230
|
-
addReflowListeners() {
|
|
9231
|
-
if (this.listening)
|
|
9232
|
-
return;
|
|
9233
|
-
// capture=true so scrolling any ancestor scroll container repositions.
|
|
9234
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
9235
|
-
window.addEventListener('resize', this.reflow);
|
|
9236
|
-
this.listening = true;
|
|
9237
|
-
}
|
|
9238
|
-
removeReflowListeners() {
|
|
9239
|
-
if (!this.listening)
|
|
9240
|
-
return;
|
|
9241
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
9242
|
-
window.removeEventListener('resize', this.reflow);
|
|
9243
|
-
this.listening = false;
|
|
9244
|
-
}
|
|
9245
|
-
positionPanel() {
|
|
9246
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
9247
|
-
const panelEl = this.panelEl;
|
|
9248
|
-
if (!triggerEl || !panelEl)
|
|
9249
|
-
return;
|
|
9250
|
-
// appendTo body: relocate out of any clipping/stacking ancestor.
|
|
9251
|
-
if (panelEl.parentElement !== document.body) {
|
|
9252
|
-
document.body.appendChild(panelEl);
|
|
9253
|
-
}
|
|
9254
|
-
const r = triggerEl.getBoundingClientRect();
|
|
9255
|
-
const ph = panelEl.offsetHeight || 360;
|
|
9256
|
-
const pw = panelEl.offsetWidth || 296;
|
|
9257
|
-
const gap = 6;
|
|
9258
|
-
const margin = 8;
|
|
9259
|
-
// Below the field by default; flip above when there isn't room.
|
|
9260
|
-
let top = r.bottom + gap;
|
|
9261
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
9262
|
-
top = r.top - gap - ph;
|
|
9263
|
-
}
|
|
9264
|
-
// Align to the field's left, but keep it within the viewport.
|
|
9265
|
-
let left = r.left;
|
|
9266
|
-
if (left + pw > window.innerWidth - margin) {
|
|
9267
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
9268
|
-
}
|
|
9269
|
-
// Set directly (position: fixed → viewport coordinates) to avoid a flash.
|
|
9270
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
9271
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
9272
|
-
}
|
|
9273
9309
|
ngOnDestroy() {
|
|
9274
|
-
this.
|
|
9275
|
-
this.panelEl?.remove();
|
|
9310
|
+
this.overlay.detach();
|
|
9276
9311
|
}
|
|
9277
9312
|
setValue(v) {
|
|
9278
9313
|
this.value.set(v);
|
|
@@ -9379,7 +9414,7 @@ class SdsDatePicker {
|
|
|
9379
9414
|
|
|
9380
9415
|
@if (open()) {
|
|
9381
9416
|
<!-- Relocated to document.body on open (appendTo body) to escape
|
|
9382
|
-
ancestor overflow/stacking;
|
|
9417
|
+
ancestor overflow/stacking; placed by SdsOverlayPanel. -->
|
|
9383
9418
|
<div
|
|
9384
9419
|
class="fixed z-[1000]"
|
|
9385
9420
|
role="dialog"
|
|
@@ -9514,7 +9549,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
9514
9549
|
|
|
9515
9550
|
@if (open()) {
|
|
9516
9551
|
<!-- Relocated to document.body on open (appendTo body) to escape
|
|
9517
|
-
ancestor overflow/stacking;
|
|
9552
|
+
ancestor overflow/stacking; placed by SdsOverlayPanel. -->
|
|
9518
9553
|
<div
|
|
9519
9554
|
class="fixed z-[1000]"
|
|
9520
9555
|
role="dialog"
|
|
@@ -9943,7 +9978,12 @@ class SdsRangeCalendar {
|
|
|
9943
9978
|
}
|
|
9944
9979
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsRangeCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9945
9980
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsRangeCalendar, isStandalone: true, selector: "sds-range-calendar", inputs: { start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, end: { classPropertyName: "end", publicName: "end", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, maxDays: { classPropertyName: "maxDays", publicName: "maxDays", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, months: { classPropertyName: "months", publicName: "months", isSignal: true, isRequired: false, transformFunction: null }, calendarLabel: { classPropertyName: "calendarLabel", publicName: "calendarLabel", isSignal: true, isRequired: false, transformFunction: null }, previousLabel: { classPropertyName: "previousLabel", publicName: "previousLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { start: "startChange", end: "endChange", daySelect: "daySelect", rangeSelect: "rangeSelect", dayHover: "dayHover" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
9946
|
-
<div
|
|
9981
|
+
<div
|
|
9982
|
+
class="select-none"
|
|
9983
|
+
role="group"
|
|
9984
|
+
[attr.aria-label]="calendarLabel()"
|
|
9985
|
+
(mouseleave)="setHover(null)"
|
|
9986
|
+
>
|
|
9947
9987
|
<div class="flex flex-col gap-4 sm:flex-row sm:gap-6">
|
|
9948
9988
|
@for (m of monthViews(); track m.key; let first = $first; let last = $last) {
|
|
9949
9989
|
<div class="w-[17.5rem]">
|
|
@@ -9979,7 +10019,11 @@ class SdsRangeCalendar {
|
|
|
9979
10019
|
}
|
|
9980
10020
|
</div>
|
|
9981
10021
|
|
|
9982
|
-
<table
|
|
10022
|
+
<table
|
|
10023
|
+
class="w-full table-fixed border-collapse"
|
|
10024
|
+
role="grid"
|
|
10025
|
+
[attr.aria-label]="m.title"
|
|
10026
|
+
>
|
|
9983
10027
|
<thead>
|
|
9984
10028
|
<tr>
|
|
9985
10029
|
@for (wd of weekdayLabels(); track $index) {
|
|
@@ -10036,7 +10080,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10036
10080
|
imports: [SdsIcon],
|
|
10037
10081
|
host: { class: 'block' },
|
|
10038
10082
|
template: `
|
|
10039
|
-
<div
|
|
10083
|
+
<div
|
|
10084
|
+
class="select-none"
|
|
10085
|
+
role="group"
|
|
10086
|
+
[attr.aria-label]="calendarLabel()"
|
|
10087
|
+
(mouseleave)="setHover(null)"
|
|
10088
|
+
>
|
|
10040
10089
|
<div class="flex flex-col gap-4 sm:flex-row sm:gap-6">
|
|
10041
10090
|
@for (m of monthViews(); track m.key; let first = $first; let last = $last) {
|
|
10042
10091
|
<div class="w-[17.5rem]">
|
|
@@ -10072,7 +10121,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10072
10121
|
}
|
|
10073
10122
|
</div>
|
|
10074
10123
|
|
|
10075
|
-
<table
|
|
10124
|
+
<table
|
|
10125
|
+
class="w-full table-fixed border-collapse"
|
|
10126
|
+
role="grid"
|
|
10127
|
+
[attr.aria-label]="m.title"
|
|
10128
|
+
>
|
|
10076
10129
|
<thead>
|
|
10077
10130
|
<tr>
|
|
10078
10131
|
@for (wd of weekdayLabels(); track $index) {
|
|
@@ -10220,8 +10273,8 @@ class SdsDateRangePicker {
|
|
|
10220
10273
|
pendingEnd = signal(null, ...(ngDevMode ? [{ debugName: "pendingEnd" }] : []));
|
|
10221
10274
|
calendar = viewChild(SdsRangeCalendar, ...(ngDevMode ? [{ debugName: "calendar" }] : []));
|
|
10222
10275
|
trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
|
|
10223
|
-
/**
|
|
10224
|
-
|
|
10276
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
10277
|
+
overlay = new SdsOverlayPanel();
|
|
10225
10278
|
/** Id of the preset the current value equals, or `null` (no value, or a custom range). */
|
|
10226
10279
|
activePresetId = computed(() => this.presetIdFor(this.value()), ...(ngDevMode ? [{ debugName: "activePresetId" }] : []));
|
|
10227
10280
|
pendingPresetId = computed(() => {
|
|
@@ -10290,9 +10343,12 @@ class SdsDateRangePicker {
|
|
|
10290
10343
|
this.open.set(true);
|
|
10291
10344
|
// Run after the overlay is rendered, then relocate it to <body>.
|
|
10292
10345
|
afterNextRender(() => {
|
|
10293
|
-
|
|
10294
|
-
this.
|
|
10295
|
-
|
|
10346
|
+
const panel = this.host.nativeElement.querySelector('[role="dialog"]');
|
|
10347
|
+
const trigger = this.trigger()?.nativeElement;
|
|
10348
|
+
if (panel && trigger) {
|
|
10349
|
+
// gap 6, as this field has always used — the shared default is 4.
|
|
10350
|
+
this.overlay.attach(trigger, panel, { gap: 6 });
|
|
10351
|
+
}
|
|
10296
10352
|
this.calendar()?.focusActiveCell();
|
|
10297
10353
|
}, { injector: this.injector });
|
|
10298
10354
|
}
|
|
@@ -10300,9 +10356,7 @@ class SdsDateRangePicker {
|
|
|
10300
10356
|
close(returnFocus = false) {
|
|
10301
10357
|
if (!this.open())
|
|
10302
10358
|
return;
|
|
10303
|
-
this.
|
|
10304
|
-
this.panelEl?.remove(); // pull the body-relocated node before destroying the view
|
|
10305
|
-
this.panelEl = null;
|
|
10359
|
+
this.overlay.detach(); // pull the body-relocated node before destroying the view
|
|
10306
10360
|
this.open.set(false);
|
|
10307
10361
|
if (returnFocus)
|
|
10308
10362
|
this.trigger()?.nativeElement.focus();
|
|
@@ -10339,17 +10393,18 @@ class SdsDateRangePicker {
|
|
|
10339
10393
|
* modal's focus trap; this keeps Tab / Shift+Tab cycling within the panel's own focusables.
|
|
10340
10394
|
*/
|
|
10341
10395
|
onPanelKeydown(event) {
|
|
10342
|
-
|
|
10396
|
+
const panel = this.overlay.element;
|
|
10397
|
+
if (event.key !== 'Tab' || !panel)
|
|
10343
10398
|
return;
|
|
10344
10399
|
const sel = 'a[href],button:not([disabled]):not([tabindex="-1"]),input:not([disabled]),' +
|
|
10345
10400
|
'[tabindex]:not([tabindex="-1"])';
|
|
10346
|
-
const items = Array.from(
|
|
10401
|
+
const items = Array.from(panel.querySelectorAll(sel)).filter((el) => el.offsetParent !== null);
|
|
10347
10402
|
if (items.length === 0)
|
|
10348
10403
|
return;
|
|
10349
10404
|
const first = items[0];
|
|
10350
10405
|
const last = items[items.length - 1];
|
|
10351
10406
|
const active = document.activeElement;
|
|
10352
|
-
if (event.shiftKey && (active === first || !
|
|
10407
|
+
if (event.shiftKey && (active === first || !panel.contains(active))) {
|
|
10353
10408
|
event.preventDefault();
|
|
10354
10409
|
last.focus();
|
|
10355
10410
|
}
|
|
@@ -10364,58 +10419,13 @@ class SdsDateRangePicker {
|
|
|
10364
10419
|
// composedPath is fixed at dispatch time, so a click on a node re-rendered away still counts.
|
|
10365
10420
|
const path = event.composedPath();
|
|
10366
10421
|
const inField = path.includes(this.host.nativeElement);
|
|
10367
|
-
const inPanel = !!this.
|
|
10422
|
+
const inPanel = !!this.overlay.element && path.includes(this.overlay.element);
|
|
10368
10423
|
if (!inField && !inPanel)
|
|
10369
10424
|
this.close();
|
|
10370
10425
|
}
|
|
10371
10426
|
// --- body-appended overlay positioning ---
|
|
10372
|
-
reflow = () => this.positionPanel();
|
|
10373
|
-
listening = false;
|
|
10374
|
-
addReflowListeners() {
|
|
10375
|
-
if (this.listening)
|
|
10376
|
-
return;
|
|
10377
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
10378
|
-
window.addEventListener('resize', this.reflow);
|
|
10379
|
-
this.listening = true;
|
|
10380
|
-
}
|
|
10381
|
-
removeReflowListeners() {
|
|
10382
|
-
if (!this.listening)
|
|
10383
|
-
return;
|
|
10384
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
10385
|
-
window.removeEventListener('resize', this.reflow);
|
|
10386
|
-
this.listening = false;
|
|
10387
|
-
}
|
|
10388
|
-
positionPanel() {
|
|
10389
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
10390
|
-
const panelEl = this.panelEl;
|
|
10391
|
-
if (!triggerEl || !panelEl)
|
|
10392
|
-
return;
|
|
10393
|
-
if (panelEl.parentElement !== document.body) {
|
|
10394
|
-
document.body.appendChild(panelEl);
|
|
10395
|
-
}
|
|
10396
|
-
const r = triggerEl.getBoundingClientRect();
|
|
10397
|
-
const ph = panelEl.offsetHeight || 420;
|
|
10398
|
-
const pw = panelEl.offsetWidth || 760;
|
|
10399
|
-
const gap = 6;
|
|
10400
|
-
const margin = 8;
|
|
10401
|
-
// Below the field by default; flip above when there isn't room; else clamp into the viewport.
|
|
10402
|
-
let top = r.bottom + gap;
|
|
10403
|
-
if (top + ph > window.innerHeight - margin) {
|
|
10404
|
-
if (r.top - gap - ph > margin)
|
|
10405
|
-
top = r.top - gap - ph;
|
|
10406
|
-
else
|
|
10407
|
-
top = Math.max(margin, window.innerHeight - margin - ph);
|
|
10408
|
-
}
|
|
10409
|
-
let left = r.left;
|
|
10410
|
-
if (left + pw > window.innerWidth - margin) {
|
|
10411
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
10412
|
-
}
|
|
10413
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
10414
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
10415
|
-
}
|
|
10416
10427
|
ngOnDestroy() {
|
|
10417
|
-
this.
|
|
10418
|
-
this.panelEl?.remove();
|
|
10428
|
+
this.overlay.detach();
|
|
10419
10429
|
}
|
|
10420
10430
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10421
10431
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsDateRangePicker, isStandalone: true, selector: "sds-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, maxDays: { classPropertyName: "maxDays", publicName: "maxDays", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, months: { classPropertyName: "months", publicName: "months", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, panelLabel: { classPropertyName: "panelLabel", publicName: "panelLabel", isSignal: true, isRequired: false, transformFunction: null }, presetsLabel: { classPropertyName: "presetsLabel", publicName: "presetsLabel", isSignal: true, isRequired: false, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, endLabel: { classPropertyName: "endLabel", publicName: "endLabel", isSignal: true, isRequired: false, transformFunction: null }, applyLabel: { classPropertyName: "applyLabel", publicName: "applyLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, calendarLabel: { classPropertyName: "calendarLabel", publicName: "calendarLabel", isSignal: true, isRequired: false, transformFunction: null }, previousLabel: { classPropertyName: "previousLabel", publicName: "previousLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", presetSelect: "presetSelect" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape($event)" }, classAttribute: "block" }, viewQueries: [{ propertyName: "calendar", first: true, predicate: SdsRangeCalendar, descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
@@ -10433,7 +10443,9 @@ class SdsDateRangePicker {
|
|
|
10433
10443
|
aria-haspopup="dialog"
|
|
10434
10444
|
[attr.aria-expanded]="open()"
|
|
10435
10445
|
[attr.aria-labelledby]="label() ? labelId + ' ' + valueId : null"
|
|
10436
|
-
[attr.aria-label]="
|
|
10446
|
+
[attr.aria-label]="
|
|
10447
|
+
!label() && ariaLabel() ? ariaLabel() + ', ' + (displayValue() || placeholder()) : null
|
|
10448
|
+
"
|
|
10437
10449
|
[class]="triggerClasses()"
|
|
10438
10450
|
(click)="toggle($event)"
|
|
10439
10451
|
(keydown.arrowdown)="onTriggerArrowDown($event)"
|
|
@@ -10458,7 +10470,7 @@ class SdsDateRangePicker {
|
|
|
10458
10470
|
|
|
10459
10471
|
@if (open()) {
|
|
10460
10472
|
<!-- Relocated to document.body on open (appendTo body) to escape ancestor overflow/stacking;
|
|
10461
|
-
|
|
10473
|
+
placed by SdsOverlayPanel. -->
|
|
10462
10474
|
<div
|
|
10463
10475
|
class="fixed z-[1000] max-w-[calc(100vw-1rem)] overflow-auto rounded-card border border-border bg-surface shadow-lg"
|
|
10464
10476
|
style="max-height: calc(100vh - 1rem)"
|
|
@@ -10475,7 +10487,9 @@ class SdsDateRangePicker {
|
|
|
10475
10487
|
role="group"
|
|
10476
10488
|
[attr.aria-label]="presetsLabel()"
|
|
10477
10489
|
>
|
|
10478
|
-
<p
|
|
10490
|
+
<p
|
|
10491
|
+
class="px-2 pb-1.5 pt-1 text-[11px] font-semibold uppercase tracking-wide text-ink-faint"
|
|
10492
|
+
>
|
|
10479
10493
|
{{ presetsLabel() }}
|
|
10480
10494
|
</p>
|
|
10481
10495
|
<div class="flex flex-wrap gap-1 sm:flex-col sm:flex-nowrap">
|
|
@@ -10515,15 +10529,27 @@ class SdsDateRangePicker {
|
|
|
10515
10529
|
>
|
|
10516
10530
|
<p class="flex items-stretch gap-2 text-sm" aria-live="polite">
|
|
10517
10531
|
<span class="flex flex-col leading-tight">
|
|
10518
|
-
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10519
|
-
|
|
10532
|
+
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10533
|
+
startLabel()
|
|
10534
|
+
}}</span>
|
|
10535
|
+
<span
|
|
10536
|
+
class="font-medium"
|
|
10537
|
+
[class.text-ink]="pendingStart()"
|
|
10538
|
+
[class.text-ink-faint]="!pendingStart()"
|
|
10539
|
+
>
|
|
10520
10540
|
{{ pendingStartText() }}
|
|
10521
10541
|
</span>
|
|
10522
10542
|
</span>
|
|
10523
10543
|
<span class="self-end text-ink-faint" aria-hidden="true">–</span>
|
|
10524
10544
|
<span class="flex flex-col leading-tight">
|
|
10525
|
-
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10526
|
-
|
|
10545
|
+
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10546
|
+
endLabel()
|
|
10547
|
+
}}</span>
|
|
10548
|
+
<span
|
|
10549
|
+
class="font-medium"
|
|
10550
|
+
[class.text-ink]="pendingEnd()"
|
|
10551
|
+
[class.text-ink-faint]="!pendingEnd()"
|
|
10552
|
+
>
|
|
10527
10553
|
{{ pendingEndText() }}
|
|
10528
10554
|
</span>
|
|
10529
10555
|
</span>
|
|
@@ -10569,7 +10595,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10569
10595
|
aria-haspopup="dialog"
|
|
10570
10596
|
[attr.aria-expanded]="open()"
|
|
10571
10597
|
[attr.aria-labelledby]="label() ? labelId + ' ' + valueId : null"
|
|
10572
|
-
[attr.aria-label]="
|
|
10598
|
+
[attr.aria-label]="
|
|
10599
|
+
!label() && ariaLabel() ? ariaLabel() + ', ' + (displayValue() || placeholder()) : null
|
|
10600
|
+
"
|
|
10573
10601
|
[class]="triggerClasses()"
|
|
10574
10602
|
(click)="toggle($event)"
|
|
10575
10603
|
(keydown.arrowdown)="onTriggerArrowDown($event)"
|
|
@@ -10594,7 +10622,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10594
10622
|
|
|
10595
10623
|
@if (open()) {
|
|
10596
10624
|
<!-- Relocated to document.body on open (appendTo body) to escape ancestor overflow/stacking;
|
|
10597
|
-
|
|
10625
|
+
placed by SdsOverlayPanel. -->
|
|
10598
10626
|
<div
|
|
10599
10627
|
class="fixed z-[1000] max-w-[calc(100vw-1rem)] overflow-auto rounded-card border border-border bg-surface shadow-lg"
|
|
10600
10628
|
style="max-height: calc(100vh - 1rem)"
|
|
@@ -10611,7 +10639,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10611
10639
|
role="group"
|
|
10612
10640
|
[attr.aria-label]="presetsLabel()"
|
|
10613
10641
|
>
|
|
10614
|
-
<p
|
|
10642
|
+
<p
|
|
10643
|
+
class="px-2 pb-1.5 pt-1 text-[11px] font-semibold uppercase tracking-wide text-ink-faint"
|
|
10644
|
+
>
|
|
10615
10645
|
{{ presetsLabel() }}
|
|
10616
10646
|
</p>
|
|
10617
10647
|
<div class="flex flex-wrap gap-1 sm:flex-col sm:flex-nowrap">
|
|
@@ -10651,15 +10681,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10651
10681
|
>
|
|
10652
10682
|
<p class="flex items-stretch gap-2 text-sm" aria-live="polite">
|
|
10653
10683
|
<span class="flex flex-col leading-tight">
|
|
10654
|
-
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10655
|
-
|
|
10684
|
+
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10685
|
+
startLabel()
|
|
10686
|
+
}}</span>
|
|
10687
|
+
<span
|
|
10688
|
+
class="font-medium"
|
|
10689
|
+
[class.text-ink]="pendingStart()"
|
|
10690
|
+
[class.text-ink-faint]="!pendingStart()"
|
|
10691
|
+
>
|
|
10656
10692
|
{{ pendingStartText() }}
|
|
10657
10693
|
</span>
|
|
10658
10694
|
</span>
|
|
10659
10695
|
<span class="self-end text-ink-faint" aria-hidden="true">–</span>
|
|
10660
10696
|
<span class="flex flex-col leading-tight">
|
|
10661
|
-
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10662
|
-
|
|
10697
|
+
<span class="text-[11px] font-medium uppercase text-ink-faint">{{
|
|
10698
|
+
endLabel()
|
|
10699
|
+
}}</span>
|
|
10700
|
+
<span
|
|
10701
|
+
class="font-medium"
|
|
10702
|
+
[class.text-ink]="pendingEnd()"
|
|
10703
|
+
[class.text-ink-faint]="!pendingEnd()"
|
|
10704
|
+
>
|
|
10663
10705
|
{{ pendingEndText() }}
|
|
10664
10706
|
</span>
|
|
10665
10707
|
</span>
|
|
@@ -10682,112 +10724,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
10682
10724
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], maxDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDays", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], months: [{ type: i0.Input, args: [{ isSignal: true, alias: "months", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], panelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabel", required: false }] }], presetsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "presetsLabel", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], endLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "endLabel", required: false }] }], applyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], calendarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarLabel", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], presetSelect: [{ type: i0.Output, args: ["presetSelect"] }], calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdsRangeCalendar), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
|
|
10683
10725
|
let nextId$a = 0;
|
|
10684
10726
|
|
|
10685
|
-
/**
|
|
10686
|
-
* Where a floating panel goes, and the plumbing that keeps it there.
|
|
10687
|
-
*
|
|
10688
|
-
* <p><b>Why this exists.</b> Ten components in this library append a panel to `<body>` and position
|
|
10689
|
-
* it against their trigger, and nine of them had their own copy of the same twenty-odd lines. The
|
|
10690
|
-
* copies had already drifted: some clamp the panel inside the viewport vertically and some only
|
|
10691
|
-
* flip it, some match the trigger's width and some do not. Worse, the *lifecycle* was duplicated
|
|
10692
|
-
* too — and `sds-autocomplete` shipped a version that removed the panel through a `viewChild`
|
|
10693
|
-
* already cleared by `ngOnDestroy`, orphaning it in `<body>` for ever. That bug could only exist in
|
|
10694
|
-
* one copy of nine.
|
|
10695
|
-
*/
|
|
10696
|
-
/**
|
|
10697
|
-
* The arithmetic, with no DOM in it.
|
|
10698
|
-
*
|
|
10699
|
-
* <p>Vertical: below the trigger, flipped above when it does not fit below and fits better above —
|
|
10700
|
-
* then <b>clamped inside the viewport</b>. The clamp is not decoration: without it a panel taller
|
|
10701
|
-
* than the space either side is pushed off the top edge and its first rows become unreachable, and
|
|
10702
|
-
* four of the nine copies were missing it.
|
|
10703
|
-
*
|
|
10704
|
-
* <p>Horizontal: the panel's start (or end) edge meets the trigger's, then clamped the same way. A
|
|
10705
|
-
* panel wider than the viewport pins to the left margin rather than running off both sides.
|
|
10706
|
-
*/
|
|
10707
|
-
function placePanel(trigger, panel, viewport, options = {}) {
|
|
10708
|
-
const gap = options.gap ?? 4;
|
|
10709
|
-
const margin = options.margin ?? 8;
|
|
10710
|
-
const roomBelow = viewport.height - trigger.bottom - gap;
|
|
10711
|
-
const roomAbove = trigger.top - gap;
|
|
10712
|
-
const above = options.prefer === 'above'
|
|
10713
|
-
? roomAbove >= panel.height || roomAbove > roomBelow
|
|
10714
|
-
: roomBelow < panel.height && roomAbove > roomBelow;
|
|
10715
|
-
const top = clamp(above ? trigger.top - gap - panel.height : trigger.bottom + gap, margin, viewport.height - margin - panel.height);
|
|
10716
|
-
const left = clamp(options.align === 'end' ? trigger.right - panel.width : trigger.left, margin, viewport.width - margin - panel.width);
|
|
10717
|
-
return { top: Math.round(top), left: Math.round(left) };
|
|
10718
|
-
}
|
|
10719
|
-
/**
|
|
10720
|
-
* A viewport bound that may be smaller than the lower one — when the panel is taller than the
|
|
10721
|
-
* window there is no position that satisfies both, and the top edge is the one that must win, or
|
|
10722
|
-
* the panel starts off-screen and cannot be scrolled back.
|
|
10723
|
-
*/
|
|
10724
|
-
function clamp(value, low, high) {
|
|
10725
|
-
return Math.max(low, Math.min(value, Math.max(low, high)));
|
|
10726
|
-
}
|
|
10727
|
-
/**
|
|
10728
|
-
* The DOM half: append to `<body>`, place, follow scroll and resize, and — the part that was worth
|
|
10729
|
-
* centralising — take it all down again exactly once.
|
|
10730
|
-
*
|
|
10731
|
-
* <p>Hold one of these per component and call {@link detach} from `ngOnDestroy`. It keeps its own
|
|
10732
|
-
* reference to the panel element, so it does not depend on a `viewChild` that Angular has already
|
|
10733
|
-
* cleared by the time the component is torn down.
|
|
10734
|
-
*/
|
|
10735
|
-
class SdsOverlayPanel {
|
|
10736
|
-
trigger = null;
|
|
10737
|
-
panel = null;
|
|
10738
|
-
options = {};
|
|
10739
|
-
listening = false;
|
|
10740
|
-
reflow = () => this.reposition();
|
|
10741
|
-
/** Attach (or re-attach) the panel to the body and start following the trigger. */
|
|
10742
|
-
attach(trigger, panel, options = {}) {
|
|
10743
|
-
this.trigger = trigger;
|
|
10744
|
-
this.panel = panel;
|
|
10745
|
-
this.options = options;
|
|
10746
|
-
if (panel.parentElement !== document.body) {
|
|
10747
|
-
document.body.appendChild(panel);
|
|
10748
|
-
}
|
|
10749
|
-
this.reposition();
|
|
10750
|
-
if (!this.listening) {
|
|
10751
|
-
// `true` for scroll: a panel inside a scrolling container must follow it, and scroll does
|
|
10752
|
-
// not bubble.
|
|
10753
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
10754
|
-
window.addEventListener('resize', this.reflow);
|
|
10755
|
-
this.listening = true;
|
|
10756
|
-
}
|
|
10757
|
-
}
|
|
10758
|
-
reposition() {
|
|
10759
|
-
const trigger = this.trigger;
|
|
10760
|
-
const panel = this.panel;
|
|
10761
|
-
if (!trigger || !panel) {
|
|
10762
|
-
return;
|
|
10763
|
-
}
|
|
10764
|
-
const r = trigger.getBoundingClientRect();
|
|
10765
|
-
if (this.options.matchTriggerWidth) {
|
|
10766
|
-
panel.style.minWidth = `${Math.round(r.width)}px`;
|
|
10767
|
-
}
|
|
10768
|
-
const { top, left } = placePanel(r,
|
|
10769
|
-
// offset* is 0 before the panel has laid out; the fallbacks keep the first frame sane.
|
|
10770
|
-
{ width: panel.offsetWidth || r.width, height: panel.offsetHeight || 240 }, { width: window.innerWidth, height: window.innerHeight }, this.options);
|
|
10771
|
-
panel.style.top = `${top}px`;
|
|
10772
|
-
panel.style.left = `${left}px`;
|
|
10773
|
-
}
|
|
10774
|
-
/** Does the panel contain this node? For "did the click land outside" checks. */
|
|
10775
|
-
contains(node) {
|
|
10776
|
-
return !!node && !!this.panel?.contains(node);
|
|
10777
|
-
}
|
|
10778
|
-
/** Stop following, and remove the panel from the body. Safe to call more than once. */
|
|
10779
|
-
detach() {
|
|
10780
|
-
if (this.listening) {
|
|
10781
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
10782
|
-
window.removeEventListener('resize', this.reflow);
|
|
10783
|
-
this.listening = false;
|
|
10784
|
-
}
|
|
10785
|
-
this.panel?.remove();
|
|
10786
|
-
this.panel = null;
|
|
10787
|
-
this.trigger = null;
|
|
10788
|
-
}
|
|
10789
|
-
}
|
|
10790
|
-
|
|
10791
10727
|
/**
|
|
10792
10728
|
* Supplies a custom template for each option row (and the selected value shown
|
|
10793
10729
|
* in the trigger) of an `<sds-select>` / `<sds-multi-select>`. Use as a
|
|
@@ -11224,7 +11160,7 @@ class SdsSelect {
|
|
|
11224
11160
|
attachPanel() {
|
|
11225
11161
|
const trigger = this.trigger()?.nativeElement;
|
|
11226
11162
|
if (trigger && this.panelEl) {
|
|
11227
|
-
this.overlay.attach(trigger, this.panelEl, {
|
|
11163
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
11228
11164
|
}
|
|
11229
11165
|
}
|
|
11230
11166
|
onDocumentClick(event) {
|
|
@@ -12010,7 +11946,7 @@ class SdsMultiSelect {
|
|
|
12010
11946
|
attachPanel() {
|
|
12011
11947
|
const trigger = this.trigger()?.nativeElement;
|
|
12012
11948
|
if (trigger && this.panelEl) {
|
|
12013
|
-
this.overlay.attach(trigger, this.panelEl, {
|
|
11949
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
12014
11950
|
}
|
|
12015
11951
|
}
|
|
12016
11952
|
onDocumentClick(event) {
|
|
@@ -12813,7 +12749,7 @@ class SdsAutocomplete {
|
|
|
12813
12749
|
const trigger = this.triggerEl()?.nativeElement;
|
|
12814
12750
|
const panel = this.panelRef()?.nativeElement;
|
|
12815
12751
|
if (trigger && panel) {
|
|
12816
|
-
this.overlay.attach(trigger, panel, {
|
|
12752
|
+
this.overlay.attach(trigger, panel, { width: 'min' });
|
|
12817
12753
|
}
|
|
12818
12754
|
}
|
|
12819
12755
|
ngOnDestroy() {
|
|
@@ -13735,7 +13671,7 @@ class SdsPhoneInput {
|
|
|
13735
13671
|
attachPanel() {
|
|
13736
13672
|
const trigger = this.anchor()?.nativeElement;
|
|
13737
13673
|
if (trigger && this.panelEl) {
|
|
13738
|
-
this.overlay.attach(trigger, this.panelEl, {
|
|
13674
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
13739
13675
|
}
|
|
13740
13676
|
}
|
|
13741
13677
|
onDocumentClick(event) {
|
|
@@ -14272,7 +14208,8 @@ class SdsAddressAutocomplete {
|
|
|
14272
14208
|
seq = 0;
|
|
14273
14209
|
timer;
|
|
14274
14210
|
destroyed = false;
|
|
14275
|
-
|
|
14211
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
14212
|
+
overlay = new SdsOverlayPanel();
|
|
14276
14213
|
fieldId = computed(() => this.inputId() || this.autoId, ...(ngDevMode ? [{ debugName: "fieldId" }] : []));
|
|
14277
14214
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
14278
14215
|
pill = computed(() => this.shape() === 'pill', ...(ngDevMode ? [{ debugName: "pill" }] : []));
|
|
@@ -14535,79 +14472,57 @@ class SdsAddressAutocomplete {
|
|
|
14535
14472
|
}
|
|
14536
14473
|
scrollHighlightedIntoView() {
|
|
14537
14474
|
setTimeout(() => {
|
|
14538
|
-
const root = this.
|
|
14475
|
+
const root = this.overlay.element ?? this.host.nativeElement;
|
|
14539
14476
|
root
|
|
14540
14477
|
.querySelector(`[data-index="${this.highlighted()}"]`)
|
|
14541
14478
|
?.scrollIntoView?.({ block: 'nearest' });
|
|
14542
14479
|
});
|
|
14543
14480
|
}
|
|
14544
14481
|
// --- overlay ----------------------------------------------------------------
|
|
14545
|
-
/**
|
|
14546
|
-
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
reflow = () => this.positionPanel();
|
|
14552
|
-
listening = false;
|
|
14482
|
+
/**
|
|
14483
|
+
* Keep the body-appended panel in step with whether it should be shown at all.
|
|
14484
|
+
*
|
|
14485
|
+
* <p>Driven from `afterEveryRender`, so it runs on every change and must be cheap and idempotent
|
|
14486
|
+
* — attaching once, then repositioning while it stays open.
|
|
14487
|
+
*/
|
|
14553
14488
|
syncPanel() {
|
|
14554
14489
|
if (this.inline() || !this.panelShown()) {
|
|
14555
|
-
|
|
14556
|
-
this.detachPanel();
|
|
14490
|
+
this.overlay.detach();
|
|
14557
14491
|
return;
|
|
14558
14492
|
}
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
if (!this.listening) {
|
|
14565
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
14566
|
-
window.addEventListener('resize', this.reflow);
|
|
14567
|
-
this.listening = true;
|
|
14568
|
-
}
|
|
14569
|
-
}
|
|
14570
|
-
this.positionPanel();
|
|
14571
|
-
}
|
|
14572
|
-
detachPanel() {
|
|
14573
|
-
if (this.listening) {
|
|
14574
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
14575
|
-
window.removeEventListener('resize', this.reflow);
|
|
14576
|
-
this.listening = false;
|
|
14577
|
-
}
|
|
14578
|
-
this.panelEl?.remove();
|
|
14579
|
-
this.panelEl = null;
|
|
14580
|
-
}
|
|
14581
|
-
positionPanel() {
|
|
14582
|
-
const fieldEl = this.fieldRef()?.nativeElement;
|
|
14583
|
-
const panelEl = this.panelEl;
|
|
14584
|
-
if (!fieldEl || !panelEl)
|
|
14493
|
+
const field = this.fieldRef()?.nativeElement;
|
|
14494
|
+
if (!field)
|
|
14495
|
+
return;
|
|
14496
|
+
if (this.overlay.element) {
|
|
14497
|
+
this.overlay.reposition();
|
|
14585
14498
|
return;
|
|
14586
|
-
const r = fieldEl.getBoundingClientRect();
|
|
14587
|
-
const gap = 4;
|
|
14588
|
-
const margin = 8;
|
|
14589
|
-
panelEl.style.width = `${Math.round(r.width)}px`;
|
|
14590
|
-
const ph = panelEl.offsetHeight || 240;
|
|
14591
|
-
let top = r.bottom + gap;
|
|
14592
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
14593
|
-
top = r.top - gap - ph;
|
|
14594
14499
|
}
|
|
14595
|
-
|
|
14596
|
-
|
|
14500
|
+
const panel = this.host.nativeElement.querySelector('[data-sds-address-panel]');
|
|
14501
|
+
if (!panel)
|
|
14502
|
+
return;
|
|
14503
|
+
// `exact`, not `min`: a suggestion list wider than the input it hangs off stops reading as
|
|
14504
|
+
// part of the field.
|
|
14505
|
+
this.overlay.attach(field, panel, { width: 'exact' });
|
|
14506
|
+
}
|
|
14507
|
+
/** Close the overlay (a no-op `inline`, where the rows are always shown). */
|
|
14508
|
+
close() {
|
|
14509
|
+
this.open.set(false);
|
|
14510
|
+
this.highlighted.set(-1);
|
|
14511
|
+
this.overlay.detach();
|
|
14597
14512
|
}
|
|
14598
14513
|
onDocumentClick(event) {
|
|
14599
14514
|
if (this.inline() || !this.open())
|
|
14600
14515
|
return;
|
|
14601
14516
|
const target = event.target;
|
|
14602
14517
|
const inField = this.host.nativeElement.contains(target);
|
|
14603
|
-
const inPanel =
|
|
14518
|
+
const inPanel = this.overlay.contains(target);
|
|
14604
14519
|
if (!inField && !inPanel)
|
|
14605
14520
|
this.close();
|
|
14606
14521
|
}
|
|
14607
14522
|
ngOnDestroy() {
|
|
14608
14523
|
this.destroyed = true;
|
|
14609
14524
|
clearTimeout(this.timer);
|
|
14610
|
-
this.
|
|
14525
|
+
this.overlay.detach();
|
|
14611
14526
|
}
|
|
14612
14527
|
// --- ControlValueAccessor -----------------------------------------------------
|
|
14613
14528
|
onChange = () => { };
|
|
@@ -14714,7 +14629,7 @@ class SdsAddressAutocomplete {
|
|
|
14714
14629
|
<ng-container [ngTemplateOutlet]="panelBody" />
|
|
14715
14630
|
</div>
|
|
14716
14631
|
} @else if (panelShown()) {
|
|
14717
|
-
<!-- Appended to document.body on open (see
|
|
14632
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel). -->
|
|
14718
14633
|
<div
|
|
14719
14634
|
data-sds-address-panel
|
|
14720
14635
|
class="fixed z-[1000] max-h-80 overflow-auto rounded-card border border-border bg-surface p-1 shadow-lg"
|
|
@@ -14939,7 +14854,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14939
14854
|
<ng-container [ngTemplateOutlet]="panelBody" />
|
|
14940
14855
|
</div>
|
|
14941
14856
|
} @else if (panelShown()) {
|
|
14942
|
-
<!-- Appended to document.body on open (see
|
|
14857
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel). -->
|
|
14943
14858
|
<div
|
|
14944
14859
|
data-sds-address-panel
|
|
14945
14860
|
class="fixed z-[1000] max-h-80 overflow-auto rounded-card border border-border bg-surface p-1 shadow-lg"
|
|
@@ -21309,7 +21224,7 @@ let nextId = 0;
|
|
|
21309
21224
|
* disclosure mechanics (closes on outside click, on Escape, and on selection, restoring focus to the trigger),
|
|
21310
21225
|
* and the same panel styling and open animation — so the three top-bar controls read as one set.
|
|
21311
21226
|
*
|
|
21312
|
-
* The panel is appended to `document.body` while open and
|
|
21227
|
+
* The panel is appended to `document.body` while open and placed by `SdsOverlayPanel` (like sds-select),
|
|
21313
21228
|
* so a clipping ancestor — a bar with `overflow:hidden`, or a `glass` bar whose `backdrop-filter` is a
|
|
21314
21229
|
* containing block — can never cut it off or paint over it. It repositions on scroll/resize and is removed
|
|
21315
21230
|
* on close. Accessibility follows the APG select-only combobox: focus stays on the trigger, and Arrow/Home/
|
|
@@ -21350,7 +21265,8 @@ class SdsLanguageSwitcher {
|
|
|
21350
21265
|
openChange = output();
|
|
21351
21266
|
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
21352
21267
|
highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
21353
|
-
|
|
21268
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
21269
|
+
overlay = new SdsOverlayPanel();
|
|
21354
21270
|
/** Normalise the mixed `languages` input to `{ code, label }`, defaulting the label to the code. */
|
|
21355
21271
|
options = computed(() => this.languages().map((l) => typeof l === 'string'
|
|
21356
21272
|
? { code: l, label: l.toUpperCase() }
|
|
@@ -21377,9 +21293,17 @@ class SdsLanguageSwitcher {
|
|
|
21377
21293
|
// clipping/stacking ancestor (an overflow:hidden bar, a backdrop-filter glass bar) can cut it off.
|
|
21378
21294
|
// Focus stays on the trigger (combobox model); the active row is surfaced via aria-activedescendant.
|
|
21379
21295
|
afterNextRender(() => {
|
|
21380
|
-
|
|
21381
|
-
this.
|
|
21382
|
-
|
|
21296
|
+
const panel = this.el.nativeElement.querySelector('[data-sds-language-panel]');
|
|
21297
|
+
const trigger = this.trigger()?.nativeElement;
|
|
21298
|
+
if (panel && trigger) {
|
|
21299
|
+
// gap 8 and an end-aligned panel, as this control has always used; `direction="up"`
|
|
21300
|
+
// becomes the shared `prefer: 'above'`.
|
|
21301
|
+
this.overlay.attach(trigger, panel, {
|
|
21302
|
+
gap: 8,
|
|
21303
|
+
align: this.align(),
|
|
21304
|
+
prefer: this.direction() === 'up' ? 'above' : 'below',
|
|
21305
|
+
});
|
|
21306
|
+
}
|
|
21383
21307
|
this.trigger()?.nativeElement.focus();
|
|
21384
21308
|
this.scrollHighlightedIntoView();
|
|
21385
21309
|
}, { injector: this.injector });
|
|
@@ -21387,10 +21311,8 @@ class SdsLanguageSwitcher {
|
|
|
21387
21311
|
close(returnFocus = false) {
|
|
21388
21312
|
if (!this.open())
|
|
21389
21313
|
return;
|
|
21390
|
-
this.removeReflowListeners();
|
|
21391
21314
|
// Remove the portalled node ourselves before Angular tears the view down (it lives in <body> now).
|
|
21392
|
-
this.
|
|
21393
|
-
this.panelEl = null;
|
|
21315
|
+
this.overlay.detach();
|
|
21394
21316
|
this.open.set(false);
|
|
21395
21317
|
this.openChange.emit(false);
|
|
21396
21318
|
if (returnFocus)
|
|
@@ -21406,7 +21328,7 @@ class SdsLanguageSwitcher {
|
|
|
21406
21328
|
return;
|
|
21407
21329
|
const target = event.target;
|
|
21408
21330
|
const inHost = this.el.nativeElement.contains(target);
|
|
21409
|
-
const inPanel =
|
|
21331
|
+
const inPanel = this.overlay.contains(target);
|
|
21410
21332
|
if (!inHost && !inPanel)
|
|
21411
21333
|
this.close();
|
|
21412
21334
|
}
|
|
@@ -21453,7 +21375,8 @@ class SdsLanguageSwitcher {
|
|
|
21453
21375
|
case 'Tab':
|
|
21454
21376
|
this.close(false);
|
|
21455
21377
|
break;
|
|
21456
|
-
default:
|
|
21378
|
+
default:
|
|
21379
|
+
break;
|
|
21457
21380
|
}
|
|
21458
21381
|
}
|
|
21459
21382
|
initHighlight() {
|
|
@@ -21480,68 +21403,19 @@ class SdsLanguageSwitcher {
|
|
|
21480
21403
|
}
|
|
21481
21404
|
scrollHighlightedIntoView() {
|
|
21482
21405
|
setTimeout(() => {
|
|
21483
|
-
this.
|
|
21406
|
+
this.overlay.element
|
|
21484
21407
|
?.querySelector(`[data-index="${this.highlightedIndex()}"]`)
|
|
21485
21408
|
?.scrollIntoView({ block: 'nearest' });
|
|
21486
21409
|
});
|
|
21487
21410
|
}
|
|
21488
|
-
// --- body-appended overlay positioning (mirrors sds-select / sds-date-picker) ---
|
|
21489
|
-
reflow = () => this.positionPanel();
|
|
21490
|
-
listening = false;
|
|
21491
|
-
addReflowListeners() {
|
|
21492
|
-
if (this.listening)
|
|
21493
|
-
return;
|
|
21494
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
21495
|
-
window.addEventListener('resize', this.reflow);
|
|
21496
|
-
this.listening = true;
|
|
21497
|
-
}
|
|
21498
|
-
removeReflowListeners() {
|
|
21499
|
-
if (!this.listening)
|
|
21500
|
-
return;
|
|
21501
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
21502
|
-
window.removeEventListener('resize', this.reflow);
|
|
21503
|
-
this.listening = false;
|
|
21504
|
-
}
|
|
21505
|
-
positionPanel() {
|
|
21506
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
21507
|
-
const panelEl = this.panelEl;
|
|
21508
|
-
if (!triggerEl || !panelEl)
|
|
21509
|
-
return;
|
|
21510
|
-
if (panelEl.parentElement !== document.body)
|
|
21511
|
-
document.body.appendChild(panelEl);
|
|
21512
|
-
const r = triggerEl.getBoundingClientRect();
|
|
21513
|
-
const gap = 8;
|
|
21514
|
-
const margin = 8;
|
|
21515
|
-
const pw = panelEl.offsetWidth || 176;
|
|
21516
|
-
const ph = panelEl.offsetHeight || 200;
|
|
21517
|
-
// Vertical: below by default; open above when asked (direction='up') and there is room, or when there
|
|
21518
|
-
// isn't room below but there is above. The panel height is bounded by the list's max-h, but clamp
|
|
21519
|
-
// defensively so top can never be pushed off the top edge.
|
|
21520
|
-
const roomBelow = window.innerHeight - r.bottom - gap;
|
|
21521
|
-
const roomAbove = r.top - gap;
|
|
21522
|
-
let top = (this.direction() === 'up' && roomAbove >= ph) || (roomBelow < ph && roomAbove > roomBelow)
|
|
21523
|
-
? r.top - gap - ph
|
|
21524
|
-
: r.bottom + gap;
|
|
21525
|
-
if (top + ph > window.innerHeight - margin)
|
|
21526
|
-
top = window.innerHeight - margin - ph;
|
|
21527
|
-
if (top < margin)
|
|
21528
|
-
top = margin;
|
|
21529
|
-
// Horizontal: align the panel's end (right) or start (left) edge to the trigger, then clamp.
|
|
21530
|
-
let left = this.align() === 'end' ? r.right - pw : r.left;
|
|
21531
|
-
left = Math.min(left, window.innerWidth - margin - pw);
|
|
21532
|
-
left = Math.max(margin, left);
|
|
21533
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
21534
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
21535
|
-
}
|
|
21536
21411
|
ngOnDestroy() {
|
|
21537
|
-
this.
|
|
21538
|
-
this.panelEl?.remove();
|
|
21412
|
+
this.overlay.detach();
|
|
21539
21413
|
}
|
|
21540
21414
|
onBrand = computed(() => this.appearance() === 'on-brand', ...(ngDevMode ? [{ debugName: "onBrand" }] : []));
|
|
21541
21415
|
triggerClass = computed(() => this.onBrand()
|
|
21542
21416
|
? 'text-white/85 hover:bg-white/10 hover:text-white focus-visible:ring-white/40'
|
|
21543
21417
|
: 'bg-muted text-ink-soft hover:bg-brand-50 hover:text-brand-600 focus-visible:ring-brand-500/40', ...(ngDevMode ? [{ debugName: "triggerClass" }] : []));
|
|
21544
|
-
chevronClass = computed(() =>
|
|
21418
|
+
chevronClass = computed(() => this.onBrand() ? 'text-white/70' : 'text-ink-faint', ...(ngDevMode ? [{ debugName: "chevronClass" }] : []));
|
|
21545
21419
|
rowClass(index, selected) {
|
|
21546
21420
|
const base = 'flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-sm font-medium transition-colors';
|
|
21547
21421
|
const highlighted = this.highlightedIndex() === index;
|
|
@@ -21582,7 +21456,7 @@ class SdsLanguageSwitcher {
|
|
|
21582
21456
|
</button>
|
|
21583
21457
|
|
|
21584
21458
|
@if (open()) {
|
|
21585
|
-
<!-- Appended to document.body on open (see
|
|
21459
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel) so no ancestor can clip it. -->
|
|
21586
21460
|
<div
|
|
21587
21461
|
data-sds-language-panel
|
|
21588
21462
|
[id]="panelId"
|
|
@@ -21591,7 +21465,9 @@ class SdsLanguageSwitcher {
|
|
|
21591
21465
|
class="fixed z-[1000] min-w-44 overflow-hidden rounded-card border border-border bg-surface shadow-xl animate-sds-menu"
|
|
21592
21466
|
>
|
|
21593
21467
|
@if (heading(); as h) {
|
|
21594
|
-
<p
|
|
21468
|
+
<p
|
|
21469
|
+
class="border-b border-border px-4 pb-2 pt-3 text-[0.68rem] font-semibold uppercase tracking-wider text-ink-faint"
|
|
21470
|
+
>
|
|
21595
21471
|
{{ h }}
|
|
21596
21472
|
</p>
|
|
21597
21473
|
}
|
|
@@ -21657,7 +21533,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
21657
21533
|
</button>
|
|
21658
21534
|
|
|
21659
21535
|
@if (open()) {
|
|
21660
|
-
<!-- Appended to document.body on open (see
|
|
21536
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel) so no ancestor can clip it. -->
|
|
21661
21537
|
<div
|
|
21662
21538
|
data-sds-language-panel
|
|
21663
21539
|
[id]="panelId"
|
|
@@ -21666,7 +21542,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
21666
21542
|
class="fixed z-[1000] min-w-44 overflow-hidden rounded-card border border-border bg-surface shadow-xl animate-sds-menu"
|
|
21667
21543
|
>
|
|
21668
21544
|
@if (heading(); as h) {
|
|
21669
|
-
<p
|
|
21545
|
+
<p
|
|
21546
|
+
class="border-b border-border px-4 pb-2 pt-3 text-[0.68rem] font-semibold uppercase tracking-wider text-ink-faint"
|
|
21547
|
+
>
|
|
21670
21548
|
{{ h }}
|
|
21671
21549
|
</p>
|
|
21672
21550
|
}
|