kmaterialize 2.3.3-1.0.11 → 2.3.3-1.0.13

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.
@@ -128,7 +128,13 @@
128
128
  }
129
129
 
130
130
  &:focus {
131
- background-color: $button-floating-background-focus;
131
+ // Was $button-floating-background-focus (--md-ref-palette-secondary80,
132
+ // a raw MD3 secondary tone) - it doesn't track a custom theme's
133
+ // primary/container colors the way :hover's color-mix above does, so
134
+ // it could show as a clashing color (e.g. blue) on a themed palette.
135
+ // Same formula as :hover, just mixed a bit stronger for the stronger
136
+ // state.
137
+ background-color: color-mix(in srgb, var(--md-sys-color-primary-container), var(--md-sys-color-on-primary-container) 24%);
132
138
  @extend .z-depth-1-half;
133
139
  }
134
140
 
@@ -114,7 +114,11 @@ input[type=range]::-moz-focus-inner {
114
114
 
115
115
  input[type=range]::-moz-range-thumb {
116
116
  @include range-thumb;
117
- margin-top: -5px;
117
+ // No margin-top here, unlike the -webkit-slider-thumb rule above:
118
+ // Firefox already vertically centers ::-moz-range-thumb on the track
119
+ // by default. Copying webkit's -5px correction (needed there because
120
+ // Chrome/Safari top-align the thumb by default) over-corrects in
121
+ // Firefox, pushing an already-centered thumb noticeably too high.
118
122
  }
119
123
 
120
124
  // hide the outline behind the border
@@ -0,0 +1,219 @@
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 8px;
19
+ margin: $element-top-margin 0 $element-bottom-margin 0;
20
+ border-radius: 27px;
21
+ background-color: var(--md-sys-color-surface-variant);
22
+ }
23
+
24
+ // A cluster of controls inside the toolbar (buttons, a track, a search
25
+ // field, ...) kept together and spaced from the next group.
26
+ .toolbar-group {
27
+ display: flex;
28
+ align-items: center;
29
+ gap: 5px;
30
+ }
31
+
32
+ /// Track (segmented control)
33
+ ///////////////////////////////////////////////////////
34
+ ///
35
+
36
+ // A pill-shaped segmented control - e.g. "Today / Previous / Next" or a
37
+ // "Week / Month / Year" range picker - with a sliding highlight behind
38
+ // whichever item is active.
39
+ .toolbar-track {
40
+ position: relative;
41
+ display: inline-flex;
42
+ align-items: center;
43
+ height: 34px;
44
+ padding: 3px;
45
+ border-radius: 999px;
46
+ background-color: var(--md-sys-color-surface);
47
+ border: 1px solid var(--md-sys-color-outline-variant);
48
+ }
49
+
50
+ // A track with no background/border of its own - just for spacing a group
51
+ // of items that don't need the pill-within-a-pill look.
52
+ .toolbar-track.toolbar-track-bare {
53
+ padding: 3px 0;
54
+ background-color: transparent;
55
+ border-color: transparent;
56
+ }
57
+
58
+ // Positioned/sized by Toolbar's JS (offsetLeft/offsetWidth of the active
59
+ // .toolbar-track-item), not by CSS - the transition below is what makes
60
+ // that JS-driven move/resize animate instead of jumping.
61
+ .toolbar-track-indicator {
62
+ position: absolute;
63
+ top: 3px;
64
+ bottom: 3px;
65
+ left: 0;
66
+ z-index: 0;
67
+ width: 0;
68
+ border-radius: 999px;
69
+ background-color: var(--md-sys-color-primary);
70
+ opacity: 0;
71
+ pointer-events: none;
72
+ transition: transform .22s cubic-bezier(.2, .7, .3, 1), width .22s cubic-bezier(.2, .7, .3, 1), opacity .14s ease;
73
+
74
+ // Only fade in once Toolbar's JS has measured and placed it for the
75
+ // first time - otherwise it flashes at 0 width in the top-left corner
76
+ // for a frame before the first real position is set.
77
+ .toolbar-track.is-armed & {
78
+ opacity: 1;
79
+ }
80
+ }
81
+
82
+ .toolbar-track-item {
83
+ position: relative;
84
+ z-index: 1;
85
+ display: inline-flex;
86
+ align-items: center;
87
+ height: 28px;
88
+ padding: 0 13px;
89
+ border: 0;
90
+ border-radius: 999px;
91
+ background-color: transparent;
92
+ color: var(--md-sys-color-on-surface-variant);
93
+ font-size: 12.5px;
94
+ line-height: 1;
95
+ white-space: nowrap;
96
+ cursor: pointer;
97
+ transition: color .16s ease;
98
+
99
+ &:hover {
100
+ color: var(--md-sys-color-on-surface);
101
+ }
102
+ &.is-active {
103
+ font-weight: 600;
104
+ color: var(--md-sys-color-on-primary);
105
+ }
106
+ &:focus-visible {
107
+ outline: 2px solid var(--md-sys-color-primary);
108
+ outline-offset: 2px;
109
+ }
110
+ }
111
+
112
+ /// Slider
113
+ ///////////////////////////////////////////////////////
114
+ ///
115
+
116
+ // A pill container for a range-field, matching .toolbar-track's look -
117
+ // also fixes two things a bare range-field has inside a .toolbar: the
118
+ // native track line is var(--md-sys-color-surface-variant), the exact
119
+ // same color as .toolbar's own background, so it's invisible without a
120
+ // container giving it something else to contrast against; and this also
121
+ // vertically centers any label text/icons placed next to it.
122
+ .toolbar-slider {
123
+ display: inline-flex;
124
+ align-items: center;
125
+ gap: 8px;
126
+ height: 34px;
127
+ padding: 0 13px;
128
+ border-radius: 999px;
129
+ background-color: var(--md-sys-color-surface);
130
+ border: 1px solid var(--md-sys-color-outline-variant);
131
+
132
+ .range-field {
133
+ width: 120px;
134
+ margin-top: 18px;
135
+ }
136
+
137
+ // Material icons default to 24px, which crowds a 34px-tall pill and
138
+ // reads a lot heavier than adjacent 13-14px text labels - match the
139
+ // track item's scale instead.
140
+ i.material-icons {
141
+ font-size: 18px;
142
+ }
143
+
144
+ span {
145
+ line-height: 1;
146
+ // Measured against the actual track/thumb center: a "-"/"+" glyph's
147
+ // ink sits a few px below the middle of its own line box in this
148
+ // font, even though the box itself is perfectly flex-centered - nudge
149
+ // the glyph up to compensate.
150
+ position: relative;
151
+ top: -3px;
152
+ }
153
+ }
154
+
155
+ /// Expandable search
156
+ ///////////////////////////////////////////////////////
157
+ ///
158
+
159
+ // A search field that starts as just an icon button and expands into a
160
+ // full text input once focused (or once it has a value) - Toolbar's JS
161
+ // toggles the .is-expanded class.
162
+ .toolbar-search {
163
+ display: inline-flex;
164
+ align-items: center;
165
+ width: 34px;
166
+ height: 34px;
167
+ padding: 2px;
168
+ overflow: hidden;
169
+ border-radius: 999px;
170
+ background-color: var(--md-sys-color-surface);
171
+ border: 1px solid var(--md-sys-color-outline-variant);
172
+ transition: width .2s cubic-bezier(.2, .7, .3, 1);
173
+
174
+ &.is-expanded {
175
+ width: 190px;
176
+ }
177
+
178
+ .toolbar-search-toggle {
179
+ display: inline-flex;
180
+ flex: 0 0 28px;
181
+ align-items: center;
182
+ justify-content: center;
183
+ width: 28px;
184
+ height: 28px;
185
+ padding: 0;
186
+ border: 0;
187
+ border-radius: 50%;
188
+ background-color: transparent;
189
+ color: var(--md-sys-color-on-surface-variant);
190
+ cursor: pointer;
191
+ }
192
+
193
+ .toolbar-search-input {
194
+ width: 0;
195
+ min-width: 0;
196
+ height: 28px;
197
+ margin: 0;
198
+ padding: 0;
199
+ border: 0;
200
+ outline: 0;
201
+ background-color: transparent;
202
+ color: var(--md-sys-color-on-surface);
203
+ font-size: 12px;
204
+ opacity: 0;
205
+ transition: opacity .12s ease;
206
+ }
207
+
208
+ &.is-expanded .toolbar-search-input {
209
+ width: 150px;
210
+ padding-right: 5px;
211
+ opacity: 1;
212
+ }
213
+ }
214
+
215
+ @media (prefers-reduced-motion: reduce) {
216
+ .toolbar-track-indicator {
217
+ transition: opacity .14s ease;
218
+ }
219
+ }
@@ -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
+ }