@thinkpixellab-public/px-vue 3.0.2 → 3.0.3

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.
@@ -122,16 +122,10 @@ export default {
122
122
 
123
123
  // popup
124
124
 
125
- $dropdown-popup: flat-merge(
126
- get(
127
- 'controls:popup',
128
- (
129
- display: flex,
130
- flex-direction: column,
131
- background-color: clr(page-bg),
132
- border-radius: get('controls:border-radius'),
133
- box-shadow: shadow(15),
134
- )
125
+ $dropdown-popup: popup(
126
+ (
127
+ display: flex,
128
+ flex-direction: column,
135
129
  )
136
130
  );
137
131
 
@@ -1,32 +1,631 @@
1
1
  <!--
2
- Describe this component...
2
+
3
+ Floating ui container. Can be used for dialogs, modals, popups, menus, etc. Used floating-ui
4
+ (https://floating-ui.com/) for positioning. Can handle modal things like disabling scrolling,
5
+ focus management, etc. but doesn't by default. API is designed to be somewhat progressive (does
6
+ more as you ask for more).
7
+
8
+ Common variants:
9
+
10
+ none - removes all styling
11
+ default - provides basic popup styling
12
+ dialog - provides same basic popup style as default but centered in the screen
13
+ sm, md, lg, xl - provides min/max sizes (expected to be used with dialog)
14
+ gray - gives the overlay a gray shade
15
+ blur - gives the overlay a blurred backdrop filter
16
+
3
17
  -->
18
+
4
19
  <template>
5
- <div :class="bem()"></div>
20
+ <px-portal :disabled="!portalEnabled" v-if="keepInDom || visibleValue || transitionVisible">
21
+ <transition
22
+ :name="calcTransitionName"
23
+ @before-enter="transitionVisible = true"
24
+ @after-leave="transitionVisible = false"
25
+ :style="{ zIndex: zStart + currentLayerIndex }"
26
+ >
27
+ <div
28
+ :class="bem({ ...variantsMap, absolute: strategy == 'absolute' })"
29
+ v-show="visibleValueNext"
30
+ >
31
+ <!-- overlay element -->
32
+ <slot name="overlay" v-if="overlayEnabled">
33
+ <div
34
+ :class="[bem('overlay'), overlayClass]"
35
+ @click="overlayClick"
36
+ v-bind="overlayAttributes"
37
+ :style="{
38
+ pointerEvents: visibleValue ? null : 'none',
39
+ userSelect: visibleValue ? null : 'none',
40
+ }"
41
+ >
42
+ <slot name="overlay-content"></slot>
43
+ </div>
44
+ </slot>
45
+
46
+ <!-- popup element -->
47
+ <div
48
+ ref="popup"
49
+ :class="[bem('popup'), popupClass]"
50
+ :style="{
51
+ pointerEvents: visibleValue ? null : 'none',
52
+ userSelect: visibleValue ? null : 'none',
53
+ ...(positionCss || {}),
54
+ ...popupStyle,
55
+ }"
56
+ >
57
+ <slot />
58
+ </div>
59
+ </div>
60
+ </transition>
61
+ </px-portal>
6
62
  </template>
7
63
 
8
64
  <script>
9
- // import { mapState } from 'vuex';
10
- // import Component from '~/components/Component.vue';
11
- // import Mixin from '~/components/Mixin.vue';
65
+ import PxBaseVariants from './PxBaseVariants.vue';
66
+ import PxPortal from './PxPortal.vue';
67
+
68
+ import { computePosition, autoUpdate, flip, shift, offset } from '@floating-ui/dom';
69
+ import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
70
+
71
+ const FOCUS_ELEMENTS = [
72
+ 'button',
73
+ '[href]:not(.disabled)',
74
+ 'input',
75
+ 'select',
76
+ 'textarea',
77
+ '[tabindex]',
78
+ '[contenteditable]',
79
+ ];
80
+
81
+ let globalLayerIndex = 0;
82
+ let globalClearLayerIndex = 0;
83
+
12
84
  export default {
13
- name: 'px-float'
14
- // components: { Component },
15
- // mixins: [ Mixin ],
85
+ name: 'px-float',
86
+ components: { PxPortal },
87
+ mixins: [PxBaseVariants],
16
88
  props: {
17
- a: { type: Boolean, default: false }
89
+ visible: { type: Boolean, default: false },
90
+
91
+ // whether to keep the popup element in the DOM even if not visible
92
+ keepInDom: { type: Boolean, default: false },
93
+
94
+ // whether to use a portal (often required for correct layout, esp. if using an overlay)
95
+ portalEnabled: { type: Boolean, default: true },
96
+
97
+ // automatically move focus to the contents of the modal when opened
98
+ autoFocusEnabled: { type: Boolean, default: true },
99
+
100
+ // automatically restore focus with then modal closes
101
+ restoreFocusEnabled: { type: Boolean, default: true },
102
+
103
+ // Selector to find the first element that gets focus when focus is set on the dialog
104
+ autoFocusSelector: { type: String, default: '[autofocus]' },
105
+
106
+ // whether to disable scrolling when visible
107
+ disablesScroll: { type: Boolean, default: true },
108
+
109
+ // if true, esc key will close the popup
110
+ escCloses: { type: Boolean, default: true },
111
+
112
+ // The starting point for the z-index (note: incremented as more layers of popups are opened
113
+ // to ensure that the last opened from the same starting z will be on top of each other)
114
+ zStart: { type: Number, default: 999999 },
115
+
116
+ // An additional class set on the popup element
117
+ popupClass: { type: String, default: '' },
118
+
119
+ // Style object that gets merged onto the popup
120
+ popupStyle: { type: Object, default: null },
121
+
122
+ // Whether to render an overlay element
123
+ overlayEnabled: { type: Boolean, default: true },
124
+
125
+ // An additional class set on the overlay element
126
+ overlayClass: { type: String, default: '' },
127
+
128
+ // Additional attributes that get set on the overlay (can be used for adding analytics attributes, etc.)
129
+ overlayAttributes: { type: Object, default: null },
130
+
131
+ // Whether clicking the overlay element will close the popup
132
+ overlayClose: { type: Boolean, default: true },
133
+
134
+ // The name of the transition (null will defaults to a basic fade but variants can override)
135
+ transitionName: { type: String, default: null },
136
+
137
+ // floating ui options
138
+
139
+ // The reference element when positioning. Can be an element, a virtual element (see docs)
140
+ // or a string. Supported strings include:
141
+ // - previous (previous sibling element)
142
+ // - next (next sibling element)
143
+ // - cursor (show at cursor location)
144
+ // - css selector (will use first found element)
145
+ reference: { default: 'previous' },
146
+
147
+ // null (no placement - use css or variant)
148
+ // top | right | bottom | left (+? -start / -end)
149
+ placement: { type: String, default: null },
150
+
151
+ // whether the position updates automatically when placement has been set
152
+ autoUpdate: { type: Boolean, default: true },
153
+
154
+ // fixed or absolute positioning
155
+ strategy: { type: String, default: 'fixed' },
156
+
157
+ // add spacing along the core axis, can be a number or an options object
158
+ // (see https://floating-ui.com/docs/offset)
159
+ offset: { default: 0 },
160
+
161
+ // flip to the other side of the reference element when space is limited
162
+ flip: { default: true },
163
+
164
+ // like flip but for the start/end direction
165
+ shift: { default: true },
166
+
167
+ // NOT IMPLEMENTED
168
+ // hiddenClass: { type: String, default: null },
169
+
170
+ // NOT IMPLEMENTED
171
+ // inline: { type: Boolean, default: false },
172
+
173
+ // use translate instead of left/top css properties (may increase performance in some cases)
174
+ useTranslate: { type: Boolean, default: false },
175
+ },
176
+ data() {
177
+ return {
178
+ // mirrors visible
179
+ visibleValue: false,
180
+
181
+ // mirrors visibleValue but one tick behind (so that we can trigger actions that require the popup to be in the DOM)
182
+ visibleValueNext: false,
183
+
184
+ // mirrors reference
185
+ referenceValue: null,
186
+
187
+ // css for positioning the element with floating-ui
188
+ positionCss: null,
189
+
190
+ // true while the element remains visible during a transition (even if visible/visibleValue are false)
191
+ transitionVisible: false,
192
+
193
+ // z offset of the current layer
194
+ currentLayerIndex: 0,
195
+ };
196
+ },
197
+ computed: {
198
+ // the name of the transition to use ()
199
+ calcTransitionName() {
200
+ if (this.transitionName) {
201
+ return this.transitionName;
202
+ }
203
+
204
+ return this.isVariant('blur') ? 'px-float-fade-blur' : 'px-float-fade';
205
+ },
206
+ },
207
+
208
+ watch: {
209
+ reference(nv) {
210
+ this.referenceValue = nv;
211
+ },
212
+
213
+ referenceValue(nv) {
214
+ this.$emit('update:reference', nv);
215
+ },
216
+
217
+ visible(nv) {
218
+ this.visibleValue = nv;
219
+ },
220
+
221
+ visibleValue(nv, ov) {
222
+ // emit update when visibleValue changes (enables .sync on parent)
223
+ this.$emit('update:visible', nv);
224
+
225
+ if (this.isServer) {
226
+ return;
227
+ }
228
+
229
+ // layer management
230
+
231
+ // show
232
+ if (ov === false && nv === true) {
233
+ globalLayerIndex += 1;
234
+ this.currentLayerIndex = globalLayerIndex;
235
+ }
236
+
237
+ // hide
238
+ if (ov === true && nv === false) {
239
+ setTimeout(() => {
240
+ globalLayerIndex -= 1;
241
+ }, 30);
242
+ }
243
+
244
+ this.$nextTick(() => {
245
+ this.visibleValueNext = nv;
246
+ });
247
+ },
248
+
249
+ visibleValueNext(nv) {
250
+ // key handler
251
+
252
+ if (nv) {
253
+ document.addEventListener('keydown', this.onKey);
254
+ } else {
255
+ document.removeEventListener('keydown', this.onKey);
256
+ }
257
+
258
+ // delayed actions (settimeout seems to work but $nextTick doesn't)
259
+
260
+ setTimeout(() => {
261
+ // floating-ui
262
+
263
+ if (nv) {
264
+ let reference = this.getReferenceElement();
265
+ let popup = this.$refs.popup;
266
+
267
+ this.updatePosition(reference, popup);
268
+
269
+ if (this.autoUpdate) {
270
+ this.cleanup = autoUpdate(reference, popup, () =>
271
+ this.updatePosition(reference, popup)
272
+ );
273
+ }
274
+ } else {
275
+ if (this.cleanup) {
276
+ console.log('cleanup');
277
+ this.cleanup();
278
+ this.cleanup = null;
279
+ }
280
+ }
281
+
282
+ // focus
283
+
284
+ if (nv) {
285
+ // track the element that was focused on open
286
+ this.previousActiveElement = document.activeElement;
287
+
288
+ // focus the first focusable element (or autofocus element if set)
289
+ if (this.autoFocusEnabled) {
290
+ this.setAutoFocus();
291
+ }
292
+ } else {
293
+ // restore focus
294
+ if (this.previousActiveElement && this.restoreFocusEnabled) {
295
+ this.previousActiveElement.focus({ preventScroll: true });
296
+ this.previousActiveElement = null;
297
+ }
298
+ }
299
+
300
+ // disable scrolling
301
+
302
+ if (nv) {
303
+ if (this.disablesScroll) {
304
+ disableBodyScroll(this.$refs.popup, { reserveScrollBarGap: true });
305
+ if (!globalClearLayerIndex) {
306
+ globalClearLayerIndex = globalLayerIndex;
307
+ }
308
+ }
309
+ } else {
310
+ if (globalLayerIndex <= globalClearLayerIndex) {
311
+ clearAllBodyScrollLocks();
312
+ globalClearLayerIndex = 0;
313
+ }
314
+ }
315
+ });
316
+ },
317
+ },
318
+ mounted() {
319
+ this.visibleValue = this.visible;
320
+ this.transitionVisible = this.visible;
321
+ this.referenceValue = this.reference;
322
+ },
323
+ methods: {
324
+ show(reference) {
325
+ this.hideSource = null;
326
+ this.visibleValue = true;
327
+ this.referenceValue = reference;
328
+ },
329
+
330
+ showAtCursor(mouseEvent) {
331
+ this.show({
332
+ getBoundingClientRect() {
333
+ return {
334
+ width: 0,
335
+ height: 0,
336
+ x: mouseEvent.clientX,
337
+ y: mouseEvent.clientY,
338
+ top: mouseEvent.clientY,
339
+ left: mouseEvent.clientX,
340
+ right: mouseEvent.clientX,
341
+ bottom: mouseEvent.clientY,
342
+ };
343
+ },
344
+ });
345
+ },
346
+
347
+ hide(hideSource) {
348
+ this.hideSource = hideSource;
349
+ this.visibleValue = false;
350
+ },
351
+
352
+ overlayClick() {
353
+ if (this.overlayClose) {
354
+ this.visibleValue = false;
355
+ }
356
+ },
357
+
358
+ onKey(e) {
359
+ if (this.visibleValue) {
360
+ if (e.keyCode === 27 && this.escCloses) {
361
+ this.onEsc(e);
362
+ } else if (e.keyCode === 9) {
363
+ this.onTab(e);
364
+ }
365
+ }
366
+ },
367
+
368
+ onEsc() {
369
+ if (this.currentLayerIndex == globalLayerIndex) {
370
+ this.hide('esc');
371
+ this.$emit('cancel');
372
+ }
373
+ },
374
+
375
+ onTab(e) {
376
+ // Refresh this on every tab press in case visibility of something has changed
377
+ let focusables = this.getFocusableChildren();
378
+ let focusedIndex = focusables.indexOf(document.activeElement);
379
+
380
+ // Trap tab focus
381
+ if (e.shiftKey && focusedIndex <= 0) {
382
+ focusables[focusables.length - 1].focus();
383
+ e.preventDefault();
384
+ } else if (!e.shiftKey && focusedIndex === focusables.length - 1) {
385
+ focusables[0].focus();
386
+ event.preventDefault();
387
+ }
388
+ },
389
+
390
+ setAutoFocus() {
391
+ let focusables = this.getFocusableChildren();
392
+ let dialogFocusable = this.$refs.popup?.querySelector(this.autoFocusSelector);
393
+ let focused = dialogFocusable || focusables[0];
394
+ if (focused) {
395
+ focused.focus({ preventScroll: true });
396
+ }
397
+ },
398
+
399
+ getFocusableChildren() {
400
+ let f = [...(this.$refs.popup?.querySelectorAll(FOCUS_ELEMENTS.join(',')) || [])];
401
+
402
+ // filter to visible only
403
+ f = f.filter(child => {
404
+ return (
405
+ child.tabIndex >= 0 &&
406
+ !!(child.offsetWidth || child.offsetHeight || child.getClientRects().length)
407
+ );
408
+ });
409
+
410
+ return f.sort((a, b) => {
411
+ if (a.tabIndex > b.tabIndex) {
412
+ return 1;
413
+ }
414
+ if (a.tabIndex < b.tabIndex) {
415
+ return -1;
416
+ }
417
+ return 0;
418
+ });
419
+ },
420
+
421
+ updatePosition(reference) {
422
+ if (this.isServer) {
423
+ return;
424
+ }
425
+
426
+ const middleware = [];
427
+
428
+ if (this.offset) {
429
+ middleware.push(offset(this.offset));
430
+ }
431
+
432
+ if (this.shift) {
433
+ middleware.push(shift(this.shift));
434
+ }
435
+
436
+ if (this.flip) {
437
+ middleware.push(flip(this.flip));
438
+ }
439
+
440
+ if (this.placement && this.$refs.popup && reference) {
441
+ computePosition(reference, this.$refs.popup, {
442
+ placement: this.placement,
443
+ strategy: this.strategy,
444
+ middleware: middleware,
445
+ }).then(({ x, y }) => {
446
+ if (this.useTranslate) {
447
+ this.positionCss = {
448
+ transform: `translate(${this.cssPx(x)},${this.cssPx(y)})`,
449
+ };
450
+ } else {
451
+ this.positionCss = {
452
+ left: this.cssPx(x),
453
+ top: this.cssPx(y),
454
+ };
455
+ }
456
+ });
457
+ } else {
458
+ this.positionCss = null;
459
+ }
460
+ },
461
+
462
+ getReferenceElement() {
463
+ let reference = this.referenceValue;
464
+
465
+ if (typeof reference == 'string') {
466
+ // previous sibling
467
+ if (reference == 'previous') {
468
+ reference = this.$el.previousSibling;
469
+ }
470
+
471
+ // next sibling
472
+ else if (reference == 'next') {
473
+ reference = this.$el.nextSibling;
474
+ }
475
+
476
+ // selector
477
+ else {
478
+ reference = document.querySelector(reference);
479
+ }
480
+ }
481
+
482
+ return reference;
483
+ },
18
484
  },
19
- // data() { return { b: 0 }; },
20
- // computed: {},
21
- // watch: {},
22
- // mounted() {},
23
- // methods: {},
24
485
  };
25
486
  </script>
26
487
 
27
488
  <style lang="scss">
28
- @import '@/styles/include.scss';
489
+ @use '../styles/px.scss' as *;
490
+
491
+ $d: 0.4s;
492
+
29
493
  .px-float {
30
- // add styles here
494
+ position: relative;
495
+ top: 0;
496
+ left: 0;
497
+
498
+ &__overlay {
499
+ position: fixed;
500
+ top: 0;
501
+ left: 0;
502
+ width: 100vw;
503
+ height: 100vh;
504
+ }
505
+
506
+ &__popup {
507
+ position: fixed;
508
+ top: 0;
509
+ left: 0;
510
+ }
511
+
512
+ &--absolute {
513
+ position: absolute;
514
+ }
515
+
516
+ // variants
517
+
518
+ // styles
519
+
520
+ &--none {
521
+ // reset
522
+ }
523
+
524
+ &--dialog &,
525
+ &--default & {
526
+ &__popup {
527
+ @include css-map(
528
+ popup(
529
+ (
530
+ padding: sp(2),
531
+ )
532
+ )
533
+ );
534
+ }
535
+ }
536
+
537
+ &--dialog & {
538
+ &__popup {
539
+ top: 40%;
540
+ left: 50%;
541
+ transform: translate(-50%, -40%);
542
+ }
543
+ }
544
+
545
+ // sizes
546
+
547
+ &--sm & {
548
+ &__popup {
549
+ max-width: min(94vw, 320px);
550
+ }
551
+ }
552
+
553
+ &--md & {
554
+ &__popup {
555
+ max-width: min(94vw, 600px);
556
+ }
557
+ }
558
+
559
+ &--lg & {
560
+ &__popup {
561
+ max-width: min(94vw, 800px);
562
+ }
563
+ }
564
+
565
+ &--lx & {
566
+ &__popup {
567
+ max-width: min(94vw, 1280px);
568
+ }
569
+ }
570
+
571
+ &--container {
572
+ max-width: min(94vw, get('container:width'));
573
+ }
574
+
575
+ // overlays
576
+
577
+ &--gray & {
578
+ &__overlay {
579
+ background-color: rgba(gray(-4), 0.4);
580
+ }
581
+ }
582
+
583
+ &--blur & {
584
+ // also see the transition below
585
+
586
+ &__overlay {
587
+ backdrop-filter: blur(4px);
588
+ }
589
+ }
590
+ }
591
+
592
+ // ----------------------------------------------------------------------------
593
+ // default 'fade' transition
594
+ // ----------------------------------------------------------------------------
595
+
596
+ @include vue-transition-fade(px-float-fade, $dur: $d);
597
+
598
+ // ----------------------------------------------------------------------------
599
+ // blur variant transition (uses filter for opacity and animates backdrop-filter)
600
+ // ----------------------------------------------------------------------------
601
+
602
+ @include vue-transition(px-float-fade-blur) {
603
+ @include vt-transition {
604
+ transition-delay: $d;
605
+
606
+ .px-float__overlay {
607
+ transition: filter $d, backdrop-filter $d;
608
+ }
609
+ .px-float__popup {
610
+ transition: filter $d;
611
+ }
612
+ }
613
+
614
+ // define the hidden state (the non-hidden state is implicty -- the normal state)
615
+ @include vt-hidden {
616
+ .px-float__overlay {
617
+ filter: opacity(0);
618
+ backdrop-filter: blur(0px);
619
+ }
620
+ .px-float__popup {
621
+ filter: opacity(0);
622
+ }
623
+ }
624
+
625
+ @include vt-hidden-leave {
626
+ .px-float__overlay {
627
+ pointer-events: none;
628
+ }
629
+ }
31
630
  }
32
631
  </style>
@@ -152,7 +152,7 @@ export default {
152
152
  // whether to disable scrolling when the modal is open
153
153
  disablesScroll: { type: Boolean, default: true },
154
154
 
155
- // if ture, esc key will close the popup
155
+ // if true, esc key will close the popup
156
156
  escCloses: { type: Boolean, default: true },
157
157
 
158
158
  // The starting point for the z-index (note: incremented as more layers of popups are opened
@@ -14,7 +14,9 @@ export default Vue.extend({
14
14
  }),
15
15
  destroyed() {
16
16
  const { $el: el } = this;
17
- el.parentNode.removeChild(el);
17
+ if (el.parentNode) {
18
+ el.parentNode.removeChild(el);
19
+ }
18
20
  },
19
21
  render(h) {
20
22
  const nodes = this.updatedNodes && this.updatedNodes();
@@ -531,7 +531,7 @@
531
531
  <script>
532
532
  // import { mapState } from 'vuex';
533
533
  // import Component from '~/components/Component.vue';
534
- import { PxTester, PxTest } from '@thinkpixellab/px-vue-tester';
534
+ import { PxTester, PxTest } from '@thinkpixellab-public/px-vue-tester';
535
535
  import PxToggle from './PxToggle.vue';
536
536
  import PxToggleButton from './PxToggleButton.vue';
537
537
  import PxFlex from './PxFlex.vue';
@@ -0,0 +1,105 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
4
+ <template>
5
+ <div :class="bem()">
6
+ <div :class="bem('dialog-buttons')">
7
+ <button :class="bem('button')" @click="visible = true">Toggle Dialog</button>
8
+
9
+ <px-float :visible.sync="visible" variant="dialog gray" :disablesScroll="false">
10
+ This is some content
11
+
12
+ <button :class="bem('button')" @click="innerVisible = true">Inner Toggle</button>
13
+
14
+ <!-- inner dialogs need to be used with a portal if they are contained -->
15
+ <px-float :visible.sync="innerVisible" variant="dialog blur">
16
+ This is some inner content
17
+
18
+ <button :class="bem('button')" @click="innerInnerVisible = true">
19
+ Inner Inner Toggle
20
+ </button>
21
+
22
+ <!-- inner dialogs need to be used with a portal if they are contained -->
23
+ <px-float :visible.sync="innerInnerVisible" variant="dialog blur gray">
24
+ Wow this is deep
25
+ </px-float>
26
+ </px-float>
27
+ </px-float>
28
+ </div>
29
+
30
+ <div :class="bem('position-buttons')">
31
+ <div :class="bem('button')" @click="showPosition('top')">Top</div>
32
+ <px-float
33
+ :visible.sync="positionVisible"
34
+ :placement="'top-start'"
35
+ :disablesScroll="false"
36
+ :offset="10"
37
+ >
38
+ Position: {{ position }}
39
+ </px-float>
40
+ </div>
41
+
42
+ <!-- mouse -->
43
+ <div :class="bem('button')" @click="showMouseFloat">Mouse</div>
44
+ <px-float ref="mouseFloat" :placement="'bottom-start'" variant="default sm">
45
+ Positioned with showAtCursor! (Note: needs to be done pogramatically because we only
46
+ have access to mouse coordinates in a mouse event.)
47
+ </px-float>
48
+ </div>
49
+ </template>
50
+
51
+ <script>
52
+ import PxFloat from '../../components/PxFloat.vue';
53
+ export default {
54
+ name: 'demo-px-swiper',
55
+ components: { PxFloat },
56
+ // mixins: [ Mixin ],
57
+ // props: { a: { type: Boolean, default: false } },
58
+ data() {
59
+ return {
60
+ visible: false,
61
+ innerVisible: false,
62
+ innerInnerVisible: false,
63
+ position: 'top',
64
+ positionVisible: false,
65
+ };
66
+ },
67
+ // computed: {},
68
+ // watch: {},
69
+ // mounted() {},
70
+ methods: {
71
+ showPosition(pos) {
72
+ this.position = pos;
73
+ this.positionVisible = true;
74
+ },
75
+ showMouseFloat(e) {
76
+ this.$refs.mouseFloat.showAtCursor(e);
77
+ },
78
+ },
79
+ };
80
+ </script>
81
+
82
+ <style lang="scss">
83
+ @use '../../styles/px.scss' as *;
84
+
85
+ .demo-px-swiper {
86
+ min-height: 500vh;
87
+ @include debug-zebra(rgba(black, 0.03), rgba(black, 0));
88
+ &__button {
89
+ position: relative;
90
+ margin-top: 20px;
91
+ margin-left: 20px;
92
+ @include button();
93
+ &:focus {
94
+ outline: 2px dashed accent();
95
+ outline-offset: 5px;
96
+ }
97
+ }
98
+ &__position-buttons {
99
+ @include center(0%, 50%);
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 20px;
103
+ }
104
+ }
105
+ </style>
package/dev/dev.vue CHANGED
@@ -16,6 +16,7 @@ import DemoPxSwiper from './demos/Demo-PxSwiper.vue';
16
16
  import DemoPxBaseDrag from './demos/Demo-PxBaseDrag.vue';
17
17
  import DemoPxStylesPreview from './demos/Demo-PxStylesPreview.vue';
18
18
  import DemoPxPivot from './demos/Demo-PxPivot.vue';
19
+ import DemoPxFloat from './demos/Demo-PxFloat.vue';
19
20
 
20
21
  export default Vue.extend({
21
22
  components: {
@@ -28,6 +29,7 @@ export default Vue.extend({
28
29
  DemoPxBaseDrag,
29
30
  DemoPxStylesPreview,
30
31
  DemoPxPivot,
32
+ DemoPxFloat,
31
33
  },
32
34
  name: 'ServeDev',
33
35
  data() {
@@ -40,7 +42,7 @@ export default Vue.extend({
40
42
  const params = new Proxy(new URLSearchParams(window.location.search), {
41
43
  get: (searchParams, prop) => searchParams.get(prop),
42
44
  });
43
- this.componentToRender = params.component || 'PxVueTest';
45
+ this.componentToRender = params.component || 'DemoPxFloat';
44
46
  console.log(`this.componentToRender: ${this.componentToRender}`);
45
47
  },
46
48
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": "Pixel Lab",
6
6
  "license": "MIT",
@@ -12,8 +12,9 @@
12
12
  "serve": "vue-cli-service serve dev/serve.js"
13
13
  },
14
14
  "dependencies": {
15
+ "@floating-ui/dom": "^1.0.1",
15
16
  "@popperjs/core": "^2.11.5",
16
- "@thinkpixellab-public/px-styles": "^3.6.2",
17
+ "@thinkpixellab-public/px-styles": "^3.6.3",
17
18
  "@thinkpixellab-public/px-vue-tester": "^2.0.0",
18
19
  "@thinkpixellab-public/vue-resize-directive": "^1.2.2",
19
20
  "body-scroll-lock": "^3.1.5",
package/plugins/common.js CHANGED
@@ -21,7 +21,6 @@ export default {
21
21
  Vue.prototype.cssEm = cssEm;
22
22
  Vue.prototype.cssPcnt = cssPcnt;
23
23
  Vue.prototype.cssUrl = cssUrl;
24
- Vue.prototype.isServer = isServer;
25
24
 
26
25
  // filters
27
26
  Vue.filter('aspect', aspect);
@@ -30,6 +29,15 @@ export default {
30
29
  Vue.filter('spacenator', spacenator);
31
30
  Vue.filter('date', date);
32
31
 
32
+ // mixins
33
+ Vue.mixin({
34
+ computed: {
35
+ isServer() {
36
+ return isServer();
37
+ },
38
+ },
39
+ });
40
+
33
41
  // event bus
34
42
  eventBus.install(Vue);
35
43