@thinkpixellab-public/px-vue 4.1.9 → 4.1.14

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.
@@ -37,6 +37,12 @@ export default {
37
37
  * items is updated and no other item is selected
38
38
  */
39
39
  autoSelectFirst: { type: Boolean, default: false },
40
+
41
+ /**
42
+ * If true, the update and change events will only be emitted after if the component is
43
+ * mounted.
44
+ */
45
+ emitEventsBeforeMounted: { type: Boolean, default: false },
40
46
  },
41
47
 
42
48
  data() {
@@ -179,7 +185,7 @@ export default {
179
185
  * Emit the change / update:selected event
180
186
  */
181
187
  emitUpdateEvent() {
182
- if (!this.isMounted) {
188
+ if (!this.isMounted && !this.emitEventsBeforeMounted) {
183
189
  return;
184
190
  }
185
191
 
@@ -1,8 +1,8 @@
1
1
  <script>
2
2
  export default {
3
3
  beforeCreate() {
4
- const id = typeof useId === 'function' ? useId() : Math.floor(Math.random() * 0x10000);
5
- this.$__uniqueId = id;
4
+ const id = this.$.uid || Math.floor(Math.random() * 1000);
5
+ this.$__uniqueId = String(id).padStart(4, '0');
6
6
  },
7
7
  methods: {
8
8
  /**
@@ -140,7 +140,7 @@ export default {
140
140
 
141
141
  methods: {
142
142
  combineIconClasses(...classNames) {
143
- return classNames.filter((c) => c).join(' ');
143
+ return classNames.filter(c => c).join(' ');
144
144
  },
145
145
  pxIconProps(attrs) {
146
146
  return propsFilter(PxIcon)(attrs);
@@ -233,7 +233,7 @@ export default {
233
233
 
234
234
  let r;
235
235
 
236
- if (this.aspect) {
236
+ if (this.aspect || (this.naturalWidth && this.naturalHeight)) {
237
237
  r = contain(
238
238
  inner,
239
239
  outer,
@@ -39,6 +39,7 @@
39
39
  ref="popup"
40
40
  :class="[bem('popup'), popupClass]"
41
41
  :style="{
42
+ opacity: !popupPositioned ? 0 : null,
42
43
  pointerEvents: visibleValue ? null : 'none',
43
44
  userSelect: visibleValue ? null : 'none',
44
45
  ...(positionCss || {}),
@@ -274,6 +275,9 @@ export default {
274
275
  // mirrors visibleValue but one tick behind (so that we can trigger actions that require the popup to be in the DOM)
275
276
  visibleValueNext: false,
276
277
 
278
+ // whether the popup has been finally positioned (used to trigger opacity)
279
+ popupPositioned: false,
280
+
277
281
  // mirrors reference
278
282
  referenceValue: null,
279
283
 
@@ -357,6 +361,10 @@ export default {
357
361
 
358
362
  // delayed actions (settimeout seems to work but $nextTick doesn't)
359
363
 
364
+ if (nv) {
365
+ this.popupPositioned = false;
366
+ }
367
+
360
368
  setTimeout(() => {
361
369
  // floating-ui
362
370
 
@@ -412,6 +420,8 @@ export default {
412
420
  // see watcher for transitionVisible
413
421
  }
414
422
 
423
+ this.popupPositioned = true;
424
+
415
425
  // show and hide events (also see beforeshow/beforehide in visibleValue watcher)
416
426
 
417
427
  if (nv) {
@@ -136,8 +136,8 @@ const PxIcon = {
136
136
 
137
137
  if (offset || spacing || this.strokeWidth) {
138
138
  return {
139
- transform: offset ? `translateY(${cssEmFallback(offset)})` : null,
140
- margin: spacing ? `auto ${cssEmFallback(spacing)}` : null,
139
+ transform: offset ? `translateY(${cssEmFallback(this.offset)})` : null,
140
+ margin: spacing ? `auto ${cssEmFallback(this.spacing)}` : null,
141
141
  strokeWidth: cssPx(this.strokeWidth, false),
142
142
  };
143
143
  }
@@ -115,6 +115,7 @@ export default {
115
115
 
116
116
  this.updateSize(r.width, r.height, this.downDirection);
117
117
 
118
+ document.body.style.userSelect = 'none';
118
119
  document.addEventListener('mousemove', this.mousemove);
119
120
  document.addEventListener('mouseup', this.mouseup);
120
121
  }
@@ -130,6 +131,7 @@ export default {
130
131
  e.preventDefault();
131
132
  },
132
133
  mouseup() {
134
+ document.body.style.removeProperty('user-select');
133
135
  this.resizing = false;
134
136
  this.emitResizeEnd();
135
137
  document.removeEventListener('mousemove', this.mousemove);
@@ -143,6 +145,16 @@ export default {
143
145
  @use '../styles/px.scss' as *;
144
146
  @use 'sass:color';
145
147
 
148
+ @at-root {
149
+ :root {
150
+ --px-resizer-color: #{accent()};
151
+ --px-resizer-color-hover: #{color.adjust(accent(), $lightness: -10%)};
152
+ --px-resizer-width: 3px;
153
+ --px-resizer-height: 24px;
154
+ --px-resizer-hit-width: 24px;
155
+ --px-resizer-offset: 10px;
156
+ }
157
+ }
146
158
  .px-resizer {
147
159
  position: relative;
148
160
  margin: 0 auto;
@@ -165,16 +177,16 @@ export default {
165
177
 
166
178
  &--show-overlay {
167
179
  &::after {
168
- border: 1px dashed accent();
180
+ border: 1px dashed var(--px-resizer-color);
169
181
  border-radius: inherit;
170
182
  }
171
183
  }
172
184
 
173
185
  &__resize-x,
174
186
  &__resize-y {
175
- color: accent();
187
+ color: var(--px-resizer-color);
176
188
  &:hover {
177
- color: color.adjust(accent(), $lightness: -10%);
189
+ color: var(--px-resizer-color-hover);
178
190
  }
179
191
  &:active {
180
192
  opacity: 0.5;
@@ -184,13 +196,16 @@ export default {
184
196
  &__resize-x {
185
197
  @include center-y();
186
198
  cursor: ew-resize;
187
- width: 24px;
188
- height: 36px;
189
- right: -24px;
199
+ width: var(--px-resizer-hit-width);
200
+ height: var(--px-resizer-height);
201
+ right: calc(var(--px-resizer-hit-width) * -1);
202
+
190
203
  @include after() {
191
- @include center();
192
- width: 3px;
193
- height: 24px;
204
+ position: absolute;
205
+ top: 0;
206
+ left: var(--px-resizer-offset);
207
+ width: var(--px-resizer-width);
208
+ height: var(--px-resizer-height);
194
209
  background-color: currentColor;
195
210
  }
196
211
  }
@@ -198,13 +213,17 @@ export default {
198
213
  &__resize-y {
199
214
  @include center-x();
200
215
  cursor: ns-resize;
201
- width: 36px;
202
- height: 24px;
203
- bottom: -24px;
216
+
217
+ height: var(--px-resizer-hit-width);
218
+ width: var(--px-resizer-height);
219
+ bottom: calc(var(--px-resizer-hit-width) * -1);
220
+
204
221
  @include after() {
205
- @include center();
206
- height: 3px;
207
- width: 24px;
222
+ position: absolute;
223
+ left: 0;
224
+ top: var(--px-resizer-offset);
225
+ height: var(--px-resizer-width);
226
+ width: var(--px-resizer-height);
208
227
  background-color: currentColor;
209
228
  }
210
229
  }
@@ -14,6 +14,19 @@
14
14
  :style="{ backgroundColor: checkedValue ? checkedColor : uncheckedColor }"
15
15
  >
16
16
  <span :class="bem('spacer')"></span>
17
+ <transition name="px-toggle-icon">
18
+ <span v-if="$slots['icon-on'] && checkedValue" :class="bem('icon', { on: true })">
19
+ <slot name="icon-on" />
20
+ </span>
21
+ </transition>
22
+ <transition name="px-toggle-icon">
23
+ <span
24
+ v-if="$slots['icon-off'] && !checkedValue"
25
+ :class="bem('icon', { off: true })"
26
+ >
27
+ <slot name="icon-off" />
28
+ </span>
29
+ </transition>
17
30
  <span
18
31
  :class="[bem('thumb'), trackClass]"
19
32
  :style="{ backroundColor: thumbColor }"
@@ -79,10 +92,18 @@ export default {
79
92
  @use '@thinkpixellab-public/px-styles/src/modules/controls' as *;
80
93
 
81
94
  .px-toggle {
82
- --px-toggle-size: 1em;
83
- --px-toggle-unchecked: #{gray(-8, 0.33)};
84
- --px-toggle-checked: #{accent()};
85
- --px-toggle-thumb: white;
95
+ @at-root {
96
+ @include vue-transition-fade(px-toggle-icon);
97
+
98
+ :root {
99
+ --px-toggle-size: 1em;
100
+ --px-toggle-unchecked: #{gray(-8, 0.33)};
101
+ --px-toggle-checked: #{accent()};
102
+ --px-toggle-thumb: white;
103
+ --px-toggle-track-shadow: inset 0 1px 1px 0 rgba(0, 0, 0, 0.2);
104
+ --px-toggle-thumb-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.4);
105
+ }
106
+ }
86
107
 
87
108
  @include toggle-switch(
88
109
  (
@@ -90,6 +111,8 @@ export default {
90
111
  '--thumb.font-size': var(--px-toggle-size),
91
112
  '--track.background-color': var(--px-toggle-unchecked),
92
113
  '--track.font-size': var(--px-toggle-size),
114
+ '--track.box-shadow': var(--px-toggle-track-shadow),
115
+ '--thumb.box-shadow': var(--px-toggle-thumb-shadow),
93
116
  'checked.--track.background-color': var(--px-toggle-checked),
94
117
  ),
95
118
  '.px-toggle__track',
@@ -97,6 +120,26 @@ export default {
97
120
  '.px-toggle__spacer'
98
121
  );
99
122
 
123
+ &__icon {
124
+ position: absolute;
125
+ top: 0;
126
+ height: 100%;
127
+ aspect-ratio: 1;
128
+ border-radius: 50%;
129
+
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+
134
+ &--on {
135
+ left: 0;
136
+ }
137
+
138
+ &--off {
139
+ right: 0;
140
+ }
141
+ }
142
+
100
143
  &--reverse {
101
144
  flex-direction: row-reverse;
102
145
  }
@@ -163,12 +163,16 @@ export default {
163
163
  return this.mode === 'expander' ? 'button' : 'switch';
164
164
  },
165
165
  ariaChecked() {
166
- if (!this.isSwitch) return null;
166
+ if (!this.isSwitch) {
167
+ return null;
168
+ }
167
169
 
168
170
  return this.checked ? 'true' : 'false';
169
171
  },
170
172
  ariaExpanded() {
171
- if (this.isSwitch) return null;
173
+ if (this.isSwitch) {
174
+ return null;
175
+ }
172
176
 
173
177
  return this.checked ? 'true' : 'false';
174
178
  },
@@ -28,7 +28,11 @@
28
28
  v-for="item in items"
29
29
  :key="getItemKey(item)"
30
30
  :is="isVariant('radio') ? 'px-radio-button' : 'px-toggle-button'"
31
- :class="[bem('button'), buttonClass]"
31
+ :class="[
32
+ bem('button'),
33
+ buttonClass,
34
+ isItemSelected(item) ? buttonClass + '--checked' : null,
35
+ ]"
32
36
  :icon="icon"
33
37
  :iconSize="iconSize"
34
38
  :variant="isVariant('radio') ? 'default' : variant"
@@ -79,6 +83,11 @@ export default {
79
83
 
80
84
  //
81
85
  multiSelect: { type: Boolean, default: false },
86
+
87
+ /**
88
+ * When true, ensures that one item is always selected (radio button behavior)
89
+ */
90
+ requireSelection: { type: Boolean, default: true },
82
91
  },
83
92
  computed: {
84
93
  calculatedInputGroup() {
@@ -96,6 +105,15 @@ export default {
96
105
  this.selectItems(item);
97
106
  },
98
107
  itemUncheck(item) {
108
+ // Prevent unchecking if requireSelection is true and this is the last selected item
109
+ if (
110
+ this.requireSelection &&
111
+ !this.multiSelect &&
112
+ this.selectedItems.length === 1 &&
113
+ this.isItemSelected(item)
114
+ ) {
115
+ return;
116
+ }
99
117
  this.unselectItems(item);
100
118
  },
101
119
  },