@besaitech/ng-design-system 0.0.80 → 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.
- package/fesm2022/besaitech-ng-design-system.mjs +666 -586
- package/fesm2022/besaitech-ng-design-system.mjs.map +1 -1
- package/index.d.ts +127 -64
- package/package.json +1 -1
|
@@ -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>
|
|
@@ -10838,6 +10880,8 @@ class SdsSelect {
|
|
|
10838
10880
|
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
10839
10881
|
trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
|
|
10840
10882
|
panelEl = null;
|
|
10883
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
10884
|
+
overlay = new SdsOverlayPanel();
|
|
10841
10885
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
10842
10886
|
filled = computed(() => this.appearance() === 'filled', ...(ngDevMode ? [{ debugName: "filled" }] : []));
|
|
10843
10887
|
onBrand = computed(() => this.appearance() === 'on-brand', ...(ngDevMode ? [{ debugName: "onBrand" }] : []));
|
|
@@ -10882,9 +10926,7 @@ class SdsSelect {
|
|
|
10882
10926
|
? 'border-danger focus:border-danger focus:ring-2 focus:ring-danger/25'
|
|
10883
10927
|
: 'border-border focus:border-brand-500 focus:ring-2 focus:ring-brand-500/25');
|
|
10884
10928
|
}
|
|
10885
|
-
parts.push(this.isDisabled()
|
|
10886
|
-
? 'opacity-60 cursor-not-allowed pointer-events-none'
|
|
10887
|
-
: 'cursor-pointer');
|
|
10929
|
+
parts.push(this.isDisabled() ? 'opacity-60 cursor-not-allowed pointer-events-none' : 'cursor-pointer');
|
|
10888
10930
|
return parts.join(' ');
|
|
10889
10931
|
}, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
|
|
10890
10932
|
// --- option normalization & equality ---
|
|
@@ -10918,7 +10960,11 @@ class SdsSelect {
|
|
|
10918
10960
|
}, ...(ngDevMode ? [{ debugName: "triggerContext" }] : []));
|
|
10919
10961
|
/** Context for a projected footer template. */
|
|
10920
10962
|
footerContext() {
|
|
10921
|
-
return {
|
|
10963
|
+
return {
|
|
10964
|
+
$implicit: this.filterText(),
|
|
10965
|
+
filterText: this.filterText(),
|
|
10966
|
+
close: () => this.close(true),
|
|
10967
|
+
};
|
|
10922
10968
|
}
|
|
10923
10969
|
equals(a, b) {
|
|
10924
10970
|
const k = this.dataKey();
|
|
@@ -10958,9 +11004,9 @@ class SdsSelect {
|
|
|
10958
11004
|
this.onTouched();
|
|
10959
11005
|
this.initHighlight();
|
|
10960
11006
|
afterNextRender(() => {
|
|
10961
|
-
this.panelEl =
|
|
10962
|
-
|
|
10963
|
-
this.
|
|
11007
|
+
this.panelEl =
|
|
11008
|
+
this.host.nativeElement.querySelector('[data-sds-select-panel]');
|
|
11009
|
+
this.attachPanel();
|
|
10964
11010
|
if (this.filter()) {
|
|
10965
11011
|
this.panelEl?.querySelector('input')?.focus();
|
|
10966
11012
|
}
|
|
@@ -10973,8 +11019,7 @@ class SdsSelect {
|
|
|
10973
11019
|
close(returnFocus = false) {
|
|
10974
11020
|
if (!this.open())
|
|
10975
11021
|
return;
|
|
10976
|
-
this.
|
|
10977
|
-
this.panelEl?.remove();
|
|
11022
|
+
this.overlay.detach();
|
|
10978
11023
|
this.panelEl = null;
|
|
10979
11024
|
this.open.set(false);
|
|
10980
11025
|
if (returnFocus)
|
|
@@ -10991,7 +11036,7 @@ class SdsSelect {
|
|
|
10991
11036
|
onFilter(event) {
|
|
10992
11037
|
this.filterText.set(event.target.value);
|
|
10993
11038
|
this.highlightFirst();
|
|
10994
|
-
setTimeout(() => this.
|
|
11039
|
+
setTimeout(() => this.overlay.reposition());
|
|
10995
11040
|
}
|
|
10996
11041
|
// --- keyboard ---
|
|
10997
11042
|
onTriggerKeydown(event) {
|
|
@@ -11047,7 +11092,8 @@ class SdsSelect {
|
|
|
11047
11092
|
case 'Tab':
|
|
11048
11093
|
this.close(false);
|
|
11049
11094
|
return true;
|
|
11050
|
-
default:
|
|
11095
|
+
default:
|
|
11096
|
+
return false;
|
|
11051
11097
|
}
|
|
11052
11098
|
}
|
|
11053
11099
|
initHighlight() {
|
|
@@ -11110,46 +11156,12 @@ class SdsSelect {
|
|
|
11110
11156
|
el?.scrollIntoView({ block: 'nearest' });
|
|
11111
11157
|
});
|
|
11112
11158
|
}
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
return;
|
|
11119
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
11120
|
-
window.addEventListener('resize', this.reflow);
|
|
11121
|
-
this.listening = true;
|
|
11122
|
-
}
|
|
11123
|
-
removeReflowListeners() {
|
|
11124
|
-
if (!this.listening)
|
|
11125
|
-
return;
|
|
11126
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
11127
|
-
window.removeEventListener('resize', this.reflow);
|
|
11128
|
-
this.listening = false;
|
|
11129
|
-
}
|
|
11130
|
-
positionPanel() {
|
|
11131
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
11132
|
-
const panelEl = this.panelEl;
|
|
11133
|
-
if (!triggerEl || !panelEl)
|
|
11134
|
-
return;
|
|
11135
|
-
if (panelEl.parentElement !== document.body)
|
|
11136
|
-
document.body.appendChild(panelEl);
|
|
11137
|
-
const r = triggerEl.getBoundingClientRect();
|
|
11138
|
-
panelEl.style.minWidth = `${Math.round(r.width)}px`;
|
|
11139
|
-
const ph = panelEl.offsetHeight || 240;
|
|
11140
|
-
const gap = 4;
|
|
11141
|
-
const margin = 8;
|
|
11142
|
-
let top = r.bottom + gap;
|
|
11143
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
11144
|
-
top = r.top - gap - ph;
|
|
11145
|
-
}
|
|
11146
|
-
let left = r.left;
|
|
11147
|
-
const pw = panelEl.offsetWidth || r.width;
|
|
11148
|
-
if (left + pw > window.innerWidth - margin) {
|
|
11149
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
11159
|
+
/** Attach once the panel exists — it is rendered inside an `@if`. */
|
|
11160
|
+
attachPanel() {
|
|
11161
|
+
const trigger = this.trigger()?.nativeElement;
|
|
11162
|
+
if (trigger && this.panelEl) {
|
|
11163
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
11150
11164
|
}
|
|
11151
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
11152
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
11153
11165
|
}
|
|
11154
11166
|
onDocumentClick(event) {
|
|
11155
11167
|
if (!this.open())
|
|
@@ -11161,9 +11173,8 @@ class SdsSelect {
|
|
|
11161
11173
|
this.close();
|
|
11162
11174
|
}
|
|
11163
11175
|
ngOnDestroy() {
|
|
11164
|
-
this.removeReflowListeners();
|
|
11165
11176
|
clearTimeout(this.typeTimer);
|
|
11166
|
-
this.
|
|
11177
|
+
this.overlay.detach();
|
|
11167
11178
|
}
|
|
11168
11179
|
setValue(v) {
|
|
11169
11180
|
this.value.set(v);
|
|
@@ -11224,10 +11235,7 @@ class SdsSelect {
|
|
|
11224
11235
|
/>
|
|
11225
11236
|
</span>
|
|
11226
11237
|
} @else {
|
|
11227
|
-
<span
|
|
11228
|
-
class="flex-1 truncate pl-3.5"
|
|
11229
|
-
[class]="valueTextClass()"
|
|
11230
|
-
>
|
|
11238
|
+
<span class="flex-1 truncate pl-3.5" [class]="valueTextClass()">
|
|
11231
11239
|
{{ selectedOption() ? selectedOption()!.label : placeholder() }}
|
|
11232
11240
|
</span>
|
|
11233
11241
|
}
|
|
@@ -11238,7 +11246,11 @@ class SdsSelect {
|
|
|
11238
11246
|
tabindex="-1"
|
|
11239
11247
|
[attr.aria-label]="clearLabel()"
|
|
11240
11248
|
class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
|
|
11241
|
-
[class]="
|
|
11249
|
+
[class]="
|
|
11250
|
+
onBrand()
|
|
11251
|
+
? 'text-white/60 hover:bg-white/10 hover:text-white'
|
|
11252
|
+
: 'text-ink-faint hover:bg-black/5 hover:text-ink-soft'
|
|
11253
|
+
"
|
|
11242
11254
|
(click)="clear($event)"
|
|
11243
11255
|
>
|
|
11244
11256
|
<sds-icon name="x" [size]="15" />
|
|
@@ -11257,11 +11269,20 @@ class SdsSelect {
|
|
|
11257
11269
|
|
|
11258
11270
|
@if (open()) {
|
|
11259
11271
|
<!-- Appended to document.body on open (see positionPanel). -->
|
|
11260
|
-
<div
|
|
11272
|
+
<div
|
|
11273
|
+
data-sds-select-panel
|
|
11274
|
+
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
11275
|
+
>
|
|
11261
11276
|
@if (filter()) {
|
|
11262
11277
|
<div class="border-b border-border p-2">
|
|
11263
|
-
<div
|
|
11264
|
-
|
|
11278
|
+
<div
|
|
11279
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
11280
|
+
>
|
|
11281
|
+
<sds-icon
|
|
11282
|
+
name="search"
|
|
11283
|
+
[size]="16"
|
|
11284
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
11285
|
+
/>
|
|
11265
11286
|
<input
|
|
11266
11287
|
type="text"
|
|
11267
11288
|
role="combobox"
|
|
@@ -11310,7 +11331,10 @@ class SdsSelect {
|
|
|
11310
11331
|
<li class="px-3 py-2 text-sm text-ink-faint">
|
|
11311
11332
|
<ng-container
|
|
11312
11333
|
[ngTemplateOutlet]="emptyTpl()!.template"
|
|
11313
|
-
[ngTemplateOutletContext]="{
|
|
11334
|
+
[ngTemplateOutletContext]="{
|
|
11335
|
+
$implicit: filterText(),
|
|
11336
|
+
filterText: filterText(),
|
|
11337
|
+
}"
|
|
11314
11338
|
/>
|
|
11315
11339
|
</li>
|
|
11316
11340
|
} @else {
|
|
@@ -11386,10 +11410,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
11386
11410
|
/>
|
|
11387
11411
|
</span>
|
|
11388
11412
|
} @else {
|
|
11389
|
-
<span
|
|
11390
|
-
class="flex-1 truncate pl-3.5"
|
|
11391
|
-
[class]="valueTextClass()"
|
|
11392
|
-
>
|
|
11413
|
+
<span class="flex-1 truncate pl-3.5" [class]="valueTextClass()">
|
|
11393
11414
|
{{ selectedOption() ? selectedOption()!.label : placeholder() }}
|
|
11394
11415
|
</span>
|
|
11395
11416
|
}
|
|
@@ -11400,7 +11421,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
11400
11421
|
tabindex="-1"
|
|
11401
11422
|
[attr.aria-label]="clearLabel()"
|
|
11402
11423
|
class="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
|
|
11403
|
-
[class]="
|
|
11424
|
+
[class]="
|
|
11425
|
+
onBrand()
|
|
11426
|
+
? 'text-white/60 hover:bg-white/10 hover:text-white'
|
|
11427
|
+
: 'text-ink-faint hover:bg-black/5 hover:text-ink-soft'
|
|
11428
|
+
"
|
|
11404
11429
|
(click)="clear($event)"
|
|
11405
11430
|
>
|
|
11406
11431
|
<sds-icon name="x" [size]="15" />
|
|
@@ -11419,11 +11444,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
11419
11444
|
|
|
11420
11445
|
@if (open()) {
|
|
11421
11446
|
<!-- Appended to document.body on open (see positionPanel). -->
|
|
11422
|
-
<div
|
|
11447
|
+
<div
|
|
11448
|
+
data-sds-select-panel
|
|
11449
|
+
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
11450
|
+
>
|
|
11423
11451
|
@if (filter()) {
|
|
11424
11452
|
<div class="border-b border-border p-2">
|
|
11425
|
-
<div
|
|
11426
|
-
|
|
11453
|
+
<div
|
|
11454
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
11455
|
+
>
|
|
11456
|
+
<sds-icon
|
|
11457
|
+
name="search"
|
|
11458
|
+
[size]="16"
|
|
11459
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
11460
|
+
/>
|
|
11427
11461
|
<input
|
|
11428
11462
|
type="text"
|
|
11429
11463
|
role="combobox"
|
|
@@ -11472,7 +11506,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
11472
11506
|
<li class="px-3 py-2 text-sm text-ink-faint">
|
|
11473
11507
|
<ng-container
|
|
11474
11508
|
[ngTemplateOutlet]="emptyTpl()!.template"
|
|
11475
|
-
[ngTemplateOutletContext]="{
|
|
11509
|
+
[ngTemplateOutletContext]="{
|
|
11510
|
+
$implicit: filterText(),
|
|
11511
|
+
filterText: filterText(),
|
|
11512
|
+
}"
|
|
11476
11513
|
/>
|
|
11477
11514
|
</li>
|
|
11478
11515
|
} @else {
|
|
@@ -11596,6 +11633,8 @@ class SdsMultiSelect {
|
|
|
11596
11633
|
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
11597
11634
|
trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
|
|
11598
11635
|
panelEl = null;
|
|
11636
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
11637
|
+
overlay = new SdsOverlayPanel();
|
|
11599
11638
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
11600
11639
|
normalizedOptions = computed(() => this.options().map((o) => this.norm(o)), ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : []));
|
|
11601
11640
|
filteredOptions = computed(() => {
|
|
@@ -11641,9 +11680,7 @@ class SdsMultiSelect {
|
|
|
11641
11680
|
parts.push(this.error()
|
|
11642
11681
|
? 'border-danger focus:border-danger focus:ring-2 focus:ring-danger/25'
|
|
11643
11682
|
: 'border-border focus:border-brand-500 focus:ring-2 focus:ring-brand-500/25');
|
|
11644
|
-
parts.push(this.isDisabled()
|
|
11645
|
-
? 'opacity-60 cursor-not-allowed pointer-events-none'
|
|
11646
|
-
: 'cursor-pointer');
|
|
11683
|
+
parts.push(this.isDisabled() ? 'opacity-60 cursor-not-allowed pointer-events-none' : 'cursor-pointer');
|
|
11647
11684
|
return parts.join(' ');
|
|
11648
11685
|
}, ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
|
|
11649
11686
|
// --- normalization & equality ---
|
|
@@ -11672,7 +11709,11 @@ class SdsMultiSelect {
|
|
|
11672
11709
|
}
|
|
11673
11710
|
/** Context for a projected footer template. */
|
|
11674
11711
|
footerContext() {
|
|
11675
|
-
return {
|
|
11712
|
+
return {
|
|
11713
|
+
$implicit: this.filterText(),
|
|
11714
|
+
filterText: this.filterText(),
|
|
11715
|
+
close: () => this.close(true),
|
|
11716
|
+
};
|
|
11676
11717
|
}
|
|
11677
11718
|
equals(a, b) {
|
|
11678
11719
|
const k = this.dataKey();
|
|
@@ -11718,9 +11759,9 @@ class SdsMultiSelect {
|
|
|
11718
11759
|
this.onTouched();
|
|
11719
11760
|
this.initHighlight();
|
|
11720
11761
|
afterNextRender(() => {
|
|
11721
|
-
this.panelEl =
|
|
11722
|
-
|
|
11723
|
-
this.
|
|
11762
|
+
this.panelEl =
|
|
11763
|
+
this.host.nativeElement.querySelector('[data-sds-select-panel]');
|
|
11764
|
+
this.attachPanel();
|
|
11724
11765
|
if (this.filter()) {
|
|
11725
11766
|
this.panelEl?.querySelector('input')?.focus();
|
|
11726
11767
|
}
|
|
@@ -11733,8 +11774,7 @@ class SdsMultiSelect {
|
|
|
11733
11774
|
close(returnFocus = false) {
|
|
11734
11775
|
if (!this.open())
|
|
11735
11776
|
return;
|
|
11736
|
-
this.
|
|
11737
|
-
this.panelEl?.remove();
|
|
11777
|
+
this.overlay.detach();
|
|
11738
11778
|
this.panelEl = null;
|
|
11739
11779
|
this.open.set(false);
|
|
11740
11780
|
if (returnFocus)
|
|
@@ -11752,9 +11792,7 @@ class SdsMultiSelect {
|
|
|
11752
11792
|
toggleOption(opt) {
|
|
11753
11793
|
const cur = this.value();
|
|
11754
11794
|
const exists = cur.some((x) => this.equals(x, opt.value));
|
|
11755
|
-
const next = exists
|
|
11756
|
-
? cur.filter((x) => !this.equals(x, opt.value))
|
|
11757
|
-
: [...cur, opt.value];
|
|
11795
|
+
const next = exists ? cur.filter((x) => !this.equals(x, opt.value)) : [...cur, opt.value];
|
|
11758
11796
|
this.setValue(next);
|
|
11759
11797
|
}
|
|
11760
11798
|
toggleAll() {
|
|
@@ -11770,7 +11808,7 @@ class SdsMultiSelect {
|
|
|
11770
11808
|
onFilter(event) {
|
|
11771
11809
|
this.filterText.set(event.target.value);
|
|
11772
11810
|
this.highlightFirst();
|
|
11773
|
-
setTimeout(() => this.
|
|
11811
|
+
setTimeout(() => this.overlay.reposition());
|
|
11774
11812
|
}
|
|
11775
11813
|
// --- keyboard ---
|
|
11776
11814
|
onTriggerKeydown(event) {
|
|
@@ -11835,7 +11873,8 @@ class SdsMultiSelect {
|
|
|
11835
11873
|
case 'Tab':
|
|
11836
11874
|
this.close(false);
|
|
11837
11875
|
return true;
|
|
11838
|
-
default:
|
|
11876
|
+
default:
|
|
11877
|
+
return false;
|
|
11839
11878
|
}
|
|
11840
11879
|
}
|
|
11841
11880
|
initHighlight() {
|
|
@@ -11903,46 +11942,12 @@ class SdsMultiSelect {
|
|
|
11903
11942
|
el?.scrollIntoView({ block: 'nearest' });
|
|
11904
11943
|
});
|
|
11905
11944
|
}
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
return;
|
|
11912
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
11913
|
-
window.addEventListener('resize', this.reflow);
|
|
11914
|
-
this.listening = true;
|
|
11915
|
-
}
|
|
11916
|
-
removeReflowListeners() {
|
|
11917
|
-
if (!this.listening)
|
|
11918
|
-
return;
|
|
11919
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
11920
|
-
window.removeEventListener('resize', this.reflow);
|
|
11921
|
-
this.listening = false;
|
|
11922
|
-
}
|
|
11923
|
-
positionPanel() {
|
|
11924
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
11925
|
-
const panelEl = this.panelEl;
|
|
11926
|
-
if (!triggerEl || !panelEl)
|
|
11927
|
-
return;
|
|
11928
|
-
if (panelEl.parentElement !== document.body)
|
|
11929
|
-
document.body.appendChild(panelEl);
|
|
11930
|
-
const r = triggerEl.getBoundingClientRect();
|
|
11931
|
-
panelEl.style.minWidth = `${Math.round(r.width)}px`;
|
|
11932
|
-
const ph = panelEl.offsetHeight || 240;
|
|
11933
|
-
const gap = 4;
|
|
11934
|
-
const margin = 8;
|
|
11935
|
-
let top = r.bottom + gap;
|
|
11936
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
11937
|
-
top = r.top - gap - ph;
|
|
11945
|
+
/** Attach once the panel exists — it is rendered inside an `@if`. */
|
|
11946
|
+
attachPanel() {
|
|
11947
|
+
const trigger = this.trigger()?.nativeElement;
|
|
11948
|
+
if (trigger && this.panelEl) {
|
|
11949
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
11938
11950
|
}
|
|
11939
|
-
let left = r.left;
|
|
11940
|
-
const pw = panelEl.offsetWidth || r.width;
|
|
11941
|
-
if (left + pw > window.innerWidth - margin) {
|
|
11942
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
11943
|
-
}
|
|
11944
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
11945
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
11946
11951
|
}
|
|
11947
11952
|
onDocumentClick(event) {
|
|
11948
11953
|
if (!this.open())
|
|
@@ -11954,9 +11959,8 @@ class SdsMultiSelect {
|
|
|
11954
11959
|
this.close();
|
|
11955
11960
|
}
|
|
11956
11961
|
ngOnDestroy() {
|
|
11957
|
-
this.removeReflowListeners();
|
|
11958
11962
|
clearTimeout(this.typeTimer);
|
|
11959
|
-
this.
|
|
11963
|
+
this.overlay.detach();
|
|
11960
11964
|
}
|
|
11961
11965
|
setValue(v) {
|
|
11962
11966
|
this.value.set(v);
|
|
@@ -11964,7 +11968,7 @@ class SdsMultiSelect {
|
|
|
11964
11968
|
this.onTouched();
|
|
11965
11969
|
// Chip display can change the trigger height while the panel is open.
|
|
11966
11970
|
if (this.open())
|
|
11967
|
-
setTimeout(() => this.
|
|
11971
|
+
setTimeout(() => this.overlay.reposition());
|
|
11968
11972
|
}
|
|
11969
11973
|
// --- ControlValueAccessor ---
|
|
11970
11974
|
onChange = () => { };
|
|
@@ -12012,7 +12016,9 @@ class SdsMultiSelect {
|
|
|
12012
12016
|
@if (display() === 'chip' && selectedOptions().length) {
|
|
12013
12017
|
<div class="flex flex-1 flex-wrap items-center gap-1 py-1 pl-2">
|
|
12014
12018
|
@for (opt of selectedOptions(); track $index) {
|
|
12015
|
-
<span
|
|
12019
|
+
<span
|
|
12020
|
+
class="inline-flex items-center gap-1 rounded bg-brand-50 py-0.5 pl-2 pr-1 text-xs font-medium text-brand-700"
|
|
12021
|
+
>
|
|
12016
12022
|
{{ opt.label }}
|
|
12017
12023
|
<button
|
|
12018
12024
|
type="button"
|
|
@@ -12057,11 +12063,20 @@ class SdsMultiSelect {
|
|
|
12057
12063
|
|
|
12058
12064
|
@if (open()) {
|
|
12059
12065
|
<!-- Appended to document.body on open (see positionPanel). -->
|
|
12060
|
-
<div
|
|
12066
|
+
<div
|
|
12067
|
+
data-sds-select-panel
|
|
12068
|
+
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
12069
|
+
>
|
|
12061
12070
|
@if (filter()) {
|
|
12062
12071
|
<div class="border-b border-border p-2">
|
|
12063
|
-
<div
|
|
12064
|
-
|
|
12072
|
+
<div
|
|
12073
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
12074
|
+
>
|
|
12075
|
+
<sds-icon
|
|
12076
|
+
name="search"
|
|
12077
|
+
[size]="16"
|
|
12078
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
12079
|
+
/>
|
|
12065
12080
|
<input
|
|
12066
12081
|
type="text"
|
|
12067
12082
|
role="combobox"
|
|
@@ -12085,13 +12100,18 @@ class SdsMultiSelect {
|
|
|
12085
12100
|
role="checkbox"
|
|
12086
12101
|
[id]="toggleAllId"
|
|
12087
12102
|
[attr.data-index]="-1"
|
|
12088
|
-
[attr.aria-checked]="
|
|
12103
|
+
[attr.aria-checked]="
|
|
12104
|
+
allFilteredSelected() ? 'true' : toggleAllIndeterminate() ? 'mixed' : 'false'
|
|
12105
|
+
"
|
|
12089
12106
|
class="flex w-full items-center gap-2.5 border-b border-border px-3 py-2 text-sm text-ink"
|
|
12090
12107
|
[class.bg-brand-50]="highlightedIndex() === -1"
|
|
12091
12108
|
(click)="toggleAll()"
|
|
12092
12109
|
(mouseenter)="highlightedIndex.set(-1)"
|
|
12093
12110
|
>
|
|
12094
|
-
<span
|
|
12111
|
+
<span
|
|
12112
|
+
class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border"
|
|
12113
|
+
[class]="checkboxClass(allFilteredSelected(), toggleAllIndeterminate())"
|
|
12114
|
+
>
|
|
12095
12115
|
@if (allFilteredSelected()) {
|
|
12096
12116
|
<sds-icon name="check" [size]="12" />
|
|
12097
12117
|
} @else if (toggleAllIndeterminate()) {
|
|
@@ -12102,7 +12122,12 @@ class SdsMultiSelect {
|
|
|
12102
12122
|
</button>
|
|
12103
12123
|
}
|
|
12104
12124
|
|
|
12105
|
-
<ul
|
|
12125
|
+
<ul
|
|
12126
|
+
[id]="listId"
|
|
12127
|
+
role="listbox"
|
|
12128
|
+
aria-multiselectable="true"
|
|
12129
|
+
class="max-h-60 overflow-auto py-1"
|
|
12130
|
+
>
|
|
12106
12131
|
@for (opt of filteredOptions(); track $index; let i = $index) {
|
|
12107
12132
|
<li
|
|
12108
12133
|
[id]="optionId(i)"
|
|
@@ -12114,7 +12139,10 @@ class SdsMultiSelect {
|
|
|
12114
12139
|
(click)="opt.disabled ? null : toggleOption(opt)"
|
|
12115
12140
|
(mouseenter)="opt.disabled ? null : highlightedIndex.set(i)"
|
|
12116
12141
|
>
|
|
12117
|
-
<span
|
|
12142
|
+
<span
|
|
12143
|
+
class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border"
|
|
12144
|
+
[class]="checkboxClass(isSelected(opt.value), false)"
|
|
12145
|
+
>
|
|
12118
12146
|
@if (isSelected(opt.value)) {
|
|
12119
12147
|
<sds-icon name="check" [size]="12" />
|
|
12120
12148
|
}
|
|
@@ -12135,7 +12163,10 @@ class SdsMultiSelect {
|
|
|
12135
12163
|
<li class="px-3 py-2 text-sm text-ink-faint">
|
|
12136
12164
|
<ng-container
|
|
12137
12165
|
[ngTemplateOutlet]="emptyTpl()!.template"
|
|
12138
|
-
[ngTemplateOutletContext]="{
|
|
12166
|
+
[ngTemplateOutletContext]="{
|
|
12167
|
+
$implicit: filterText(),
|
|
12168
|
+
filterText: filterText(),
|
|
12169
|
+
}"
|
|
12139
12170
|
/>
|
|
12140
12171
|
</li>
|
|
12141
12172
|
} @else {
|
|
@@ -12203,7 +12234,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12203
12234
|
@if (display() === 'chip' && selectedOptions().length) {
|
|
12204
12235
|
<div class="flex flex-1 flex-wrap items-center gap-1 py-1 pl-2">
|
|
12205
12236
|
@for (opt of selectedOptions(); track $index) {
|
|
12206
|
-
<span
|
|
12237
|
+
<span
|
|
12238
|
+
class="inline-flex items-center gap-1 rounded bg-brand-50 py-0.5 pl-2 pr-1 text-xs font-medium text-brand-700"
|
|
12239
|
+
>
|
|
12207
12240
|
{{ opt.label }}
|
|
12208
12241
|
<button
|
|
12209
12242
|
type="button"
|
|
@@ -12248,11 +12281,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12248
12281
|
|
|
12249
12282
|
@if (open()) {
|
|
12250
12283
|
<!-- Appended to document.body on open (see positionPanel). -->
|
|
12251
|
-
<div
|
|
12284
|
+
<div
|
|
12285
|
+
data-sds-select-panel
|
|
12286
|
+
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
12287
|
+
>
|
|
12252
12288
|
@if (filter()) {
|
|
12253
12289
|
<div class="border-b border-border p-2">
|
|
12254
|
-
<div
|
|
12255
|
-
|
|
12290
|
+
<div
|
|
12291
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
12292
|
+
>
|
|
12293
|
+
<sds-icon
|
|
12294
|
+
name="search"
|
|
12295
|
+
[size]="16"
|
|
12296
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
12297
|
+
/>
|
|
12256
12298
|
<input
|
|
12257
12299
|
type="text"
|
|
12258
12300
|
role="combobox"
|
|
@@ -12276,13 +12318,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12276
12318
|
role="checkbox"
|
|
12277
12319
|
[id]="toggleAllId"
|
|
12278
12320
|
[attr.data-index]="-1"
|
|
12279
|
-
[attr.aria-checked]="
|
|
12321
|
+
[attr.aria-checked]="
|
|
12322
|
+
allFilteredSelected() ? 'true' : toggleAllIndeterminate() ? 'mixed' : 'false'
|
|
12323
|
+
"
|
|
12280
12324
|
class="flex w-full items-center gap-2.5 border-b border-border px-3 py-2 text-sm text-ink"
|
|
12281
12325
|
[class.bg-brand-50]="highlightedIndex() === -1"
|
|
12282
12326
|
(click)="toggleAll()"
|
|
12283
12327
|
(mouseenter)="highlightedIndex.set(-1)"
|
|
12284
12328
|
>
|
|
12285
|
-
<span
|
|
12329
|
+
<span
|
|
12330
|
+
class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border"
|
|
12331
|
+
[class]="checkboxClass(allFilteredSelected(), toggleAllIndeterminate())"
|
|
12332
|
+
>
|
|
12286
12333
|
@if (allFilteredSelected()) {
|
|
12287
12334
|
<sds-icon name="check" [size]="12" />
|
|
12288
12335
|
} @else if (toggleAllIndeterminate()) {
|
|
@@ -12293,7 +12340,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12293
12340
|
</button>
|
|
12294
12341
|
}
|
|
12295
12342
|
|
|
12296
|
-
<ul
|
|
12343
|
+
<ul
|
|
12344
|
+
[id]="listId"
|
|
12345
|
+
role="listbox"
|
|
12346
|
+
aria-multiselectable="true"
|
|
12347
|
+
class="max-h-60 overflow-auto py-1"
|
|
12348
|
+
>
|
|
12297
12349
|
@for (opt of filteredOptions(); track $index; let i = $index) {
|
|
12298
12350
|
<li
|
|
12299
12351
|
[id]="optionId(i)"
|
|
@@ -12305,7 +12357,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12305
12357
|
(click)="opt.disabled ? null : toggleOption(opt)"
|
|
12306
12358
|
(mouseenter)="opt.disabled ? null : highlightedIndex.set(i)"
|
|
12307
12359
|
>
|
|
12308
|
-
<span
|
|
12360
|
+
<span
|
|
12361
|
+
class="grid h-[18px] w-[18px] shrink-0 place-items-center rounded border"
|
|
12362
|
+
[class]="checkboxClass(isSelected(opt.value), false)"
|
|
12363
|
+
>
|
|
12309
12364
|
@if (isSelected(opt.value)) {
|
|
12310
12365
|
<sds-icon name="check" [size]="12" />
|
|
12311
12366
|
}
|
|
@@ -12326,7 +12381,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
12326
12381
|
<li class="px-3 py-2 text-sm text-ink-faint">
|
|
12327
12382
|
<ng-container
|
|
12328
12383
|
[ngTemplateOutlet]="emptyTpl()!.template"
|
|
12329
|
-
[ngTemplateOutletContext]="{
|
|
12384
|
+
[ngTemplateOutletContext]="{
|
|
12385
|
+
$implicit: filterText(),
|
|
12386
|
+
filterText: filterText(),
|
|
12387
|
+
}"
|
|
12330
12388
|
/>
|
|
12331
12389
|
</li>
|
|
12332
12390
|
} @else {
|
|
@@ -12450,15 +12508,8 @@ class SdsAutocomplete {
|
|
|
12450
12508
|
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
12451
12509
|
/** Every option this control has seen, by value — how a chip keeps its label after a new search. */
|
|
12452
12510
|
seen = new Map();
|
|
12453
|
-
/**
|
|
12454
|
-
|
|
12455
|
-
*
|
|
12456
|
-
* <p>On destroy the viewChild is already cleared — it lives inside an `@if` — so removing through
|
|
12457
|
-
* it removes nothing and the panel is orphaned in `<body>` for ever. A test caught it: a leaked
|
|
12458
|
-
* panel from one spec was still in the document when the next one queried for its own.
|
|
12459
|
-
* `sds-select` keeps the same raw handle, for the same reason.
|
|
12460
|
-
*/
|
|
12461
|
-
panelEl = null;
|
|
12511
|
+
/** Body-appending, positioning, follow-on-scroll and teardown all live in here now. */
|
|
12512
|
+
overlay = new SdsOverlayPanel();
|
|
12462
12513
|
timer;
|
|
12463
12514
|
seq = 0;
|
|
12464
12515
|
destroyed = false;
|
|
@@ -12490,7 +12541,7 @@ class SdsAutocomplete {
|
|
|
12490
12541
|
}, ...(ngDevMode ? [{ debugName: "triggerPlaceholder" }] : []));
|
|
12491
12542
|
closeFn = () => this.close();
|
|
12492
12543
|
constructor() {
|
|
12493
|
-
afterNextRender(() => this.
|
|
12544
|
+
afterNextRender(() => this.place(), { injector: this.injector });
|
|
12494
12545
|
}
|
|
12495
12546
|
// ------------------------------------------------------------------ options
|
|
12496
12547
|
labelOf(option) {
|
|
@@ -12553,8 +12604,7 @@ class SdsAutocomplete {
|
|
|
12553
12604
|
this.open.set(true);
|
|
12554
12605
|
this.opened.emit();
|
|
12555
12606
|
this.schedule(0);
|
|
12556
|
-
setTimeout(() => this.
|
|
12557
|
-
this.addReflow();
|
|
12607
|
+
setTimeout(() => this.place());
|
|
12558
12608
|
}
|
|
12559
12609
|
toggle(event) {
|
|
12560
12610
|
event.stopPropagation();
|
|
@@ -12598,7 +12648,8 @@ class SdsAutocomplete {
|
|
|
12598
12648
|
finally {
|
|
12599
12649
|
if (seq === this.seq && !this.destroyed) {
|
|
12600
12650
|
this.loading.set(false);
|
|
12601
|
-
|
|
12651
|
+
// The panel's height changed with its contents, so it may now need flipping.
|
|
12652
|
+
setTimeout(() => this.overlay.reposition());
|
|
12602
12653
|
}
|
|
12603
12654
|
}
|
|
12604
12655
|
}
|
|
@@ -12682,67 +12733,29 @@ class SdsAutocomplete {
|
|
|
12682
12733
|
return;
|
|
12683
12734
|
this.open.set(false);
|
|
12684
12735
|
this.active.set(-1);
|
|
12685
|
-
this.
|
|
12686
|
-
this.panelEl?.remove();
|
|
12687
|
-
this.panelEl = null;
|
|
12736
|
+
this.overlay.detach();
|
|
12688
12737
|
this.closed.emit(this.value());
|
|
12689
12738
|
}
|
|
12690
12739
|
onDocumentClick(event) {
|
|
12691
12740
|
if (!this.open())
|
|
12692
12741
|
return;
|
|
12693
12742
|
const target = event.target;
|
|
12694
|
-
if (!this.host.nativeElement.contains(target) && !this.
|
|
12743
|
+
if (!this.host.nativeElement.contains(target) && !this.overlay.contains(target)) {
|
|
12695
12744
|
this.close();
|
|
12696
12745
|
}
|
|
12697
12746
|
}
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
listening = false;
|
|
12701
|
-
addReflow() {
|
|
12702
|
-
if (this.listening)
|
|
12703
|
-
return;
|
|
12704
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
12705
|
-
window.addEventListener('resize', this.reflow);
|
|
12706
|
-
this.listening = true;
|
|
12707
|
-
}
|
|
12708
|
-
removeReflow() {
|
|
12709
|
-
if (!this.listening)
|
|
12710
|
-
return;
|
|
12711
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
12712
|
-
window.removeEventListener('resize', this.reflow);
|
|
12713
|
-
this.listening = false;
|
|
12714
|
-
}
|
|
12715
|
-
position() {
|
|
12747
|
+
/** Attach once the panel exists in the DOM — it is inside an `@if`. */
|
|
12748
|
+
place() {
|
|
12716
12749
|
const trigger = this.triggerEl()?.nativeElement;
|
|
12717
12750
|
const panel = this.panelRef()?.nativeElement;
|
|
12718
|
-
if (
|
|
12719
|
-
|
|
12720
|
-
this.panelEl = panel;
|
|
12721
|
-
if (panel.parentElement !== document.body)
|
|
12722
|
-
document.body.appendChild(panel);
|
|
12723
|
-
const r = trigger.getBoundingClientRect();
|
|
12724
|
-
panel.style.minWidth = `${Math.round(r.width)}px`;
|
|
12725
|
-
const ph = panel.offsetHeight || 240;
|
|
12726
|
-
const gap = 4;
|
|
12727
|
-
const margin = 8;
|
|
12728
|
-
let top = r.bottom + gap;
|
|
12729
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
12730
|
-
top = r.top - gap - ph;
|
|
12751
|
+
if (trigger && panel) {
|
|
12752
|
+
this.overlay.attach(trigger, panel, { width: 'min' });
|
|
12731
12753
|
}
|
|
12732
|
-
let left = r.left;
|
|
12733
|
-
const pw = panel.offsetWidth || r.width;
|
|
12734
|
-
if (left + pw > window.innerWidth - margin) {
|
|
12735
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
12736
|
-
}
|
|
12737
|
-
panel.style.top = `${Math.round(top)}px`;
|
|
12738
|
-
panel.style.left = `${Math.round(left)}px`;
|
|
12739
12754
|
}
|
|
12740
12755
|
ngOnDestroy() {
|
|
12741
12756
|
this.destroyed = true;
|
|
12742
12757
|
clearTimeout(this.timer);
|
|
12743
|
-
this.
|
|
12744
|
-
this.panelEl?.remove();
|
|
12745
|
-
this.panelEl = null;
|
|
12758
|
+
this.overlay.detach();
|
|
12746
12759
|
}
|
|
12747
12760
|
setValue(v) {
|
|
12748
12761
|
this.value.set(v);
|
|
@@ -13110,26 +13123,166 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13110
13123
|
}], ctorParameters: () => [], propDecorators: { search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], display: [{ type: i0.Input, args: [{ isSignal: true, alias: "display", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], selectedOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedOptions", 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 }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], searchingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchingText", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], searchErrorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchErrorText", required: false }] }], minCharsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "minCharsText", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], openLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "openLabel", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], fieldEl: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], optionTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectOption), { isSignal: true }] }], emptyTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectEmpty), { isSignal: true }] }], footerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdsSelectFooter), { isSignal: true }] }] } });
|
|
13111
13124
|
|
|
13112
13125
|
const SDS_PHONE_COUNTRIES = [
|
|
13113
|
-
{
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
{
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
{
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13126
|
+
{
|
|
13127
|
+
iso2: 'BE',
|
|
13128
|
+
name: 'Belgium',
|
|
13129
|
+
dialCode: '32',
|
|
13130
|
+
lengths: [9],
|
|
13131
|
+
format: '### ## ## ##',
|
|
13132
|
+
example: '470123456',
|
|
13133
|
+
},
|
|
13134
|
+
{
|
|
13135
|
+
iso2: 'FR',
|
|
13136
|
+
name: 'France',
|
|
13137
|
+
dialCode: '33',
|
|
13138
|
+
lengths: [9],
|
|
13139
|
+
format: '# ## ## ## ##',
|
|
13140
|
+
example: '612345678',
|
|
13141
|
+
},
|
|
13142
|
+
{
|
|
13143
|
+
iso2: 'LU',
|
|
13144
|
+
name: 'Luxembourg',
|
|
13145
|
+
dialCode: '352',
|
|
13146
|
+
lengths: [9, 8, 6],
|
|
13147
|
+
format: '### ### ###',
|
|
13148
|
+
example: '628123456',
|
|
13149
|
+
},
|
|
13150
|
+
{
|
|
13151
|
+
iso2: 'NL',
|
|
13152
|
+
name: 'Netherlands',
|
|
13153
|
+
dialCode: '31',
|
|
13154
|
+
lengths: [9],
|
|
13155
|
+
format: '# ## ## ## ##',
|
|
13156
|
+
example: '612345678',
|
|
13157
|
+
},
|
|
13158
|
+
{
|
|
13159
|
+
iso2: 'DE',
|
|
13160
|
+
name: 'Germany',
|
|
13161
|
+
dialCode: '49',
|
|
13162
|
+
lengths: [10, 11],
|
|
13163
|
+
format: '#### #######',
|
|
13164
|
+
example: '15123456789',
|
|
13165
|
+
},
|
|
13166
|
+
{
|
|
13167
|
+
iso2: 'GB',
|
|
13168
|
+
name: 'United Kingdom',
|
|
13169
|
+
dialCode: '44',
|
|
13170
|
+
lengths: [10],
|
|
13171
|
+
format: '#### ######',
|
|
13172
|
+
example: '7400123456',
|
|
13173
|
+
},
|
|
13174
|
+
{
|
|
13175
|
+
iso2: 'IE',
|
|
13176
|
+
name: 'Ireland',
|
|
13177
|
+
dialCode: '353',
|
|
13178
|
+
lengths: [9],
|
|
13179
|
+
format: '## ### ####',
|
|
13180
|
+
example: '850123456',
|
|
13181
|
+
},
|
|
13182
|
+
{
|
|
13183
|
+
iso2: 'CH',
|
|
13184
|
+
name: 'Switzerland',
|
|
13185
|
+
dialCode: '41',
|
|
13186
|
+
lengths: [9],
|
|
13187
|
+
format: '## ### ## ##',
|
|
13188
|
+
example: '781234567',
|
|
13189
|
+
},
|
|
13190
|
+
{
|
|
13191
|
+
iso2: 'ES',
|
|
13192
|
+
name: 'Spain',
|
|
13193
|
+
dialCode: '34',
|
|
13194
|
+
lengths: [9],
|
|
13195
|
+
format: '### ## ## ##',
|
|
13196
|
+
example: '612345678',
|
|
13197
|
+
},
|
|
13198
|
+
{
|
|
13199
|
+
iso2: 'IT',
|
|
13200
|
+
name: 'Italy',
|
|
13201
|
+
dialCode: '39',
|
|
13202
|
+
lengths: [9, 10],
|
|
13203
|
+
format: '### ### ####',
|
|
13204
|
+
example: '3123456789',
|
|
13205
|
+
},
|
|
13206
|
+
{
|
|
13207
|
+
iso2: 'PT',
|
|
13208
|
+
name: 'Portugal',
|
|
13209
|
+
dialCode: '351',
|
|
13210
|
+
lengths: [9],
|
|
13211
|
+
format: '### ### ###',
|
|
13212
|
+
example: '912345678',
|
|
13213
|
+
},
|
|
13214
|
+
{
|
|
13215
|
+
iso2: 'AT',
|
|
13216
|
+
name: 'Austria',
|
|
13217
|
+
dialCode: '43',
|
|
13218
|
+
lengths: [10, 11],
|
|
13219
|
+
format: '### #######',
|
|
13220
|
+
example: '6641234567',
|
|
13221
|
+
},
|
|
13222
|
+
{
|
|
13223
|
+
iso2: 'DK',
|
|
13224
|
+
name: 'Denmark',
|
|
13225
|
+
dialCode: '45',
|
|
13226
|
+
lengths: [8],
|
|
13227
|
+
format: '## ## ## ##',
|
|
13228
|
+
example: '20123456',
|
|
13229
|
+
},
|
|
13230
|
+
{
|
|
13231
|
+
iso2: 'SE',
|
|
13232
|
+
name: 'Sweden',
|
|
13233
|
+
dialCode: '46',
|
|
13234
|
+
lengths: [9],
|
|
13235
|
+
format: '## ### ## ##',
|
|
13236
|
+
example: '701234567',
|
|
13237
|
+
},
|
|
13238
|
+
{
|
|
13239
|
+
iso2: 'NO',
|
|
13240
|
+
name: 'Norway',
|
|
13241
|
+
dialCode: '47',
|
|
13242
|
+
lengths: [8],
|
|
13243
|
+
format: '### ## ###',
|
|
13244
|
+
example: '40612345',
|
|
13245
|
+
},
|
|
13246
|
+
{
|
|
13247
|
+
iso2: 'PL',
|
|
13248
|
+
name: 'Poland',
|
|
13249
|
+
dialCode: '48',
|
|
13250
|
+
lengths: [9],
|
|
13251
|
+
format: '### ### ###',
|
|
13252
|
+
example: '512345678',
|
|
13253
|
+
},
|
|
13254
|
+
{
|
|
13255
|
+
iso2: 'US',
|
|
13256
|
+
name: 'United States',
|
|
13257
|
+
dialCode: '1',
|
|
13258
|
+
lengths: [10],
|
|
13259
|
+
format: '(###) ###-####',
|
|
13260
|
+
example: '2015550123',
|
|
13261
|
+
},
|
|
13262
|
+
{
|
|
13263
|
+
iso2: 'CA',
|
|
13264
|
+
name: 'Canada',
|
|
13265
|
+
dialCode: '1',
|
|
13266
|
+
lengths: [10],
|
|
13267
|
+
format: '(###) ###-####',
|
|
13268
|
+
example: '2042345678',
|
|
13269
|
+
},
|
|
13270
|
+
{
|
|
13271
|
+
iso2: 'MA',
|
|
13272
|
+
name: 'Morocco',
|
|
13273
|
+
dialCode: '212',
|
|
13274
|
+
lengths: [9],
|
|
13275
|
+
format: '### ## ## ##',
|
|
13276
|
+
example: '612345678',
|
|
13277
|
+
},
|
|
13278
|
+
{
|
|
13279
|
+
iso2: 'TR',
|
|
13280
|
+
name: 'Turkey',
|
|
13281
|
+
dialCode: '90',
|
|
13282
|
+
lengths: [10],
|
|
13283
|
+
format: '### ### ## ##',
|
|
13284
|
+
example: '5012345678',
|
|
13285
|
+
},
|
|
13133
13286
|
];
|
|
13134
13287
|
/** Reduce raw input to national-significant digits (drops non-digits & leading trunk zeros). */
|
|
13135
13288
|
function sdsNormalizeSignificant(raw) {
|
|
@@ -13280,6 +13433,8 @@ class SdsPhoneInput {
|
|
|
13280
13433
|
anchor = viewChild('anchor', ...(ngDevMode ? [{ debugName: "anchor" }] : []));
|
|
13281
13434
|
field = viewChild('field', ...(ngDevMode ? [{ debugName: "field" }] : []));
|
|
13282
13435
|
panelEl = null;
|
|
13436
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
13437
|
+
overlay = new SdsOverlayPanel();
|
|
13283
13438
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
13284
13439
|
/** Countries after applying [onlyCountries] and [preferredCountries] ordering. */
|
|
13285
13440
|
availableCountries = computed(() => {
|
|
@@ -13413,8 +13568,7 @@ class SdsPhoneInput {
|
|
|
13413
13568
|
this.open.set(true);
|
|
13414
13569
|
afterNextRender(() => {
|
|
13415
13570
|
this.panelEl = this.host.nativeElement.querySelector('[data-sds-phone-panel]');
|
|
13416
|
-
this.
|
|
13417
|
-
this.addReflowListeners();
|
|
13571
|
+
this.attachPanel();
|
|
13418
13572
|
this.panelEl?.querySelector('input')?.focus();
|
|
13419
13573
|
this.scrollHighlightedIntoView();
|
|
13420
13574
|
}, { injector: this.injector });
|
|
@@ -13422,8 +13576,7 @@ class SdsPhoneInput {
|
|
|
13422
13576
|
close(returnFocus = false) {
|
|
13423
13577
|
if (!this.open())
|
|
13424
13578
|
return;
|
|
13425
|
-
this.
|
|
13426
|
-
this.panelEl?.remove();
|
|
13579
|
+
this.overlay.detach();
|
|
13427
13580
|
this.panelEl = null;
|
|
13428
13581
|
this.open.set(false);
|
|
13429
13582
|
if (returnFocus)
|
|
@@ -13444,7 +13597,7 @@ class SdsPhoneInput {
|
|
|
13444
13597
|
onFilter(event) {
|
|
13445
13598
|
this.filterText.set(event.target.value);
|
|
13446
13599
|
this.highlightedIndex.set(this.filteredCountries().length ? 0 : -1);
|
|
13447
|
-
setTimeout(() => this.
|
|
13600
|
+
setTimeout(() => this.overlay.reposition());
|
|
13448
13601
|
}
|
|
13449
13602
|
// --- keyboard ---
|
|
13450
13603
|
onTriggerKeydown(event) {
|
|
@@ -13514,46 +13667,12 @@ class SdsPhoneInput {
|
|
|
13514
13667
|
?.scrollIntoView({ block: 'nearest' });
|
|
13515
13668
|
});
|
|
13516
13669
|
}
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
return;
|
|
13523
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
13524
|
-
window.addEventListener('resize', this.reflow);
|
|
13525
|
-
this.listening = true;
|
|
13526
|
-
}
|
|
13527
|
-
removeReflowListeners() {
|
|
13528
|
-
if (!this.listening)
|
|
13529
|
-
return;
|
|
13530
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
13531
|
-
window.removeEventListener('resize', this.reflow);
|
|
13532
|
-
this.listening = false;
|
|
13533
|
-
}
|
|
13534
|
-
positionPanel() {
|
|
13535
|
-
const anchorEl = this.anchor()?.nativeElement;
|
|
13536
|
-
const panelEl = this.panelEl;
|
|
13537
|
-
if (!anchorEl || !panelEl)
|
|
13538
|
-
return;
|
|
13539
|
-
if (panelEl.parentElement !== document.body)
|
|
13540
|
-
document.body.appendChild(panelEl);
|
|
13541
|
-
const r = anchorEl.getBoundingClientRect();
|
|
13542
|
-
panelEl.style.minWidth = `${Math.round(r.width)}px`;
|
|
13543
|
-
const ph = panelEl.offsetHeight || 280;
|
|
13544
|
-
const gap = 4;
|
|
13545
|
-
const margin = 8;
|
|
13546
|
-
let top = r.bottom + gap;
|
|
13547
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
13548
|
-
top = r.top - gap - ph;
|
|
13549
|
-
}
|
|
13550
|
-
let left = r.left;
|
|
13551
|
-
const pw = panelEl.offsetWidth || r.width;
|
|
13552
|
-
if (left + pw > window.innerWidth - margin) {
|
|
13553
|
-
left = Math.max(margin, window.innerWidth - margin - pw);
|
|
13670
|
+
/** Attach once the panel exists — it is rendered inside an `@if`. */
|
|
13671
|
+
attachPanel() {
|
|
13672
|
+
const trigger = this.anchor()?.nativeElement;
|
|
13673
|
+
if (trigger && this.panelEl) {
|
|
13674
|
+
this.overlay.attach(trigger, this.panelEl, { width: 'min' });
|
|
13554
13675
|
}
|
|
13555
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
13556
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
13557
13676
|
}
|
|
13558
13677
|
onDocumentClick(event) {
|
|
13559
13678
|
if (!this.open())
|
|
@@ -13569,8 +13688,7 @@ class SdsPhoneInput {
|
|
|
13569
13688
|
this.onTouched();
|
|
13570
13689
|
}
|
|
13571
13690
|
ngOnDestroy() {
|
|
13572
|
-
this.
|
|
13573
|
-
this.panelEl?.remove();
|
|
13691
|
+
this.overlay.detach();
|
|
13574
13692
|
}
|
|
13575
13693
|
emit() {
|
|
13576
13694
|
const value = this.e164();
|
|
@@ -13667,7 +13785,9 @@ class SdsPhoneInput {
|
|
|
13667
13785
|
(click)="toggle()"
|
|
13668
13786
|
(keydown)="onTriggerKeydown($event)"
|
|
13669
13787
|
>
|
|
13670
|
-
<span
|
|
13788
|
+
<span
|
|
13789
|
+
class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft"
|
|
13790
|
+
>
|
|
13671
13791
|
{{ country().iso2 }}
|
|
13672
13792
|
</span>
|
|
13673
13793
|
<span class="text-ink-soft">+{{ country().dialCode }}</span>
|
|
@@ -13718,8 +13838,14 @@ class SdsPhoneInput {
|
|
|
13718
13838
|
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
13719
13839
|
>
|
|
13720
13840
|
<div class="border-b border-border p-2">
|
|
13721
|
-
<div
|
|
13722
|
-
|
|
13841
|
+
<div
|
|
13842
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
13843
|
+
>
|
|
13844
|
+
<sds-icon
|
|
13845
|
+
name="search"
|
|
13846
|
+
[size]="16"
|
|
13847
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
13848
|
+
/>
|
|
13723
13849
|
<input
|
|
13724
13850
|
type="text"
|
|
13725
13851
|
[attr.aria-controls]="listId"
|
|
@@ -13744,7 +13870,9 @@ class SdsPhoneInput {
|
|
|
13744
13870
|
(click)="selectCountry(c)"
|
|
13745
13871
|
(mouseenter)="highlightedIndex.set(i)"
|
|
13746
13872
|
>
|
|
13747
|
-
<span
|
|
13873
|
+
<span
|
|
13874
|
+
class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft"
|
|
13875
|
+
>
|
|
13748
13876
|
{{ c.iso2 }}
|
|
13749
13877
|
</span>
|
|
13750
13878
|
<span class="flex-1 truncate">{{ countryName(c) }}</span>
|
|
@@ -13808,7 +13936,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13808
13936
|
(click)="toggle()"
|
|
13809
13937
|
(keydown)="onTriggerKeydown($event)"
|
|
13810
13938
|
>
|
|
13811
|
-
<span
|
|
13939
|
+
<span
|
|
13940
|
+
class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft"
|
|
13941
|
+
>
|
|
13812
13942
|
{{ country().iso2 }}
|
|
13813
13943
|
</span>
|
|
13814
13944
|
<span class="text-ink-soft">+{{ country().dialCode }}</span>
|
|
@@ -13859,8 +13989,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13859
13989
|
class="fixed z-[1000] overflow-hidden rounded-card border border-border bg-surface shadow-lg"
|
|
13860
13990
|
>
|
|
13861
13991
|
<div class="border-b border-border p-2">
|
|
13862
|
-
<div
|
|
13863
|
-
|
|
13992
|
+
<div
|
|
13993
|
+
class="relative flex items-center rounded-control border border-border focus-within:border-brand-500"
|
|
13994
|
+
>
|
|
13995
|
+
<sds-icon
|
|
13996
|
+
name="search"
|
|
13997
|
+
[size]="16"
|
|
13998
|
+
class="pointer-events-none absolute left-2.5 text-ink-faint"
|
|
13999
|
+
/>
|
|
13864
14000
|
<input
|
|
13865
14001
|
type="text"
|
|
13866
14002
|
[attr.aria-controls]="listId"
|
|
@@ -13885,7 +14021,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13885
14021
|
(click)="selectCountry(c)"
|
|
13886
14022
|
(mouseenter)="highlightedIndex.set(i)"
|
|
13887
14023
|
>
|
|
13888
|
-
<span
|
|
14024
|
+
<span
|
|
14025
|
+
class="grid h-5 min-w-7 place-items-center rounded bg-muted px-1 text-[11px] font-semibold tracking-wide text-ink-soft"
|
|
14026
|
+
>
|
|
13889
14027
|
{{ c.iso2 }}
|
|
13890
14028
|
</span>
|
|
13891
14029
|
<span class="flex-1 truncate">{{ countryName(c) }}</span>
|
|
@@ -14070,7 +14208,8 @@ class SdsAddressAutocomplete {
|
|
|
14070
14208
|
seq = 0;
|
|
14071
14209
|
timer;
|
|
14072
14210
|
destroyed = false;
|
|
14073
|
-
|
|
14211
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
14212
|
+
overlay = new SdsOverlayPanel();
|
|
14074
14213
|
fieldId = computed(() => this.inputId() || this.autoId, ...(ngDevMode ? [{ debugName: "fieldId" }] : []));
|
|
14075
14214
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
14076
14215
|
pill = computed(() => this.shape() === 'pill', ...(ngDevMode ? [{ debugName: "pill" }] : []));
|
|
@@ -14333,79 +14472,57 @@ class SdsAddressAutocomplete {
|
|
|
14333
14472
|
}
|
|
14334
14473
|
scrollHighlightedIntoView() {
|
|
14335
14474
|
setTimeout(() => {
|
|
14336
|
-
const root = this.
|
|
14475
|
+
const root = this.overlay.element ?? this.host.nativeElement;
|
|
14337
14476
|
root
|
|
14338
14477
|
.querySelector(`[data-index="${this.highlighted()}"]`)
|
|
14339
14478
|
?.scrollIntoView?.({ block: 'nearest' });
|
|
14340
14479
|
});
|
|
14341
14480
|
}
|
|
14342
14481
|
// --- overlay ----------------------------------------------------------------
|
|
14343
|
-
/**
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
reflow = () => this.positionPanel();
|
|
14350
|
-
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
|
+
*/
|
|
14351
14488
|
syncPanel() {
|
|
14352
14489
|
if (this.inline() || !this.panelShown()) {
|
|
14353
|
-
|
|
14354
|
-
this.detachPanel();
|
|
14490
|
+
this.overlay.detach();
|
|
14355
14491
|
return;
|
|
14356
14492
|
}
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
if (!this.listening) {
|
|
14363
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
14364
|
-
window.addEventListener('resize', this.reflow);
|
|
14365
|
-
this.listening = true;
|
|
14366
|
-
}
|
|
14367
|
-
}
|
|
14368
|
-
this.positionPanel();
|
|
14369
|
-
}
|
|
14370
|
-
detachPanel() {
|
|
14371
|
-
if (this.listening) {
|
|
14372
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
14373
|
-
window.removeEventListener('resize', this.reflow);
|
|
14374
|
-
this.listening = false;
|
|
14375
|
-
}
|
|
14376
|
-
this.panelEl?.remove();
|
|
14377
|
-
this.panelEl = null;
|
|
14378
|
-
}
|
|
14379
|
-
positionPanel() {
|
|
14380
|
-
const fieldEl = this.fieldRef()?.nativeElement;
|
|
14381
|
-
const panelEl = this.panelEl;
|
|
14382
|
-
if (!fieldEl || !panelEl)
|
|
14493
|
+
const field = this.fieldRef()?.nativeElement;
|
|
14494
|
+
if (!field)
|
|
14495
|
+
return;
|
|
14496
|
+
if (this.overlay.element) {
|
|
14497
|
+
this.overlay.reposition();
|
|
14383
14498
|
return;
|
|
14384
|
-
const r = fieldEl.getBoundingClientRect();
|
|
14385
|
-
const gap = 4;
|
|
14386
|
-
const margin = 8;
|
|
14387
|
-
panelEl.style.width = `${Math.round(r.width)}px`;
|
|
14388
|
-
const ph = panelEl.offsetHeight || 240;
|
|
14389
|
-
let top = r.bottom + gap;
|
|
14390
|
-
if (top + ph > window.innerHeight - margin && r.top - gap - ph > margin) {
|
|
14391
|
-
top = r.top - gap - ph;
|
|
14392
14499
|
}
|
|
14393
|
-
|
|
14394
|
-
|
|
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();
|
|
14395
14512
|
}
|
|
14396
14513
|
onDocumentClick(event) {
|
|
14397
14514
|
if (this.inline() || !this.open())
|
|
14398
14515
|
return;
|
|
14399
14516
|
const target = event.target;
|
|
14400
14517
|
const inField = this.host.nativeElement.contains(target);
|
|
14401
|
-
const inPanel =
|
|
14518
|
+
const inPanel = this.overlay.contains(target);
|
|
14402
14519
|
if (!inField && !inPanel)
|
|
14403
14520
|
this.close();
|
|
14404
14521
|
}
|
|
14405
14522
|
ngOnDestroy() {
|
|
14406
14523
|
this.destroyed = true;
|
|
14407
14524
|
clearTimeout(this.timer);
|
|
14408
|
-
this.
|
|
14525
|
+
this.overlay.detach();
|
|
14409
14526
|
}
|
|
14410
14527
|
// --- ControlValueAccessor -----------------------------------------------------
|
|
14411
14528
|
onChange = () => { };
|
|
@@ -14512,7 +14629,7 @@ class SdsAddressAutocomplete {
|
|
|
14512
14629
|
<ng-container [ngTemplateOutlet]="panelBody" />
|
|
14513
14630
|
</div>
|
|
14514
14631
|
} @else if (panelShown()) {
|
|
14515
|
-
<!-- Appended to document.body on open (see
|
|
14632
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel). -->
|
|
14516
14633
|
<div
|
|
14517
14634
|
data-sds-address-panel
|
|
14518
14635
|
class="fixed z-[1000] max-h-80 overflow-auto rounded-card border border-border bg-surface p-1 shadow-lg"
|
|
@@ -14737,7 +14854,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
14737
14854
|
<ng-container [ngTemplateOutlet]="panelBody" />
|
|
14738
14855
|
</div>
|
|
14739
14856
|
} @else if (panelShown()) {
|
|
14740
|
-
<!-- Appended to document.body on open (see
|
|
14857
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel). -->
|
|
14741
14858
|
<div
|
|
14742
14859
|
data-sds-address-panel
|
|
14743
14860
|
class="fixed z-[1000] max-h-80 overflow-auto rounded-card border border-border bg-surface p-1 shadow-lg"
|
|
@@ -21107,7 +21224,7 @@ let nextId = 0;
|
|
|
21107
21224
|
* disclosure mechanics (closes on outside click, on Escape, and on selection, restoring focus to the trigger),
|
|
21108
21225
|
* and the same panel styling and open animation — so the three top-bar controls read as one set.
|
|
21109
21226
|
*
|
|
21110
|
-
* 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),
|
|
21111
21228
|
* so a clipping ancestor — a bar with `overflow:hidden`, or a `glass` bar whose `backdrop-filter` is a
|
|
21112
21229
|
* containing block — can never cut it off or paint over it. It repositions on scroll/resize and is removed
|
|
21113
21230
|
* on close. Accessibility follows the APG select-only combobox: focus stays on the trigger, and Arrow/Home/
|
|
@@ -21148,7 +21265,8 @@ class SdsLanguageSwitcher {
|
|
|
21148
21265
|
openChange = output();
|
|
21149
21266
|
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
21150
21267
|
highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
21151
|
-
|
|
21268
|
+
/** Body-appending, positioning, follow-on-scroll and teardown — see SdsOverlayPanel. */
|
|
21269
|
+
overlay = new SdsOverlayPanel();
|
|
21152
21270
|
/** Normalise the mixed `languages` input to `{ code, label }`, defaulting the label to the code. */
|
|
21153
21271
|
options = computed(() => this.languages().map((l) => typeof l === 'string'
|
|
21154
21272
|
? { code: l, label: l.toUpperCase() }
|
|
@@ -21175,9 +21293,17 @@ class SdsLanguageSwitcher {
|
|
|
21175
21293
|
// clipping/stacking ancestor (an overflow:hidden bar, a backdrop-filter glass bar) can cut it off.
|
|
21176
21294
|
// Focus stays on the trigger (combobox model); the active row is surfaced via aria-activedescendant.
|
|
21177
21295
|
afterNextRender(() => {
|
|
21178
|
-
|
|
21179
|
-
this.
|
|
21180
|
-
|
|
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
|
+
}
|
|
21181
21307
|
this.trigger()?.nativeElement.focus();
|
|
21182
21308
|
this.scrollHighlightedIntoView();
|
|
21183
21309
|
}, { injector: this.injector });
|
|
@@ -21185,10 +21311,8 @@ class SdsLanguageSwitcher {
|
|
|
21185
21311
|
close(returnFocus = false) {
|
|
21186
21312
|
if (!this.open())
|
|
21187
21313
|
return;
|
|
21188
|
-
this.removeReflowListeners();
|
|
21189
21314
|
// Remove the portalled node ourselves before Angular tears the view down (it lives in <body> now).
|
|
21190
|
-
this.
|
|
21191
|
-
this.panelEl = null;
|
|
21315
|
+
this.overlay.detach();
|
|
21192
21316
|
this.open.set(false);
|
|
21193
21317
|
this.openChange.emit(false);
|
|
21194
21318
|
if (returnFocus)
|
|
@@ -21204,7 +21328,7 @@ class SdsLanguageSwitcher {
|
|
|
21204
21328
|
return;
|
|
21205
21329
|
const target = event.target;
|
|
21206
21330
|
const inHost = this.el.nativeElement.contains(target);
|
|
21207
|
-
const inPanel =
|
|
21331
|
+
const inPanel = this.overlay.contains(target);
|
|
21208
21332
|
if (!inHost && !inPanel)
|
|
21209
21333
|
this.close();
|
|
21210
21334
|
}
|
|
@@ -21251,7 +21375,8 @@ class SdsLanguageSwitcher {
|
|
|
21251
21375
|
case 'Tab':
|
|
21252
21376
|
this.close(false);
|
|
21253
21377
|
break;
|
|
21254
|
-
default:
|
|
21378
|
+
default:
|
|
21379
|
+
break;
|
|
21255
21380
|
}
|
|
21256
21381
|
}
|
|
21257
21382
|
initHighlight() {
|
|
@@ -21278,68 +21403,19 @@ class SdsLanguageSwitcher {
|
|
|
21278
21403
|
}
|
|
21279
21404
|
scrollHighlightedIntoView() {
|
|
21280
21405
|
setTimeout(() => {
|
|
21281
|
-
this.
|
|
21406
|
+
this.overlay.element
|
|
21282
21407
|
?.querySelector(`[data-index="${this.highlightedIndex()}"]`)
|
|
21283
21408
|
?.scrollIntoView({ block: 'nearest' });
|
|
21284
21409
|
});
|
|
21285
21410
|
}
|
|
21286
|
-
// --- body-appended overlay positioning (mirrors sds-select / sds-date-picker) ---
|
|
21287
|
-
reflow = () => this.positionPanel();
|
|
21288
|
-
listening = false;
|
|
21289
|
-
addReflowListeners() {
|
|
21290
|
-
if (this.listening)
|
|
21291
|
-
return;
|
|
21292
|
-
window.addEventListener('scroll', this.reflow, true);
|
|
21293
|
-
window.addEventListener('resize', this.reflow);
|
|
21294
|
-
this.listening = true;
|
|
21295
|
-
}
|
|
21296
|
-
removeReflowListeners() {
|
|
21297
|
-
if (!this.listening)
|
|
21298
|
-
return;
|
|
21299
|
-
window.removeEventListener('scroll', this.reflow, true);
|
|
21300
|
-
window.removeEventListener('resize', this.reflow);
|
|
21301
|
-
this.listening = false;
|
|
21302
|
-
}
|
|
21303
|
-
positionPanel() {
|
|
21304
|
-
const triggerEl = this.trigger()?.nativeElement;
|
|
21305
|
-
const panelEl = this.panelEl;
|
|
21306
|
-
if (!triggerEl || !panelEl)
|
|
21307
|
-
return;
|
|
21308
|
-
if (panelEl.parentElement !== document.body)
|
|
21309
|
-
document.body.appendChild(panelEl);
|
|
21310
|
-
const r = triggerEl.getBoundingClientRect();
|
|
21311
|
-
const gap = 8;
|
|
21312
|
-
const margin = 8;
|
|
21313
|
-
const pw = panelEl.offsetWidth || 176;
|
|
21314
|
-
const ph = panelEl.offsetHeight || 200;
|
|
21315
|
-
// Vertical: below by default; open above when asked (direction='up') and there is room, or when there
|
|
21316
|
-
// isn't room below but there is above. The panel height is bounded by the list's max-h, but clamp
|
|
21317
|
-
// defensively so top can never be pushed off the top edge.
|
|
21318
|
-
const roomBelow = window.innerHeight - r.bottom - gap;
|
|
21319
|
-
const roomAbove = r.top - gap;
|
|
21320
|
-
let top = (this.direction() === 'up' && roomAbove >= ph) || (roomBelow < ph && roomAbove > roomBelow)
|
|
21321
|
-
? r.top - gap - ph
|
|
21322
|
-
: r.bottom + gap;
|
|
21323
|
-
if (top + ph > window.innerHeight - margin)
|
|
21324
|
-
top = window.innerHeight - margin - ph;
|
|
21325
|
-
if (top < margin)
|
|
21326
|
-
top = margin;
|
|
21327
|
-
// Horizontal: align the panel's end (right) or start (left) edge to the trigger, then clamp.
|
|
21328
|
-
let left = this.align() === 'end' ? r.right - pw : r.left;
|
|
21329
|
-
left = Math.min(left, window.innerWidth - margin - pw);
|
|
21330
|
-
left = Math.max(margin, left);
|
|
21331
|
-
panelEl.style.top = `${Math.round(top)}px`;
|
|
21332
|
-
panelEl.style.left = `${Math.round(left)}px`;
|
|
21333
|
-
}
|
|
21334
21411
|
ngOnDestroy() {
|
|
21335
|
-
this.
|
|
21336
|
-
this.panelEl?.remove();
|
|
21412
|
+
this.overlay.detach();
|
|
21337
21413
|
}
|
|
21338
21414
|
onBrand = computed(() => this.appearance() === 'on-brand', ...(ngDevMode ? [{ debugName: "onBrand" }] : []));
|
|
21339
21415
|
triggerClass = computed(() => this.onBrand()
|
|
21340
21416
|
? 'text-white/85 hover:bg-white/10 hover:text-white focus-visible:ring-white/40'
|
|
21341
21417
|
: 'bg-muted text-ink-soft hover:bg-brand-50 hover:text-brand-600 focus-visible:ring-brand-500/40', ...(ngDevMode ? [{ debugName: "triggerClass" }] : []));
|
|
21342
|
-
chevronClass = computed(() =>
|
|
21418
|
+
chevronClass = computed(() => this.onBrand() ? 'text-white/70' : 'text-ink-faint', ...(ngDevMode ? [{ debugName: "chevronClass" }] : []));
|
|
21343
21419
|
rowClass(index, selected) {
|
|
21344
21420
|
const base = 'flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-sm font-medium transition-colors';
|
|
21345
21421
|
const highlighted = this.highlightedIndex() === index;
|
|
@@ -21380,7 +21456,7 @@ class SdsLanguageSwitcher {
|
|
|
21380
21456
|
</button>
|
|
21381
21457
|
|
|
21382
21458
|
@if (open()) {
|
|
21383
|
-
<!-- Appended to document.body on open (see
|
|
21459
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel) so no ancestor can clip it. -->
|
|
21384
21460
|
<div
|
|
21385
21461
|
data-sds-language-panel
|
|
21386
21462
|
[id]="panelId"
|
|
@@ -21389,7 +21465,9 @@ class SdsLanguageSwitcher {
|
|
|
21389
21465
|
class="fixed z-[1000] min-w-44 overflow-hidden rounded-card border border-border bg-surface shadow-xl animate-sds-menu"
|
|
21390
21466
|
>
|
|
21391
21467
|
@if (heading(); as h) {
|
|
21392
|
-
<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
|
+
>
|
|
21393
21471
|
{{ h }}
|
|
21394
21472
|
</p>
|
|
21395
21473
|
}
|
|
@@ -21455,7 +21533,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
21455
21533
|
</button>
|
|
21456
21534
|
|
|
21457
21535
|
@if (open()) {
|
|
21458
|
-
<!-- Appended to document.body on open (see
|
|
21536
|
+
<!-- Appended to document.body on open (see SdsOverlayPanel) so no ancestor can clip it. -->
|
|
21459
21537
|
<div
|
|
21460
21538
|
data-sds-language-panel
|
|
21461
21539
|
[id]="panelId"
|
|
@@ -21464,7 +21542,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
21464
21542
|
class="fixed z-[1000] min-w-44 overflow-hidden rounded-card border border-border bg-surface shadow-xl animate-sds-menu"
|
|
21465
21543
|
>
|
|
21466
21544
|
@if (heading(); as h) {
|
|
21467
|
-
<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
|
+
>
|
|
21468
21548
|
{{ h }}
|
|
21469
21549
|
</p>
|
|
21470
21550
|
}
|
|
@@ -21651,5 +21731,5 @@ function provideSdsCharts(config) {
|
|
|
21651
21731
|
* Generated bundle index. Do not edit.
|
|
21652
21732
|
*/
|
|
21653
21733
|
|
|
21654
|
-
export { SDS_ALLERGENS, SDS_ALLERGEN_ICONS, SDS_ALLERGEN_LABELS_EN, SDS_CHART_CONFIG, SDS_CHART_LABELS_EN, SDS_ICONS, SDS_PHONE_COUNTRIES, SDS_THEME_STORAGE_KEY, SDS_UI_PREF_LANG_PARAM, SDS_UI_PREF_THEME_PARAM, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAddressAutocomplete, SdsAddressExtra, SdsAllergenIcon, SdsAutocomplete, SdsAvatar, SdsBadge, SdsBulkBar, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsChecklistRow, SdsChip, SdsChipGroup, SdsDatePicker, SdsDateRangePicker, SdsEnvBanner, SdsFileDrop, SdsFormActions, SdsIcon, SdsImageCropper, SdsInput, SdsInputNumber, SdsLanguageSwitcher, SdsLocalizedInput, SdsMenu, SdsMenuItem, SdsMenuTrigger, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsPaginator, SdsPhoneInput, SdsRadio, SdsRangeCalendar, SdsScore, SdsSearchCommand, SdsSegmented, SdsSelect, SdsSelectEmpty, SdsSelectFooter, SdsSelectOption, SdsSeverity, SdsShell, SdsSidebar, SdsSortableHandle, SdsSortableItem, SdsSortableList, SdsStatusTile, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableEmpty, SdsTableHeader, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsThemeService, SdsThemeToggle, SdsTimeInput, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SdsTriStateChip, SdsUserMenu, SidebarStore, addDays, addMonths, addMonthsKeepDay, appendUiPrefs, diffInDays, formatDateRange, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isSameRange, isSdsThemeMode, isoDay, pad2, provideSdsCharts, readUiPrefs, sdsFileMatchesAccept, sdsFormatBytes, sdsFormatNational, sdsLastDays, sdsLastMonths, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth, stripUiPrefs };
|
|
21734
|
+
export { SDS_ALLERGENS, SDS_ALLERGEN_ICONS, SDS_ALLERGEN_LABELS_EN, SDS_CHART_CONFIG, SDS_CHART_LABELS_EN, SDS_ICONS, SDS_PHONE_COUNTRIES, SDS_THEME_STORAGE_KEY, SDS_UI_PREF_LANG_PARAM, SDS_UI_PREF_THEME_PARAM, SdsAccordion, SdsAccordionContent, SdsAccordionHeader, SdsAccordionPanel, SdsAccordionStore, SdsAddressAutocomplete, SdsAddressExtra, SdsAllergenIcon, SdsAutocomplete, SdsAvatar, SdsBadge, SdsBulkBar, SdsButton, SdsCalendar, SdsCard, SdsCheckbox, SdsChecklistRow, SdsChip, SdsChipGroup, SdsDatePicker, SdsDateRangePicker, SdsEnvBanner, SdsFileDrop, SdsFormActions, SdsIcon, SdsImageCropper, SdsInput, SdsInputNumber, SdsLanguageSwitcher, SdsLocalizedInput, SdsMenu, SdsMenuItem, SdsMenuTrigger, SdsMultiSelect, SdsNavGroup, SdsNavItem, SdsOtpInput, SdsOverlayPanel, SdsPaginator, SdsPhoneInput, SdsRadio, SdsRangeCalendar, SdsScore, SdsSearchCommand, SdsSegmented, SdsSelect, SdsSelectEmpty, SdsSelectFooter, SdsSelectOption, SdsSeverity, SdsShell, SdsSidebar, SdsSortableHandle, SdsSortableItem, SdsSortableList, SdsStatusTile, SdsTab, SdsTabList, SdsTabPanel, SdsTabPanels, SdsTable, SdsTableCell, SdsTableEmpty, SdsTableHeader, SdsTableRowExpansion, SdsTabs, SdsTabsStore, SdsTextarea, SdsThemeService, SdsThemeToggle, SdsTimeInput, SdsToastHost, SdsToastService, SdsToggleSwitch, SdsTopbar, SdsTriStateChip, SdsUserMenu, SidebarStore, addDays, addMonths, addMonthsKeepDay, appendUiPrefs, diffInDays, formatDateRange, formatDdMmYyyy, formatHhMm, isOutOfRange, isSameDay, isSameRange, isSdsThemeMode, isoDay, pad2, placePanel, provideSdsCharts, readUiPrefs, sdsFileMatchesAccept, sdsFormatBytes, sdsFormatNational, sdsLastDays, sdsLastMonths, sdsMatchDialCode, sdsNormalizeSignificant, startOfDay, startOfMonth, stripUiPrefs };
|
|
21655
21735
|
//# sourceMappingURL=besaitech-ng-design-system.mjs.map
|