kmaterialize 2.3.3-1.0.2 → 2.3.3-1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/components/air-datepicker/_air-datepicker.scss +137 -0
- package/components/air-datepicker/air-datepicker.ts +192 -0
- package/components/alert/_alerts.scss +130 -0
- package/components/alert/alert.ts +73 -0
- package/components/appbar/appbar.css +301 -4
- package/components/button/_buttons.scss +39 -1
- package/components/card/_cards.scss +9 -1
- package/components/checkbox/_checkboxes.scss +120 -229
- package/components/collapsible/_collapsible.scss +50 -0
- package/components/color-input/_color-input.scss +124 -0
- package/components/color-input/color-input.ts +203 -0
- package/components/dropdown/dropdown.ts +1 -1
- package/components/file-input/_file-input-enhanced.scss +61 -0
- package/components/file-input/file-input.ts +203 -0
- package/components/kanban/_kanban.scss +148 -0
- package/components/kanban/kanban.ts +133 -0
- package/components/list/_collection.scss +10 -2
- package/components/number-input/number-input.ts +103 -0
- package/components/password-input/password-input.ts +72 -0
- package/components/slider/_range.scss +5 -1
- package/components/switch/_switches.scss +5 -0
- package/components/textfield/_input-fields.scss +4 -4
- package/components/textfield/_select.scss +54 -0
- package/components/tom-select/_tom-select-field.scss +256 -0
- package/components/tom-select/tom-select-field.ts +175 -0
- package/components/toolbar/_toolbar.scss +324 -0
- package/components/toolbar/toolbar.ts +152 -0
- package/dist/css/materialize.colors.min.css +2 -2
- package/dist/css/materialize.css +10575 -711
- package/dist/css/materialize.min.css +2 -2
- package/dist/css/materialize.min.css.map +1 -1
- package/dist/js/materialize.cjs.js +1136 -45
- package/dist/js/materialize.d.ts +227 -2
- package/dist/js/materialize.js +1136 -45
- package/dist/js/materialize.min.js +2 -2
- package/dist/js/materialize.mjs +1128 -46
- package/package.json +45 -1
- package/sass/_colors.scss +191 -5
- package/sass/_global.scss +240 -1
- package/sass/materialize.scss +14 -1
- package/sass/mixins.module.scss +6 -6
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
@use '../../sass/variables' as *;
|
|
2
|
+
|
|
3
|
+
// @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/partial/_planning_toolbar.scss
|
|
4
|
+
// A rounded "pill" action bar: a full-width flex row split into groups
|
|
5
|
+
// (buttons, a segmented "track" control, an expandable search field) laid
|
|
6
|
+
// out with space-between, sitting inline above the content it controls -
|
|
7
|
+
// not fixed/sticky, just a normal flex row like the source component.
|
|
8
|
+
//
|
|
9
|
+
// :not(.fixed-action-btn) - "toolbar" is already a FAB display mode
|
|
10
|
+
// (.fixed-action-btn.toolbar, see components/button/_buttons.scss); this
|
|
11
|
+
// guards against also painting pill styles onto that unrelated element.
|
|
12
|
+
.toolbar:not(.fixed-action-btn) {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
width: 100%;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
padding: 5px 5px;
|
|
19
|
+
margin: 0.5rem 0 1rem 0;
|
|
20
|
+
border-radius: 20px;
|
|
21
|
+
background-color: var(--md-sys-color-surface-variant);
|
|
22
|
+
|
|
23
|
+
// Default overflow behavior: more groups than fit on one line stay on
|
|
24
|
+
// that one line and become horizontally scrollable, rather than
|
|
25
|
+
// squeezing/wrapping - a plain, unobtrusive scroll, no visible
|
|
26
|
+
// scrollbar chrome by default. This is a real native overflow (not a
|
|
27
|
+
// faked one), so every native way of scrolling it already works with
|
|
28
|
+
// no extra JS: trackpad/touch swipe, shift+wheel, and middle-click
|
|
29
|
+
// drag-to-pan (Chrome/Firefox/Edge on Windows) all just work.
|
|
30
|
+
overflow-x: auto;
|
|
31
|
+
overflow-y: hidden;
|
|
32
|
+
overscroll-behavior-x: contain;
|
|
33
|
+
scrollbar-width: none;
|
|
34
|
+
-ms-overflow-style: none;
|
|
35
|
+
&::-webkit-scrollbar {
|
|
36
|
+
display: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Groups (and their contents) keep their natural size instead of being
|
|
40
|
+
// squashed to fit - that's what makes the row scroll instead of
|
|
41
|
+
// cramming everything in.
|
|
42
|
+
> .toolbar-group {
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Opt-in alternative to scrolling: wrap onto as many lines as needed
|
|
47
|
+
// instead. `overflow-x` reverts to visible since there's nothing left
|
|
48
|
+
// to scroll horizontally once wrapped, and the row gap matches the
|
|
49
|
+
// column gap above so wrapped lines aren't crammed together.
|
|
50
|
+
//
|
|
51
|
+
// flex-wrap only rearranges its own direct children - a toolbar
|
|
52
|
+
// usually has just one or two big .toolbar-group children, each
|
|
53
|
+
// holding many buttons, so the group itself has to be told to wrap
|
|
54
|
+
// too or nothing visibly changes.
|
|
55
|
+
&.toolbar-wrap {
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
height: auto;
|
|
58
|
+
overflow-x: visible;
|
|
59
|
+
overflow-y: visible;
|
|
60
|
+
row-gap: 8px;
|
|
61
|
+
|
|
62
|
+
> .toolbar-group {
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
row-gap: 8px;
|
|
65
|
+
// Undo the scroll-mode flex-shrink: 0 (see above) and let the
|
|
66
|
+
// group shrink to fit .toolbar's own width - flex-wrap only
|
|
67
|
+
// triggers once a flex container is actually constrained to less
|
|
68
|
+
// than its content's natural width; without this the group just
|
|
69
|
+
// grows past .toolbar's max-width instead of wrapping inside it.
|
|
70
|
+
flex-shrink: 1;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// A cluster of controls inside the toolbar (buttons, a track, a search
|
|
77
|
+
// field, ...) kept together and spaced from the next group.
|
|
78
|
+
.toolbar-group {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 5px;
|
|
82
|
+
flex-shrink: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Separator
|
|
86
|
+
///////////////////////////////////////////////////////
|
|
87
|
+
///
|
|
88
|
+
|
|
89
|
+
// A thin vertical divider between groups - for when the gap between
|
|
90
|
+
// .toolbar-group elements isn't enough and you want an explicit "these
|
|
91
|
+
// are unrelated" line. Just drop it between two groups as a sibling.
|
|
92
|
+
.toolbar-separator {
|
|
93
|
+
align-self: stretch;
|
|
94
|
+
width: 1px;
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
margin: 4px 2px;
|
|
97
|
+
// Not --md-sys-color-outline-variant: it's the exact same color as
|
|
98
|
+
// --md-sys-color-surface-variant (the toolbar's own background) in the
|
|
99
|
+
// dark theme, making the separator invisible. on-surface-variant is
|
|
100
|
+
// guaranteed to contrast against surface-variant by design (that's
|
|
101
|
+
// what the on-*/* pairing is for) - mixed in low instead of used
|
|
102
|
+
// solid, for a subtle line rather than a harsh one.
|
|
103
|
+
background-color: color-mix(in srgb, transparent, var(--md-sys-color-on-surface-variant) 32%);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Freeze (pinned group)
|
|
107
|
+
///////////////////////////////////////////////////////
|
|
108
|
+
///
|
|
109
|
+
|
|
110
|
+
// Keeps a group in place at the left/right edge of a scrolling toolbar
|
|
111
|
+
// (see Overflow) while the rest scrolls underneath it - e.g. a "refresh"
|
|
112
|
+
// or "close" button that should always stay reachable. Needs its own
|
|
113
|
+
// opaque background to actually cover the content scrolling behind it,
|
|
114
|
+
// a matching end radius so it doesn't show a square corner poking past
|
|
115
|
+
// the pill's own rounded edge, and a small shadow on the scrolling side
|
|
116
|
+
// so it doesn't look like content just abruptly stops.
|
|
117
|
+
.toolbar-freeze-left,
|
|
118
|
+
.toolbar-freeze-right {
|
|
119
|
+
position: sticky;
|
|
120
|
+
z-index: 1;
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
background-color: var(--md-sys-color-surface-variant);
|
|
123
|
+
}
|
|
124
|
+
.toolbar-freeze-left {
|
|
125
|
+
left: 0;
|
|
126
|
+
padding-right: 8px;
|
|
127
|
+
border-radius: 20px 0 0 20px;
|
|
128
|
+
box-shadow: 6px 0 6px -4px rgba(0, 0, 0, 0.25);
|
|
129
|
+
}
|
|
130
|
+
.toolbar-freeze-right {
|
|
131
|
+
right: 0;
|
|
132
|
+
padding-left: 8px;
|
|
133
|
+
border-radius: 0 20px 20px 0;
|
|
134
|
+
box-shadow: -6px 0 6px -4px rgba(0, 0, 0, 0.25);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Track (segmented control)
|
|
138
|
+
///////////////////////////////////////////////////////
|
|
139
|
+
///
|
|
140
|
+
|
|
141
|
+
// A pill-shaped segmented control - e.g. "Today / Previous / Next" or a
|
|
142
|
+
// "Week / Month / Year" range picker - with a sliding highlight behind
|
|
143
|
+
// whichever item is active.
|
|
144
|
+
.toolbar-track {
|
|
145
|
+
position: relative;
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
height: 34px;
|
|
149
|
+
padding: 3px;
|
|
150
|
+
border-radius: 999px;
|
|
151
|
+
background-color: var(--md-sys-color-surface);
|
|
152
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// A track with no background/border of its own - just for spacing a group
|
|
156
|
+
// of items that don't need the pill-within-a-pill look.
|
|
157
|
+
.toolbar-track.toolbar-track-bare {
|
|
158
|
+
padding: 3px 0;
|
|
159
|
+
background-color: transparent;
|
|
160
|
+
border-color: transparent;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Positioned/sized by Toolbar's JS (offsetLeft/offsetWidth of the active
|
|
164
|
+
// .toolbar-track-item), not by CSS - the transition below is what makes
|
|
165
|
+
// that JS-driven move/resize animate instead of jumping.
|
|
166
|
+
.toolbar-track-indicator {
|
|
167
|
+
position: absolute;
|
|
168
|
+
top: 3px;
|
|
169
|
+
bottom: 3px;
|
|
170
|
+
left: 0;
|
|
171
|
+
z-index: 0;
|
|
172
|
+
width: 0;
|
|
173
|
+
border-radius: 999px;
|
|
174
|
+
background-color: var(--md-sys-color-primary);
|
|
175
|
+
opacity: 0;
|
|
176
|
+
pointer-events: none;
|
|
177
|
+
transition: transform .22s cubic-bezier(.2, .7, .3, 1), width .22s cubic-bezier(.2, .7, .3, 1), opacity .14s ease;
|
|
178
|
+
|
|
179
|
+
// Only fade in once Toolbar's JS has measured and placed it for the
|
|
180
|
+
// first time - otherwise it flashes at 0 width in the top-left corner
|
|
181
|
+
// for a frame before the first real position is set.
|
|
182
|
+
.toolbar-track.is-armed & {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.toolbar-track-item {
|
|
188
|
+
position: relative;
|
|
189
|
+
z-index: 1;
|
|
190
|
+
display: inline-flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
height: 28px;
|
|
193
|
+
padding: 0 13px;
|
|
194
|
+
border: 0;
|
|
195
|
+
border-radius: 999px;
|
|
196
|
+
background-color: transparent;
|
|
197
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
198
|
+
font-size: 12.5px;
|
|
199
|
+
line-height: 1;
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
transition: color .16s ease;
|
|
203
|
+
|
|
204
|
+
&:hover {
|
|
205
|
+
color: var(--md-sys-color-on-surface);
|
|
206
|
+
}
|
|
207
|
+
&.is-active {
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
color: var(--md-sys-color-on-primary);
|
|
210
|
+
}
|
|
211
|
+
&:focus-visible {
|
|
212
|
+
outline: 2px solid var(--md-sys-color-primary);
|
|
213
|
+
outline-offset: 2px;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// Slider
|
|
218
|
+
///////////////////////////////////////////////////////
|
|
219
|
+
///
|
|
220
|
+
|
|
221
|
+
// A pill container for a range-field, matching .toolbar-track's look -
|
|
222
|
+
// also fixes two things a bare range-field has inside a .toolbar: the
|
|
223
|
+
// native track line is var(--md-sys-color-surface-variant), the exact
|
|
224
|
+
// same color as .toolbar's own background, so it's invisible without a
|
|
225
|
+
// container giving it something else to contrast against; and this also
|
|
226
|
+
// vertically centers any label text/icons placed next to it.
|
|
227
|
+
.toolbar-slider {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
height: 34px;
|
|
232
|
+
padding: 0 13px;
|
|
233
|
+
border-radius: 999px;
|
|
234
|
+
background-color: var(--md-sys-color-surface);
|
|
235
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
236
|
+
|
|
237
|
+
.range-field {
|
|
238
|
+
width: 120px;
|
|
239
|
+
margin-top: 18px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Material icons default to 24px, which crowds a 34px-tall pill and
|
|
243
|
+
// reads a lot heavier than adjacent 13-14px text labels - match the
|
|
244
|
+
// track item's scale instead.
|
|
245
|
+
i.material-icons {
|
|
246
|
+
font-size: 18px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
span {
|
|
250
|
+
line-height: 1;
|
|
251
|
+
// Measured against the actual track/thumb center: a "-"/"+" glyph's
|
|
252
|
+
// ink sits a few px below the middle of its own line box in this
|
|
253
|
+
// font, even though the box itself is perfectly flex-centered - nudge
|
|
254
|
+
// the glyph up to compensate.
|
|
255
|
+
position: relative;
|
|
256
|
+
top: -3px;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Expandable search
|
|
261
|
+
///////////////////////////////////////////////////////
|
|
262
|
+
///
|
|
263
|
+
|
|
264
|
+
// A search field that starts as just an icon button and expands into a
|
|
265
|
+
// full text input once focused (or once it has a value) - Toolbar's JS
|
|
266
|
+
// toggles the .is-expanded class.
|
|
267
|
+
.toolbar-search {
|
|
268
|
+
display: inline-flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
width: 34px;
|
|
271
|
+
height: 34px;
|
|
272
|
+
padding: 2px;
|
|
273
|
+
overflow: hidden;
|
|
274
|
+
border-radius: 999px;
|
|
275
|
+
background-color: var(--md-sys-color-surface);
|
|
276
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
277
|
+
transition: width .2s cubic-bezier(.2, .7, .3, 1);
|
|
278
|
+
|
|
279
|
+
&.is-expanded {
|
|
280
|
+
width: 190px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.toolbar-search-toggle {
|
|
284
|
+
display: inline-flex;
|
|
285
|
+
flex: 0 0 28px;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
width: 28px;
|
|
289
|
+
height: 28px;
|
|
290
|
+
padding: 0;
|
|
291
|
+
border: 0;
|
|
292
|
+
border-radius: 50%;
|
|
293
|
+
background-color: transparent;
|
|
294
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.toolbar-search-input {
|
|
299
|
+
width: 0;
|
|
300
|
+
min-width: 0;
|
|
301
|
+
height: 28px;
|
|
302
|
+
margin: 0;
|
|
303
|
+
padding: 0;
|
|
304
|
+
border: 0;
|
|
305
|
+
outline: 0;
|
|
306
|
+
background-color: transparent;
|
|
307
|
+
color: var(--md-sys-color-on-surface);
|
|
308
|
+
font-size: 12px;
|
|
309
|
+
opacity: 0;
|
|
310
|
+
transition: opacity .12s ease;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
&.is-expanded .toolbar-search-input {
|
|
314
|
+
width: 150px;
|
|
315
|
+
padding-right: 5px;
|
|
316
|
+
opacity: 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@media (prefers-reduced-motion: reduce) {
|
|
321
|
+
.toolbar-track-indicator {
|
|
322
|
+
transition: opacity .14s ease;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
2
|
+
|
|
3
|
+
export interface ToolbarOptions extends BaseOptions {}
|
|
4
|
+
|
|
5
|
+
const _defaults: ToolbarOptions = {};
|
|
6
|
+
|
|
7
|
+
// @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Environment/Partials/PlanningToolbar.ts
|
|
8
|
+
// Generic version of that file's track-indicator sliding and expandable
|
|
9
|
+
// search behavior, decoupled from the page-specific filter/state-binding
|
|
10
|
+
// system (UtilityFilter) the source used - here it's just plain DOM state.
|
|
11
|
+
export class Toolbar extends Component<ToolbarOptions> {
|
|
12
|
+
_tracks: HTMLElement[];
|
|
13
|
+
|
|
14
|
+
constructor(el: HTMLElement, options: Partial<ToolbarOptions>) {
|
|
15
|
+
super(el, options, Toolbar);
|
|
16
|
+
(this.el as any).M_Toolbar = this;
|
|
17
|
+
|
|
18
|
+
this.options = {
|
|
19
|
+
...Toolbar.defaults,
|
|
20
|
+
...options
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this._tracks = Array.from(this.el.querySelectorAll<HTMLElement>('.toolbar-track'));
|
|
24
|
+
this._setupEventHandlers();
|
|
25
|
+
this._tracks.forEach((track) => this._moveIndicator(track, false));
|
|
26
|
+
|
|
27
|
+
// A custom/web font can still be loading at construction time; its
|
|
28
|
+
// metrics landing after this first measurement would leave the
|
|
29
|
+
// indicator very slightly offset from the (now differently-sized)
|
|
30
|
+
// text it's supposed to sit behind. Re-measure once fonts are ready.
|
|
31
|
+
if (typeof document !== 'undefined' && document.fonts) {
|
|
32
|
+
document.fonts.ready.then(() => {
|
|
33
|
+
this._tracks.forEach((track) => this._moveIndicator(track, false));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static get defaults(): ToolbarOptions {
|
|
39
|
+
return _defaults;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static init(el: HTMLElement, options?: Partial<ToolbarOptions>): Toolbar;
|
|
43
|
+
static init(els: InitElements<MElement>, options?: Partial<ToolbarOptions>): Toolbar[];
|
|
44
|
+
static init(
|
|
45
|
+
els: HTMLElement | InitElements<MElement>,
|
|
46
|
+
options: Partial<ToolbarOptions> = {}
|
|
47
|
+
): Toolbar | Toolbar[] {
|
|
48
|
+
return super.init(els, options, Toolbar);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static getInstance(el: HTMLElement): Toolbar {
|
|
52
|
+
return (el as any).M_Toolbar;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
destroy() {
|
|
56
|
+
this._removeEventHandlers();
|
|
57
|
+
(this.el as any).M_Toolbar = undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Re-measure and reposition every track's sliding indicator - call this
|
|
62
|
+
* after changing which .toolbar-track-item is active from your own code,
|
|
63
|
+
* or after anything that could have changed the toolbar's layout/width.
|
|
64
|
+
*/
|
|
65
|
+
updateIndicators() {
|
|
66
|
+
this._tracks.forEach((track) => this._moveIndicator(track, true));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_setupEventHandlers() {
|
|
70
|
+
this.el.addEventListener('click', this._handleTrackClick);
|
|
71
|
+
this.el.addEventListener('focusin', this._handleSearchFocusIn);
|
|
72
|
+
this.el.addEventListener('focusout', this._handleSearchFocusOut);
|
|
73
|
+
this.el.addEventListener('input', this._handleSearchInput);
|
|
74
|
+
this.el.addEventListener('keydown', this._handleSearchKeydown);
|
|
75
|
+
window.addEventListener('resize', this._handleWindowResize);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_removeEventHandlers() {
|
|
79
|
+
this.el.removeEventListener('click', this._handleTrackClick);
|
|
80
|
+
this.el.removeEventListener('focusin', this._handleSearchFocusIn);
|
|
81
|
+
this.el.removeEventListener('focusout', this._handleSearchFocusOut);
|
|
82
|
+
this.el.removeEventListener('input', this._handleSearchInput);
|
|
83
|
+
this.el.removeEventListener('keydown', this._handleSearchKeydown);
|
|
84
|
+
window.removeEventListener('resize', this._handleWindowResize);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_handleTrackClick = (e: MouseEvent) => {
|
|
88
|
+
const item = (e.target as HTMLElement).closest('.toolbar-track-item') as HTMLElement;
|
|
89
|
+
if (!item) return;
|
|
90
|
+
const track = item.closest('.toolbar-track') as HTMLElement;
|
|
91
|
+
if (!track || !this._tracks.includes(track)) return;
|
|
92
|
+
|
|
93
|
+
track.querySelectorAll('.toolbar-track-item.is-active').forEach((el) => el.classList.remove('is-active'));
|
|
94
|
+
item.classList.add('is-active');
|
|
95
|
+
this._moveIndicator(track, true);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
_handleSearchFocusIn = (e: FocusEvent) => {
|
|
99
|
+
const search = (e.target as HTMLElement).closest('.toolbar-search');
|
|
100
|
+
if (search) search.classList.add('is-expanded');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
_handleSearchFocusOut = (e: FocusEvent) => {
|
|
104
|
+
const search = (e.target as HTMLElement).closest('.toolbar-search');
|
|
105
|
+
if (!search) return;
|
|
106
|
+
const input = search.querySelector<HTMLInputElement>('.toolbar-search-input');
|
|
107
|
+
if (input && input.value === '') search.classList.remove('is-expanded');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
_handleSearchInput = (e: Event) => {
|
|
111
|
+
const input = e.target as HTMLInputElement;
|
|
112
|
+
if (!input.classList.contains('toolbar-search-input')) return;
|
|
113
|
+
const search = input.closest('.toolbar-search');
|
|
114
|
+
if (search) search.classList.toggle('is-expanded', input.value !== '' || document.activeElement === input);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
_handleSearchKeydown = (e: KeyboardEvent) => {
|
|
118
|
+
if (e.key !== 'Escape') return;
|
|
119
|
+
const input = (e.target as HTMLElement).closest('.toolbar-search')?.querySelector<HTMLInputElement>('.toolbar-search-input');
|
|
120
|
+
if (!input) return;
|
|
121
|
+
input.value = '';
|
|
122
|
+
input.blur();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
_handleWindowResize = () => {
|
|
126
|
+
this._tracks.forEach((track) => this._moveIndicator(track, false));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
_moveIndicator(track: HTMLElement, animate: boolean) {
|
|
130
|
+
const indicator = track.querySelector<HTMLElement>('.toolbar-track-indicator');
|
|
131
|
+
const active = track.querySelector<HTMLElement>('.toolbar-track-item.is-active');
|
|
132
|
+
if (!indicator || !active) return;
|
|
133
|
+
|
|
134
|
+
if (!animate) indicator.style.transition = 'none';
|
|
135
|
+
// No "- track.clientLeft" here: offsetLeft is already relative to the
|
|
136
|
+
// offsetParent's padding edge, same origin as the indicator's own
|
|
137
|
+
// `left: 0` (an absolutely positioned element's containing block is
|
|
138
|
+
// its ancestor's padding box) - subtracting the border width again
|
|
139
|
+
// was double-counting it, nudging the indicator a px too far left.
|
|
140
|
+
indicator.style.transform = `translateX(${active.offsetLeft}px)`;
|
|
141
|
+
indicator.style.width = `${active.offsetWidth}px`;
|
|
142
|
+
track.classList.add('is-armed');
|
|
143
|
+
if (!animate) {
|
|
144
|
+
// Force layout so the transition-less move above actually applies
|
|
145
|
+
// before re-enabling transitions on the next frame.
|
|
146
|
+
void indicator.offsetWidth;
|
|
147
|
+
requestAnimationFrame(() => {
|
|
148
|
+
indicator.style.transition = '';
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|