@thinkpixellab-public/px-vue 4.1.9 → 4.1.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.
@@ -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
  /**
@@ -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,
@@ -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
  }
@@ -0,0 +1,49 @@
1
+ import { ref, watch, onMounted } from 'vue';
2
+ import { deepMerge } from '../utils/utils.js';
3
+
4
+ /**
5
+ * Provides a reactive settings object that syncs with localStorage. Basic usage:
6
+ *
7
+ * setup() {
8
+ * const settings = useSettings("appSettings", {
9
+ * setting1: true,
10
+ * setting2: 'awesome'
11
+ * }, (loadedSettings) => {
12
+ * console.log("Settings initialized:", loadedSettings);
13
+ * });
14
+ * return { settings }
15
+ * }
16
+ */
17
+
18
+ export default function useSettings(key, defaultSettings = {}, onInit = null) {
19
+ const settings = ref(defaultSettings);
20
+
21
+ const loadSettings = () => {
22
+ if (process.client) {
23
+ const stored = localStorage.getItem(key);
24
+ const parsed = stored ? JSON.parse(stored) : {};
25
+
26
+ settings.value = deepMerge(settings.value, parsed, true);
27
+
28
+ if (onInit && typeof onInit === 'function') {
29
+ onInit(settings.value);
30
+ }
31
+ }
32
+ };
33
+
34
+ onMounted(() => {
35
+ loadSettings();
36
+ });
37
+
38
+ watch(
39
+ settings,
40
+ nv => {
41
+ if (process.client) {
42
+ localStorage.setItem(key, JSON.stringify(nv));
43
+ }
44
+ },
45
+ { deep: true },
46
+ );
47
+
48
+ return settings;
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "4.1.9",
3
+ "version": "4.1.13",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": {
6
6
  "name": "Pixel Lab"
@@ -37,6 +37,44 @@ export const Basic = {
37
37
  </PxResizer>
38
38
 
39
39
 
40
+ `,
41
+ }),
42
+ };
43
+
44
+ export const ScaledContentExample = {
45
+ render: (args, { argTypes }) => ({
46
+ components: { PxResizer, PxContain },
47
+ setup() {
48
+ return { args };
49
+ },
50
+ template: `
51
+ <PxResizer :initialWidth="400" initialHeight="400" class="story-bounds" >
52
+ <PxContain #default="{width, height, left, top, scale}" :naturalWidth="1000" :naturalHeight="1000" :round="true" :to-px="true" v-bind="args" >
53
+ <div
54
+ class="story-fill"
55
+ :style="{
56
+ top,
57
+ left,
58
+ width: '1000px',
59
+ height: '1000px',
60
+ position: 'absolute',
61
+ transform: 'scale(' + scale + ')',
62
+ transformOrigin: '0 0',
63
+ fontSize: '50px',
64
+ }">
65
+ Outer natural size: 1000px x 1000px<br>
66
+ Font-size: 50px<br>
67
+ Scale {{Math.round(1000 * scale) / 1000}}
68
+
69
+
70
+ <div style="width: 500px; height: 500px; background-color: rgba(0,0,0,0.5); top: 250px; left: 250px; position: absolute;">
71
+ Inner Rectangle Size: 500px x 500px
72
+ </div>
73
+ </div>
74
+ </PxContain>
75
+ </PxResizer>
76
+
77
+
40
78
  `,
41
79
  }),
42
80
  };
@@ -99,6 +99,7 @@ test('single', async () => {
99
99
  props: {
100
100
  items: items,
101
101
  selected: [items[3].id, items[2].id],
102
+ emitEventsBeforeMounted: true,
102
103
  },
103
104
  });
104
105
 
@@ -4,9 +4,11 @@ import PxBaseUniqueId from '@/bases/PxBaseUniqueId.vue';
4
4
 
5
5
  test('Ensure uniqueId method works', () => {
6
6
  const wrapper = shallowMount(MockComponent, {
7
- mixins: [PxBaseUniqueId],
8
- props: {},
7
+ global: {
8
+ mixins: [PxBaseUniqueId],
9
+ },
9
10
  });
11
+
10
12
  expect(wrapper.vm.uniqueId()).toEqual('0001');
11
13
  });
12
14
 
@@ -18,7 +20,6 @@ test('Ensure uniqueId method works with a prefix', () => {
18
20
  expect(wrapper.vm.uniqueId('label')).toEqual('label-0003');
19
21
  });
20
22
 
21
-
22
23
  test('Ensure uniqueId method trims extra spaces', () => {
23
24
  const wrapper = shallowMount(MockComponent, {
24
25
  mixins: [PxBaseUniqueId],
@@ -1,4 +1,4 @@
1
- import { objectToArray } from '../utils/utils.js';
1
+ import { objectToArray, deepMerge } from '../utils/utils.js';
2
2
 
3
3
  test('objectToArray (defaults with value type value)', () => {
4
4
  expect(objectToArray({ one: 1, two: 2, three: 3 })).toEqual([
@@ -44,3 +44,17 @@ test('objectToArray (expand objects)', () => {
44
44
  { key: 'three', number: 3, ordinal: 'third' },
45
45
  ]);
46
46
  });
47
+
48
+ test('deepMerge', () => {
49
+ const target = { a: 1, b: { c: 2, d: 3 } };
50
+ const source = { b: { d: 4, e: 5 } };
51
+
52
+ expect(deepMerge(target, source)).toEqual({ a: 1, b: { c: 2, d: 4, e: 5 } });
53
+ });
54
+
55
+ test('deepMerge (overwrite is false)', () => {
56
+ const target = { a: 1, b: { c: 2, d: 3 } };
57
+ const source = { b: { d: 4, e: 5 } };
58
+
59
+ expect(deepMerge(target, source, false)).toEqual({ a: 1, b: { c: 2, d: 3, e: 5 } });
60
+ });
package/utils/drag.js CHANGED
@@ -36,6 +36,9 @@ const defaults = {
36
36
 
37
37
  // initial value for the disabled property
38
38
  disabled: false,
39
+
40
+ // disable text selection during drag
41
+ disableTextSelection: true,
39
42
  };
40
43
 
41
44
  function Drag(element, options) {
@@ -310,6 +313,11 @@ function Drag(element, options) {
310
313
 
311
314
  this.addRuntimeEvents = () => {
312
315
  this.removeRuntimeEvents();
316
+
317
+ if (this.ops.disableTextSelection) {
318
+ document.body.style.userSelect = 'none';
319
+ }
320
+
313
321
  if (this.supportsTouch) {
314
322
  document.addEventListener('touchmove', this.onDrag, { passive: false });
315
323
  document.addEventListener('touchend', this.onEnd);
@@ -320,6 +328,10 @@ function Drag(element, options) {
320
328
  };
321
329
 
322
330
  this.removeRuntimeEvents = () => {
331
+ if (this.ops.disableTextSelection) {
332
+ document.body.style.removeProperty('user-select');
333
+ }
334
+
323
335
  if (this.supportsTouch) {
324
336
  document.removeEventListener('touchmove', this.onDrag, { passive: false });
325
337
  document.removeEventListener('touchend', this.onEnd);
package/utils/utils.js CHANGED
@@ -251,6 +251,42 @@ export function filterObjectByKeys(obj, allowedKeys) {
251
251
  }, {});
252
252
  }
253
253
 
254
+ /**
255
+ * Performs a deep merge of two objects by merging source into target. This function is similar to
256
+ * Object.assign, but it recursively merges objects.
257
+ *
258
+ * @param {Object} target - The object to merge into.
259
+ * @param {Object} source - The object to merge from.
260
+ * @param {boolean} [overwrite=true] - If true, properties in source will overwrite properties in
261
+ * target with the same key. If false, properties in source will only be added if the key doesn't
262
+ * already exist in target.
263
+ * @returns {Object} The merged object.
264
+ *
265
+ * @example
266
+ * const target = { a: 1, b: { c: 2 } };
267
+ * const source = { b: { d: 3 } };
268
+ * const result = deepMerge(target, source);
269
+ * // result is { a: 1, b: { c: 2, d: 3 } }
270
+ */
271
+
272
+ export function deepMerge(target, source, overwrite = true) {
273
+ if (typeof target !== 'object' || typeof source !== 'object') return source;
274
+
275
+ for (const key in source) {
276
+ if (source.hasOwnProperty(key)) {
277
+ if (
278
+ typeof source[key] === 'object' &&
279
+ !Array.isArray(source[key]) &&
280
+ typeof target[key] === 'object'
281
+ ) {
282
+ target[key] = deepMerge(target[key], source[key], overwrite); // Recursively merge objects
283
+ } else if (overwrite || !(key in target)) {
284
+ target[key] = source[key];
285
+ }
286
+ }
287
+ }
288
+ return target;
289
+ }
254
290
  /**
255
291
  * Returns an object representing the current query string parameters.
256
292
  */