@ulu/frontend 0.2.0-beta.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.dev.md +36 -13
- package/README.md +3 -1
- package/dist/es/core/events.js +36 -25
- package/dist/es/core/settings.js +33 -22
- package/dist/es/index.js +47 -45
- package/dist/es/ui/dialog.js +57 -46
- package/dist/es/ui/index.d.ts +1 -0
- package/dist/es/ui/modal-builder.js +39 -28
- package/dist/es/ui/overflow-scroller.js +30 -24
- package/dist/es/ui/proxy-click.js +37 -26
- package/dist/es/ui/resizer.js +57 -49
- package/dist/es/ui/slider.d.ts.map +1 -1
- package/dist/es/ui/slider.js +90 -67
- package/dist/es/ui/tab-manager.d.ts +145 -0
- package/dist/es/ui/tab-manager.d.ts.map +1 -0
- package/dist/es/ui/tab-manager.js +155 -0
- package/dist/es/ui/tabs.d.ts +5 -5
- package/dist/es/ui/tabs.d.ts.map +1 -1
- package/dist/es/ui/tabs.js +34 -51
- package/dist/es/ui/theme-toggle.js +80 -69
- package/dist/es/ui/tooltip.js +53 -44
- package/dist/es/utils/floating-ui.js +35 -24
- package/dist/umd/frontend.css +1 -0
- package/dist/umd/ulu-frontend.umd.js +40 -47
- package/lib/js/exports.md +1 -0
- package/lib/js/ui/index.js +4 -0
- package/lib/js/ui/slider.js +3 -3
- package/lib/js/ui/tab-manager.js +324 -0
- package/lib/js/ui/tabs.js +33 -92
- package/lib/scss/_breakpoint.scss +3 -3
- package/lib/scss/_button.scss +3 -3
- package/lib/scss/_color.scss +3 -2
- package/lib/scss/_element.scss +10 -4
- package/lib/scss/_layout.scss +11 -4
- package/lib/scss/_selector.scss +2 -1
- package/lib/scss/_typography.scss +9 -10
- package/lib/scss/_utils.scss +52 -19
- package/lib/scss/base/_elements.scss +1 -1
- package/lib/scss/components/_basic-hero.scss +1 -1
- package/lib/scss/components/_button-verbose.scss +2 -2
- package/lib/scss/components/_callout.scss +3 -4
- package/lib/scss/components/_css-icon.scss +2 -2
- package/lib/scss/components/_data-grid.scss +5 -5
- package/lib/scss/components/_flipcard.scss +3 -2
- package/lib/scss/components/_index.scss +6 -0
- package/lib/scss/components/_panel.scss +1 -1
- package/lib/scss/components/_popover.scss +9 -6
- package/lib/scss/components/_tabs.scss +20 -5
- package/lib/scss/components/_tagged.scss +59 -0
- package/package.json +25 -35
- package/dist/umd/style.css +0 -1
- package/lib/js/ui/dialog.todo +0 -3
package/lib/js/exports.md
CHANGED
package/lib/js/ui/index.js
CHANGED
package/lib/js/ui/slider.js
CHANGED
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
|
|
33
33
|
import { ComponentInitializer } from "../core/component.js";
|
|
34
34
|
import { wrapSettingString } from "../core/settings.js";
|
|
35
|
-
import maintain from 'ally.js/maintain/_maintain';
|
|
36
35
|
import { hasRequiredProps } from '@ulu/utils/object.js';
|
|
37
36
|
import { trimWhitespace } from "@ulu/utils/string.js";
|
|
38
37
|
import { debounce } from "@ulu/utils/performance.js";
|
|
@@ -392,8 +391,9 @@ export class Slider {
|
|
|
392
391
|
}
|
|
393
392
|
|
|
394
393
|
// Make all slide interactive elements inert
|
|
395
|
-
|
|
394
|
+
this.elements.track.inert = true;
|
|
396
395
|
this.transitioning = true;
|
|
396
|
+
|
|
397
397
|
// Set classes first just feels better
|
|
398
398
|
if (old) old.navButton.classList.remove(activeClass);
|
|
399
399
|
slide.navButton.classList.add(activeClass);
|
|
@@ -403,8 +403,8 @@ export class Slider {
|
|
|
403
403
|
this.index = index;
|
|
404
404
|
this.slide = slide;
|
|
405
405
|
this.transitioning = false;
|
|
406
|
+
this.elements.track.inert = false;
|
|
406
407
|
elements.container.classList.remove(transitionClass);
|
|
407
|
-
lockInteractives.disengage();
|
|
408
408
|
if (!isInit) {
|
|
409
409
|
slide.element.focus();
|
|
410
410
|
this.emit("goto", [event, index, slide]);
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ui/tab-manager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ensureId } from "../utils/id.js";
|
|
6
|
+
import { getCoreEventName } from "../core/events.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} TabManagerOptions
|
|
10
|
+
* @property {String|null} [orientation=null] - "horizontal"|"vertical", auto-detected if omitted.
|
|
11
|
+
* @property {Number} [initialIndex=0] - Index to activate on load.
|
|
12
|
+
* @property {Boolean} [allArrows=false] - Allow all arrow keys to navigate regardless of orientation.
|
|
13
|
+
* @property {Boolean} [openByUrlHash=false] - Activate tab based on URL hash on initialization.
|
|
14
|
+
* @property {Boolean} [setUrlHash=false] - Update URL hash when a new tab is activated.
|
|
15
|
+
* @property {Boolean} [equalHeights=false] - Automatically match the height of all panels.
|
|
16
|
+
* @property {Function|null} [onReady=null] - Callback fired after initialization: (instance) => {}
|
|
17
|
+
* @property {Function|null} [onChange=null] - Callback fired when tab changes: (active, previous) => {}
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Class for managing Aria tabs
|
|
22
|
+
* - Designed to be minimal and lightweight but cover all traditional needs
|
|
23
|
+
* - Designed for static / traditional webpages (not SPA)
|
|
24
|
+
* - Separated from tabs.js so it can be used by itself as needed (tree-shaking)
|
|
25
|
+
*/
|
|
26
|
+
export class TabManager {
|
|
27
|
+
/**
|
|
28
|
+
* Default options for TabManager.
|
|
29
|
+
* @type {TabManagerOptions}
|
|
30
|
+
*/
|
|
31
|
+
static defaults = {
|
|
32
|
+
orientation: null,
|
|
33
|
+
initialIndex: 0,
|
|
34
|
+
allArrows: false,
|
|
35
|
+
openByUrlHash: false,
|
|
36
|
+
setUrlHash: false,
|
|
37
|
+
equalHeights: false,
|
|
38
|
+
onReady: null,
|
|
39
|
+
onChange: null
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {HTMLElement} tablistElement - The element with role="tablist"
|
|
44
|
+
* @param {Partial<TabManagerOptions>} [options] - Configuration options.
|
|
45
|
+
*/
|
|
46
|
+
constructor(tablistElement, options = {}) {
|
|
47
|
+
this.tablist = tablistElement;
|
|
48
|
+
this.options = { ...TabManager.defaults, ...options };
|
|
49
|
+
this.tabs = Array.from(this.tablist.children);
|
|
50
|
+
|
|
51
|
+
// Discover panels via `aria-controls`
|
|
52
|
+
this.panels = this.tabs.map(tab => {
|
|
53
|
+
const controlsId = tab.getAttribute('aria-controls');
|
|
54
|
+
return controlsId ? document.getElementById(controlsId) : null;
|
|
55
|
+
}).filter(Boolean); // Ensure no nulls in panels array
|
|
56
|
+
|
|
57
|
+
this.currentIndex = -1;
|
|
58
|
+
|
|
59
|
+
// Bind methods
|
|
60
|
+
this.handleKeydown = this.handleKeydown.bind(this);
|
|
61
|
+
this.handleClick = this.handleClick.bind(this);
|
|
62
|
+
|
|
63
|
+
// Bind the new height update method for the event listener
|
|
64
|
+
this.updatePanelHeights = this.updatePanelHeights.bind(this);
|
|
65
|
+
|
|
66
|
+
if (this.tabs.length === 0 || this.tabs.length !== this.panels.length) {
|
|
67
|
+
console.warn("TabManager: Tab/Panel count mismatch. Check aria-controls.", { tabs: this.tabs, panels: this.panels });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.orientation = this.options.orientation || this.tablist.getAttribute("aria-orientation") || "horizontal";
|
|
72
|
+
|
|
73
|
+
this.setupAttributes();
|
|
74
|
+
this.attachListeners();
|
|
75
|
+
|
|
76
|
+
// Handle initial state from URL hash if configured
|
|
77
|
+
let startingIndex = this.options.initialIndex;
|
|
78
|
+
if (this.options.openByUrlHash) {
|
|
79
|
+
const hash = window.location.hash.substring(1);
|
|
80
|
+
const hashIndex = this.tabs.findIndex(tab => tab.id === hash);
|
|
81
|
+
if (hashIndex > -1) {
|
|
82
|
+
startingIndex = hashIndex;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.activate(startingIndex, false);
|
|
87
|
+
|
|
88
|
+
// Handle equal heights on init and on resize
|
|
89
|
+
if (this.options.equalHeights) {
|
|
90
|
+
this.updatePanelHeights();
|
|
91
|
+
document.addEventListener(getCoreEventName('pageResized'), this.updatePanelHeights);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.options.onReady) {
|
|
95
|
+
this.options.onReady(this);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sets the necessary ARIA attributes and initial states for tabs and panels.
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
setupAttributes() {
|
|
104
|
+
this.tablist.setAttribute("role", "tablist");
|
|
105
|
+
|
|
106
|
+
this.tabs.forEach((tab, index) => {
|
|
107
|
+
const panel = this.panels[index];
|
|
108
|
+
|
|
109
|
+
// Ensure elements have IDs for ARIA attributes
|
|
110
|
+
ensureId(tab);
|
|
111
|
+
ensureId(panel);
|
|
112
|
+
|
|
113
|
+
// Set ARIA Roles & Relationships
|
|
114
|
+
tab.setAttribute("role", "tab");
|
|
115
|
+
// This is now the primary link, but we still ensure it's set.
|
|
116
|
+
if (!tab.hasAttribute('aria-controls')) {
|
|
117
|
+
tab.setAttribute("aria-controls", panel.id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
panel.setAttribute("role", "tabpanel");
|
|
121
|
+
panel.setAttribute("aria-labelledby", tab.id);
|
|
122
|
+
|
|
123
|
+
// Initial hidden state
|
|
124
|
+
panel.hidden = true;
|
|
125
|
+
tab.setAttribute("tabindex", "-1");
|
|
126
|
+
tab.setAttribute("aria-selected", "false");
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Attaches click and keydown event listeners to each tab.
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
attachListeners() {
|
|
135
|
+
this.tabs.forEach(tab => {
|
|
136
|
+
tab.addEventListener("click", this.handleClick);
|
|
137
|
+
tab.addEventListener("keydown", this.handleKeydown);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Handles click events on tabs, activating the corresponding panel.
|
|
143
|
+
* @param {MouseEvent} e - The click event.
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
handleClick(e) {
|
|
147
|
+
const index = this.tabs.indexOf(e.currentTarget);
|
|
148
|
+
this.activate(index);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Handles keyboard navigation (arrows, Home, End) on the tab list.
|
|
153
|
+
* @param {KeyboardEvent} e - The keydown event.
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
handleKeydown(e) {
|
|
157
|
+
const index = this.tabs.indexOf(e.currentTarget);
|
|
158
|
+
let nextIndex = null;
|
|
159
|
+
const isVert = this.orientation === "vertical";
|
|
160
|
+
const allArrows = this.options.allArrows;
|
|
161
|
+
const isRtl = (this.tablist.dir === 'rtl' || document.dir === 'rtl') && this.tablist.dir !== 'ltr';
|
|
162
|
+
|
|
163
|
+
const keyNext = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
164
|
+
const keyPrev = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
165
|
+
|
|
166
|
+
// Vertical movement
|
|
167
|
+
if (e.key === "ArrowDown") {
|
|
168
|
+
if (isVert || allArrows) nextIndex = (index + 1) % this.tabs.length;
|
|
169
|
+
} else if (e.key === "ArrowUp") {
|
|
170
|
+
if (isVert || allArrows) nextIndex = (index - 1 + this.tabs.length) % this.tabs.length;
|
|
171
|
+
// Horizontal movement
|
|
172
|
+
} else if (e.key === keyNext) {
|
|
173
|
+
if (!isVert || allArrows) nextIndex = (index + 1) % this.tabs.length;
|
|
174
|
+
} else if (e.key === keyPrev) {
|
|
175
|
+
if (!isVert || allArrows) nextIndex = (index - 1 + this.tabs.length) % this.tabs.length;
|
|
176
|
+
// Other keys
|
|
177
|
+
} else if (e.key === "Home") {
|
|
178
|
+
nextIndex = 0;
|
|
179
|
+
} else if (e.key === "End") {
|
|
180
|
+
nextIndex = this.tabs.length - 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (nextIndex !== null) {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
this.activate(nextIndex);
|
|
186
|
+
this.tabs[nextIndex].focus();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Activates a tab. Can be called with an index or a tab ID string.
|
|
192
|
+
* @param {Number|String} indexOrId - The index or ID of the tab to activate.
|
|
193
|
+
* @param {Boolean} [triggerActions=true] - If false, will not fire onChange or set URL hash.
|
|
194
|
+
*/
|
|
195
|
+
activate(indexOrId, triggerActions = true) {
|
|
196
|
+
let index = -1;
|
|
197
|
+
if (typeof indexOrId === "string") {
|
|
198
|
+
index = this.tabs.findIndex(tab => tab.id === indexOrId);
|
|
199
|
+
} else {
|
|
200
|
+
index = indexOrId;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (index < 0 || index >= this.tabs.length) return;
|
|
204
|
+
if (this.currentIndex === index) return;
|
|
205
|
+
|
|
206
|
+
const prevIndex = this.currentIndex;
|
|
207
|
+
const prevTab = prevIndex > -1 ? this.tabs[prevIndex] : null;
|
|
208
|
+
const prevPanel = prevIndex > -1 ? this.panels[prevIndex] : null;
|
|
209
|
+
|
|
210
|
+
// Deactivate Current
|
|
211
|
+
if (prevTab) {
|
|
212
|
+
prevTab.setAttribute("aria-selected", "false");
|
|
213
|
+
prevTab.setAttribute("tabindex", "-1");
|
|
214
|
+
prevPanel.hidden = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Activate New
|
|
218
|
+
const tab = this.tabs[index];
|
|
219
|
+
const panel = this.panels[index];
|
|
220
|
+
|
|
221
|
+
tab.setAttribute("aria-selected", "true");
|
|
222
|
+
tab.setAttribute("tabindex", "0");
|
|
223
|
+
panel.hidden = false;
|
|
224
|
+
|
|
225
|
+
this.currentIndex = index;
|
|
226
|
+
|
|
227
|
+
// Update URL hash if configured and not the initial silent activation
|
|
228
|
+
if (triggerActions && this.options.setUrlHash && window.history) {
|
|
229
|
+
window.history.replaceState(null, "", `#${ tab.id }`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Fire onChange callback
|
|
233
|
+
if (triggerActions && this.options.onChange) {
|
|
234
|
+
this.options.onChange(
|
|
235
|
+
{ index, tab, panel },
|
|
236
|
+
{ index: prevIndex, tab: prevTab, panel: prevPanel }
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Public method to activate a tab by its ID.
|
|
243
|
+
* @param {String} id - The ID of the tab element to activate.
|
|
244
|
+
*/
|
|
245
|
+
activateById(id) {
|
|
246
|
+
this.activate(id, true);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Calculates and applies equal heights to all panels.
|
|
251
|
+
* Waits for images within panels to load before calculating.
|
|
252
|
+
*/
|
|
253
|
+
updatePanelHeights() {
|
|
254
|
+
if (!this.panels || this.panels.length === 0) return;
|
|
255
|
+
|
|
256
|
+
const parent = this.panels[0].parentElement;
|
|
257
|
+
if (!parent) return;
|
|
258
|
+
const images = [ ...parent.querySelectorAll("img") ];
|
|
259
|
+
|
|
260
|
+
const imagePromise = (image) => new Promise((resolve) => {
|
|
261
|
+
if (image.complete) return resolve(image);
|
|
262
|
+
image.onload = () => resolve(image);
|
|
263
|
+
image.onerror = () => resolve(image); // Resolve on error so it doesn't block
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const imagePromises = images.map(imagePromise);
|
|
267
|
+
|
|
268
|
+
Promise.all(imagePromises).then(() => {
|
|
269
|
+
// Reset heights to auto before measuring to get natural height
|
|
270
|
+
this.panels.forEach(panel => {
|
|
271
|
+
panel.style.minHeight = '';
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const heights = this.panels.map(panel => {
|
|
275
|
+
const wasHidden = panel.hidden;
|
|
276
|
+
panel.hidden = false;
|
|
277
|
+
const panelHeight = panel.offsetHeight;
|
|
278
|
+
panel.hidden = wasHidden;
|
|
279
|
+
return panelHeight;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const max = Math.max(...heights);
|
|
283
|
+
if (max > 0) {
|
|
284
|
+
this.panels.forEach(panel => {
|
|
285
|
+
panel.style.minHeight = `${ max }px`;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Removes event listeners, cleans up ARIA attributes, and resets the DOM to its pre-initialized state.
|
|
293
|
+
*/
|
|
294
|
+
destroy() {
|
|
295
|
+
this.tabs.forEach(tab => {
|
|
296
|
+
tab.removeEventListener("click", this.handleClick);
|
|
297
|
+
tab.removeEventListener("keydown", this.handleKeydown);
|
|
298
|
+
});
|
|
299
|
+
if (this.options.equalHeights) {
|
|
300
|
+
document.removeEventListener(getCoreEventName('pageResized'), this.updatePanelHeights);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.tablist.removeAttribute("role");
|
|
304
|
+
|
|
305
|
+
this.tabs.forEach(tab => {
|
|
306
|
+
tab.removeAttribute("role");
|
|
307
|
+
tab.removeAttribute("aria-selected");
|
|
308
|
+
tab.removeAttribute("tabindex");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
this.panels.forEach(panel => {
|
|
312
|
+
panel.removeAttribute("role");
|
|
313
|
+
panel.removeAttribute("aria-labelledby");
|
|
314
|
+
panel.hidden = false;
|
|
315
|
+
panel.style.minHeight = '';
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
this.tablist = null;
|
|
319
|
+
this.tabs = [];
|
|
320
|
+
this.panels = [];
|
|
321
|
+
this.options = {};
|
|
322
|
+
this.currentIndex = -1;
|
|
323
|
+
}
|
|
324
|
+
}
|
package/lib/js/ui/tabs.js
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
* @module ui/tabs
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// - For Vertical tabs we should be updating the orientation when on mobile.
|
|
7
|
-
// Currently using all arrows so that the interface works in both
|
|
8
|
-
// orientations when vertical. Leaving that behavior for now but maybe consider
|
|
9
|
-
// setting this up to destroy tab interface when ui layout changes?
|
|
10
|
-
|
|
11
|
-
import AriaTablist from "aria-tablist";
|
|
5
|
+
import { TabManager } from "./tab-manager.js";
|
|
12
6
|
import { ComponentInitializer } from "../core/component.js";
|
|
7
|
+
import { ensureId } from "../utils/id.js";
|
|
13
8
|
|
|
14
9
|
/**
|
|
15
10
|
* Array of current tab instances (exported if you need to interact with them)
|
|
@@ -38,11 +33,8 @@ export function init() {
|
|
|
38
33
|
initialize();
|
|
39
34
|
}
|
|
40
35
|
});
|
|
41
|
-
|
|
42
|
-
// Run this on page load, optionally exported for use when page is running
|
|
43
|
-
instances.forEach(openByCurrentHash);
|
|
44
36
|
};
|
|
45
|
-
|
|
37
|
+
|
|
46
38
|
if (document.readyState === "complete") {
|
|
47
39
|
initial();
|
|
48
40
|
} else {
|
|
@@ -51,97 +43,46 @@ export function init() {
|
|
|
51
43
|
}
|
|
52
44
|
|
|
53
45
|
/**
|
|
54
|
-
*
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
57
|
-
* @return {
|
|
46
|
+
* Setup a new TabManager instance
|
|
47
|
+
* @param {HTMLElement} element Tablist Element
|
|
48
|
+
* @param {object} options Options to set as defaults
|
|
49
|
+
* @return {object} Instance object
|
|
58
50
|
*/
|
|
59
51
|
export function setup(element, options = {}) {
|
|
60
|
-
const config =
|
|
52
|
+
const config = { ...options };
|
|
61
53
|
|
|
54
|
+
// Backwards compatibility, `vertical:true` implies `allArrows:true`
|
|
62
55
|
if (config.vertical) {
|
|
63
56
|
config.allArrows = true;
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
onOpen(...args) {
|
|
71
|
-
args.unshift(instance);
|
|
72
|
-
handleOpen.apply(null, args);
|
|
73
|
-
},
|
|
74
|
-
...config
|
|
75
|
-
});
|
|
76
|
-
instances.push(instance);
|
|
77
|
-
|
|
78
|
-
if (config.equalHeights) {
|
|
79
|
-
setHeights(element);
|
|
59
|
+
// Backwards compatibility, `openByUrlHash:true` implies `setUrlHash:true`
|
|
60
|
+
// to replicate the behavior of the old aria-tablist library.
|
|
61
|
+
if (config.openByUrlHash) {
|
|
62
|
+
config.setUrlHash = true;
|
|
80
63
|
}
|
|
81
|
-
|
|
82
|
-
return instance;
|
|
83
|
-
}
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
65
|
+
// Backwards compatibility, ensure `aria-controls` is present,
|
|
66
|
+
// generating it from the legacy `aria-labelledby` pattern if needed.
|
|
67
|
+
const tabs = [...element.children];
|
|
68
|
+
|
|
69
|
+
tabs.forEach((tab) => {
|
|
70
|
+
if (!tab.hasAttribute('aria-controls')) {
|
|
71
|
+
// Find the panel using the old association method
|
|
72
|
+
const panel = document.querySelector(`[aria-labelledby="${tab.id}"]`);
|
|
73
|
+
if (panel) {
|
|
74
|
+
ensureId(panel);
|
|
75
|
+
tab.setAttribute('aria-controls', panel.id);
|
|
76
|
+
}
|
|
98
77
|
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
78
|
+
});
|
|
101
79
|
|
|
102
|
-
|
|
103
|
-
* Responsible for setting hash on open if option is set
|
|
104
|
-
*/
|
|
105
|
-
function handleOpen({ options }, panel, tab) {
|
|
106
|
-
if (options.openByUrlHash && window.history) {
|
|
107
|
-
window.history.replaceState(null, "", `#${ tab.id }`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
80
|
+
const instance = { element, options };
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const parent = panels[0].parentElement;
|
|
118
|
-
const images = [ ...parent.querySelectorAll("img") ];
|
|
119
|
-
const imagePromises = images.map(image => imagePromise(image));
|
|
120
|
-
function imagePromise(image) {
|
|
121
|
-
return new Promise((resolve) => {
|
|
122
|
-
if (image.complete) {
|
|
123
|
-
resolve(image);
|
|
124
|
-
} else {
|
|
125
|
-
image.onload = resolve;
|
|
126
|
-
// Errors should also resolve so that height matching continues
|
|
127
|
-
image.onerror = resolve;
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
// Run after images are loaded, or if no images it will resolve and run
|
|
132
|
-
Promise.all(imagePromises).then(() => {
|
|
133
|
-
const heights = panels.map(panel => {
|
|
134
|
-
let panelHeight = panel.offsetHeight;
|
|
135
|
-
if (panel.hidden) {
|
|
136
|
-
panel.hidden = false;
|
|
137
|
-
panelHeight = panel.offsetHeight;
|
|
138
|
-
// This explicity needs "hidden" for aria-tablist (it checks this string value)
|
|
139
|
-
// Will break the initial window push state when using openWithUrlHash
|
|
140
|
-
panel.setAttribute("hidden", "hidden");
|
|
141
|
-
}
|
|
142
|
-
return panelHeight;
|
|
143
|
-
});
|
|
144
|
-
const max = Math.max(...heights);
|
|
145
|
-
panels.forEach(panel => panel.style.minHeight = `${ max }px`);
|
|
146
|
-
});
|
|
82
|
+
// Instantiate the new TabManager. It will find its own tabs/panels
|
|
83
|
+
// and handle all URL hash and equal heights logic internally now
|
|
84
|
+
instance.tabManager = new TabManager(element, config);
|
|
85
|
+
instances.push(instance);
|
|
86
|
+
|
|
87
|
+
return instance;
|
|
147
88
|
}
|
|
@@ -116,13 +116,13 @@ $sizes: (
|
|
|
116
116
|
/// @return {Boolean}
|
|
117
117
|
/// @example scss {compile} Example usage
|
|
118
118
|
/// .test-exists {
|
|
119
|
-
/// @if(ulu.breakpoint-exists("medium")) {
|
|
119
|
+
/// @if (ulu.breakpoint-exists("medium")) {
|
|
120
120
|
/// @include ulu.breakpoint-min("medium") {
|
|
121
121
|
/// padding: 2rem;
|
|
122
122
|
/// }
|
|
123
123
|
/// }
|
|
124
124
|
/// // The below content doesn't print because the size doesn't exist.
|
|
125
|
-
/// @if(ulu.breakpoint-exists("too-large")) {
|
|
125
|
+
/// @if (ulu.breakpoint-exists("too-large")) {
|
|
126
126
|
/// @include ulu.breakpoint-min("too-large") {
|
|
127
127
|
/// padding: 20000rem;
|
|
128
128
|
/// }
|
|
@@ -131,7 +131,7 @@ $sizes: (
|
|
|
131
131
|
|
|
132
132
|
@function exists($name) {
|
|
133
133
|
$size: map.get($sizes, $name);
|
|
134
|
-
@return
|
|
134
|
+
@return utils.when($size != null, true, false);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/// Create a media query that matches the min-width for a given size
|
package/lib/scss/_button.scss
CHANGED
|
@@ -296,15 +296,15 @@ $styles: (
|
|
|
296
296
|
// If a specific state [hover, active] grab that map
|
|
297
297
|
@if ($state) {
|
|
298
298
|
$state-style: map.get($style, $state);
|
|
299
|
-
$state-style:
|
|
299
|
+
$state-style: utils.when($state-style, $state-style, ());
|
|
300
300
|
}
|
|
301
301
|
// From is the map to grab styles from
|
|
302
|
-
$from:
|
|
302
|
+
$from: utils.when($state, $state-style, $style);
|
|
303
303
|
$value: map.get($from, $prop);
|
|
304
304
|
|
|
305
305
|
// Fallback to parent (if hover)
|
|
306
306
|
@if ($state == "hover") {
|
|
307
|
-
$value:
|
|
307
|
+
$value: utils.when($value, $value, map.get($style, $prop));
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
@if (meta.type-of($value) == "string" and string.index($prop, "color")) {
|
package/lib/scss/_color.scss
CHANGED
|
@@ -20,7 +20,7 @@ $palette: (
|
|
|
20
20
|
"white": white,
|
|
21
21
|
"type": black,
|
|
22
22
|
"type-secondary": rgb(82, 82, 82),
|
|
23
|
-
"type-tertiary": rgb(
|
|
23
|
+
"type-tertiary": rgb(110, 110, 110),
|
|
24
24
|
"type-disabled": rgb(160, 160, 160),
|
|
25
25
|
"headline": inherit,
|
|
26
26
|
"background": white,
|
|
@@ -38,6 +38,7 @@ $palette: (
|
|
|
38
38
|
"placeholder-background": #e2e2e2,
|
|
39
39
|
"placeholder-background-alt": #bababa,
|
|
40
40
|
"selected": green,
|
|
41
|
+
"selected-background": rgb(184, 202, 184),
|
|
41
42
|
"box-shadow": rgba(0, 0, 0, 0.349),
|
|
42
43
|
"box-shadow-hover": rgba(0, 0, 0, 0.5),
|
|
43
44
|
"rule": gray,
|
|
@@ -133,7 +134,7 @@ $color-classes: (
|
|
|
133
134
|
|
|
134
135
|
@function exists($name) {
|
|
135
136
|
$color: map.get($palette, $name);
|
|
136
|
-
@return
|
|
137
|
+
@return utils.when($color != null, true, false);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
/// Set color contexts
|
package/lib/scss/_element.scss
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
@use "sass:map";
|
|
6
6
|
@use "sass:meta";
|
|
7
|
+
|
|
7
8
|
@use "color";
|
|
8
9
|
@use "utils";
|
|
9
10
|
|
|
@@ -175,7 +176,12 @@ $rule-margins: (
|
|
|
175
176
|
/// @param {String} $name ["default"] name of rule style
|
|
176
177
|
|
|
177
178
|
@mixin rule-margin($name: null) {
|
|
178
|
-
$margin:
|
|
179
|
+
$margin: null;
|
|
180
|
+
@if ($name) {
|
|
181
|
+
$margin: get-rule-margin($name);
|
|
182
|
+
} @else {
|
|
183
|
+
$margin: get("margin");
|
|
184
|
+
}
|
|
179
185
|
margin-top: $margin;
|
|
180
186
|
margin-bottom: $margin;
|
|
181
187
|
}
|
|
@@ -337,7 +343,7 @@ $rule-margins: (
|
|
|
337
343
|
"padding-adjust" : null,
|
|
338
344
|
);
|
|
339
345
|
$config: map.merge($defaults, $options);
|
|
340
|
-
$element:
|
|
346
|
+
$element: utils.when($before, "::before", "::after");
|
|
341
347
|
|
|
342
348
|
&#{ $element } {
|
|
343
349
|
content: "";
|
|
@@ -362,14 +368,14 @@ $rule-margins: (
|
|
|
362
368
|
$options: (),
|
|
363
369
|
$before: true
|
|
364
370
|
) {
|
|
365
|
-
$element:
|
|
371
|
+
$element: utils.when($before, "::before", "::after");
|
|
366
372
|
$size: map.get($options, "size");
|
|
367
373
|
$offset: map.get($options, "offset");
|
|
368
374
|
$border-radius: map.get($options, "border-radius");
|
|
369
375
|
$padding-adjust: map.get($options, "padding-adjust");
|
|
370
376
|
|
|
371
377
|
$end: $side == "top" or $side == "bottom";
|
|
372
|
-
$position:
|
|
378
|
+
$position: utils.when($offset, 0 - $offset, null);
|
|
373
379
|
|
|
374
380
|
@if ($padding-adjust and $size) {
|
|
375
381
|
padding-#{ $side }: calc($padding-adjust + $size);
|