@thinkpixellab-public/px-vue 4.0.11 → 4.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.
@@ -0,0 +1,140 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
4
+ <template>
5
+ <button :class="bem({ selected, pending })" :style="{ color: hslaStr }">
6
+ <span />
7
+ <slot />
8
+ </button>
9
+ </template>
10
+
11
+ <script>
12
+ // import { mapState } from 'vuex';
13
+ // import Component from '~/components/Component.vue';
14
+ // import Mixin from '~/components/Mixin.vue';
15
+ import { hsvaToHsla, hslaToStr } from '../utils/color.js';
16
+
17
+ export default {
18
+ name: 'px-color-palette-button',
19
+ props: {
20
+ hsva: {
21
+ type: Object,
22
+ default: () => {
23
+ return { h: 0, s: 0, v: 0, a: 0 };
24
+ },
25
+ },
26
+ selected: { type: Boolean, default: false },
27
+ pending: { type: Boolean, default: false },
28
+ },
29
+ // data() { return { b: 0 }; },
30
+ computed: {
31
+ hslaStr() {
32
+ return hslaToStr(hsvaToHsla(this.hsva), true);
33
+ },
34
+ },
35
+ // watch: {},
36
+ // mounted() {},
37
+ // methods: {},
38
+ };
39
+ </script>
40
+
41
+ <style lang="scss">
42
+ @use '../styles/px.scss' as *;
43
+
44
+ @function color-wheel-gradient($count, $blend: true, $sat: 100%, $light: 50%) {
45
+ $grad: 'conic-gradient(';
46
+ @for $i from 1 through $count {
47
+ $color: hsl(360 - divide(360, $count) * $i, $sat, $light);
48
+ $start: percentage(divide($i - 1, $count));
49
+ $stop: percentage(divide($i, $count));
50
+ $end: if($i == $count, ')', ',');
51
+
52
+ @if $blend {
53
+ $grad: '#{$grad} #{$color} #{$start}#{$end}';
54
+ } @else {
55
+ $grad: '#{$grad} #{$color} #{$start}, #{$color} #{$stop}#{$end}';
56
+ }
57
+ }
58
+
59
+ @return unquote($grad);
60
+ }
61
+
62
+ .px-color-palette-button {
63
+ @include control-reset();
64
+
65
+ position: relative;
66
+ aspect-ratio: 1;
67
+ width: 2em;
68
+ border-radius: 50%;
69
+ cursor: pointer;
70
+ display: inline-flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+
74
+ @include transition(transform, $dur: 0.2s);
75
+
76
+ @include checkered-background(
77
+ $width: 0.25em,
78
+ $color: rgba(black, 0.1),
79
+ $color-alt: transparent
80
+ );
81
+
82
+ > span {
83
+ @include abs();
84
+ border-radius: inherit;
85
+ background-color: currentColor;
86
+ }
87
+
88
+ &:before,
89
+ &:after {
90
+ border-radius: inherit;
91
+ @include transition(all, $dur: 0.3s);
92
+ }
93
+
94
+ @include before() {
95
+ @include abs(-2px);
96
+ border: 2px solid black;
97
+ opacity: 0;
98
+ }
99
+ @include after() {
100
+ @include abs();
101
+ border: 1.5px solid white;
102
+ opacity: 0;
103
+ }
104
+
105
+ &:hover {
106
+ transform: scale(1.1);
107
+ }
108
+
109
+ &:active {
110
+ transform: scale(1) !important;
111
+ }
112
+
113
+ &--selected {
114
+ &:before,
115
+ &:after {
116
+ opacity: 1;
117
+ }
118
+ }
119
+
120
+ &--pending > span {
121
+ @include abs(-0.5px);
122
+ background-color: transparent;
123
+ background-image: color-wheel-gradient(8, false, $light: 50%);
124
+ @include after() {
125
+ @include abs(4px);
126
+ background-color: rgba(white, 0.8);
127
+ border: 0px solid white;
128
+ border-radius: 50%;
129
+ }
130
+ }
131
+
132
+ &--pending {
133
+ color: black !important;
134
+ // &:before {
135
+ // border: 1px solid rgba(black, 0.5);
136
+ // opacity: 1;
137
+ // }
138
+ }
139
+ }
140
+ </style>
@@ -0,0 +1,267 @@
1
+ <template>
2
+ <div
3
+ :class="bem()"
4
+ :style="{
5
+ '--px-color-panel-hsl': hslStr,
6
+ '--px-color-panel-hsla': hslaStr,
7
+ '--px-color-panel-hsla-transparent': hslaTransparentStr,
8
+ '--px-color-panel-hsl-hue': hueStr,
9
+ '--px-color-panel-contrast': contrastStr,
10
+ }"
11
+ >
12
+ <div ref="satval" :class="[bem('satval'), satValPanelClass]">
13
+ <div
14
+ :class="bem('sel', { satval: true })"
15
+ :style="{
16
+ left: cssPcnt(s),
17
+ top: cssPcnt(1 - v),
18
+ }"
19
+ ></div>
20
+ </div>
21
+ <div :class="bem('sliders')">
22
+ <div :class="[bem('hue'), hueSliderClass]" ref="hue">
23
+ <div
24
+ :class="bem('sel', { hue: true })"
25
+ :style="{
26
+ left: cssPcnt(h / 360),
27
+ }"
28
+ ></div>
29
+ </div>
30
+ <div v-if="alphaEnabled" :class="[bem('alpha'), alphaSliderClass]" ref="alpha">
31
+ <div
32
+ :class="bem('sel', { alpha: true })"
33
+ :style="{
34
+ left: cssPcnt(a),
35
+ }"
36
+ ></div>
37
+ </div>
38
+ </div>
39
+ <div :class="bem('swatch')"></div>
40
+ <PxIconButton
41
+ v-if="eyedropperEnabled && eyedropperSupported"
42
+ :class="[bem('eyedropper'), eyedropperClass]"
43
+ @click="setColorFromEyedropper"
44
+ :src="EyeDropperSvg"
45
+ :size="0.85"
46
+ />
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ import Drag from '../utils/drag.js';
52
+ import PxBaseColor from '../bases/PxBaseColor.vue';
53
+ import PxIconButton from './PxIconButton.vue';
54
+ import EyeDropperSvg from '../assets/icons/eyedropper.svg';
55
+
56
+ export default {
57
+ name: 'px-color-panel',
58
+ mixins: [PxBaseColor],
59
+ components: { PxIconButton },
60
+ props: {
61
+ hueSliderClass: { type: String, default: '' },
62
+ alphaSliderClass: { type: String, default: '' },
63
+ satValPanelClass: { type: String, default: '' },
64
+ eyedropperClass: { type: String, default: '' },
65
+ eyedropperEnabled: { type: Boolean, default: true },
66
+ eyedropperIcon: { type: String, default: EyeDropperSvg },
67
+ swatchVisible: { type: Boolean, default: true },
68
+ },
69
+
70
+ data() {
71
+ return {
72
+ EyeDropperSvg,
73
+ };
74
+ },
75
+
76
+ // watch: {},
77
+ mounted() {
78
+ if (this.$refs.satval) {
79
+ this.satvalDrag = new Drag(this.$refs.satval, {
80
+ startThreshold: 0,
81
+ lock: null,
82
+ onStart: this.onSatvalDrag,
83
+ onDrag: this.onSatvalDrag,
84
+ excludeSelector: this.dragExcludeSelector,
85
+ });
86
+ }
87
+
88
+ if (this.$refs.hue) {
89
+ this.hueDrag = new Drag(this.$refs.hue, {
90
+ startThreshold: 0,
91
+ lock: null,
92
+ onStart: this.onHueDrag,
93
+ onDrag: this.onHueDrag,
94
+ excludeSelector: this.dragExcludeSelector,
95
+ });
96
+ }
97
+
98
+ if (this.alphaEnabled && this.$refs.alpha) {
99
+ this.alphaDrag = new Drag(this.$refs.alpha, {
100
+ startThreshold: 0,
101
+ lock: null,
102
+ onStart: this.onAlphaDrag,
103
+ onDrag: this.onAlphaDrag,
104
+ excludeSelector: this.dragExcludeSelector,
105
+ });
106
+ }
107
+ },
108
+ beforeDestroy() {
109
+ if (this.satvalDrag) {
110
+ this.satvalDrag.destroy();
111
+ }
112
+
113
+ if (this.hueDrag) {
114
+ this.hueDrag.destroy();
115
+ }
116
+
117
+ if (this.alphaDrag) {
118
+ this.alphaDrag.destroy();
119
+ }
120
+ },
121
+ methods: {
122
+ onSatvalDrag(e) {
123
+ this.s = Math.max(0, Math.min(1, e.elementXPos));
124
+ this.v = Math.max(0, Math.min(1, 1 - e.elementYPos));
125
+ },
126
+ onHueDrag(e) {
127
+ this.h = 360 * Math.max(0, Math.min(1, e.elementXPos));
128
+ },
129
+ onAlphaDrag(e) {
130
+ this.a = Math.max(0, Math.min(1, e.elementXPos));
131
+ },
132
+ },
133
+ };
134
+ </script>
135
+
136
+ <style lang="scss">
137
+ @use '../styles/px.scss' as *;
138
+
139
+ @at-root {
140
+ :root {
141
+ --px-color-panel-aspect: 1;
142
+ --px-color-panel-gap: 0.5em;
143
+ --px-color-panel-radius: 0.5em;
144
+ --px-color-panel-slider-height: 0.5em;
145
+ }
146
+ }
147
+
148
+ .px-color-panel {
149
+ width: 20em;
150
+ display: flex;
151
+ flex-direction: column;
152
+ aspect-ratio: var(--px-color-panel-aspect);
153
+ gap: var(--px-color-panel-gap) 0;
154
+
155
+ // prettier-ignore
156
+ @include grid-art(
157
+ (
158
+ 'auto | #(0, 1fr) |',
159
+ 'satval | satval | #(0,1fr)',
160
+ 'swatch | sliders | auto',
161
+ )
162
+ );
163
+
164
+ &__satval {
165
+ grid-area: satval;
166
+ flex: 1;
167
+ position: relative;
168
+ display: block;
169
+ width: 100%;
170
+ border-radius: var(--px-color-panel-radius);
171
+
172
+ background-image: linear-gradient(rgba(0, 0, 0, 0), #000),
173
+ linear-gradient(90deg, #fff, var(--px-color-panel-hsl-hue));
174
+ }
175
+
176
+ &__sliders {
177
+ grid-area: sliders;
178
+ display: flex;
179
+ flex-direction: column;
180
+ justify-content: center;
181
+ gap: var(--px-color-panel-gap);
182
+ }
183
+
184
+ &__hue,
185
+ &__alpha {
186
+ position: relative;
187
+ display: block;
188
+ width: 100%;
189
+ height: 0.66em;
190
+ border-radius: var(--px-color-panel-radius);
191
+ flex: none;
192
+ align-self: center;
193
+ }
194
+
195
+ &__hue {
196
+ grid-area: hue;
197
+ background-image: linear-gradient(90deg, red, yellow, lime, aqua, blue, fuchsia, red);
198
+ }
199
+
200
+ &__alpha {
201
+ grid-area: alpha;
202
+
203
+ @include checkered-background(
204
+ $width: 0.25em,
205
+ $color: rgba(black, 0.1),
206
+ $color-alt: transparent
207
+ );
208
+
209
+ @include after() {
210
+ @include abs();
211
+ border-radius: inherit;
212
+ background-image: linear-gradient(
213
+ to right,
214
+ var(--px-color-panel-hsla-transparent) 0%,
215
+ var(--px-color-panel-hsl) 100%
216
+ );
217
+ }
218
+ }
219
+
220
+ &__sel {
221
+ position: absolute;
222
+ top: 100%;
223
+ left: 100%;
224
+ transform: translate(-50%, -50%);
225
+ width: 1px;
226
+ height: 1px;
227
+ width: 0.66em;
228
+ height: 0.66em;
229
+ background-color: var(--px-color-panel-hsl);
230
+ border: 1px solid white;
231
+ border-radius: 50%;
232
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
233
+ z-index: 1;
234
+
235
+ &:hover {
236
+ z-index: 2;
237
+ }
238
+
239
+ &--hue {
240
+ top: 50%;
241
+ background-color: var(--px-color-panel-hsl-hue);
242
+ }
243
+
244
+ &--alpha {
245
+ top: 50%;
246
+ background-color: var(--px-color-panel-hsla);
247
+ }
248
+ }
249
+
250
+ &__swatch {
251
+ grid-area: swatch;
252
+ aspect-ratio: 1;
253
+ border-radius: 50%;
254
+ background-color: var(--px-color-panel-hsla);
255
+ margin-inline-end: var(--px-color-panel-gap);
256
+ border: 1px solid rgba(black, 0.1);
257
+ min-height: 1.66em;
258
+ }
259
+
260
+ &__eyedropper {
261
+ grid-area: swatch;
262
+ margin-inline-end: var(--px-color-panel-gap);
263
+ color: var(--px-color-panel-contrast);
264
+ aspect-ratio: 1;
265
+ }
266
+ }
267
+ </style>
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <div :class="bem()">
3
+ <PxColorPaletteButton @click="click" :hsva="hsva" :selected="selected" :pending="pending">
4
+ <PxIcon :class="bem('icon')" v-bind="calcIconAttrs" :style="{ color: calcIconColor }" />
5
+ </PxColorPaletteButton>
6
+ <PxFloat
7
+ :popupClass="bem('popup')"
8
+ v-model:visible="popupVisible"
9
+ variant="none"
10
+ placement="bottom-center"
11
+ >
12
+ <PxColorPanel
13
+ :class="bem('panel')"
14
+ :color="hsva"
15
+ :alphaEnabled="alphaEnabled"
16
+ :eyedropperEnabled="eyedropperMode === 'popup'"
17
+ format="hsva"
18
+ @change="panelColorChange"
19
+ ></PxColorPanel>
20
+ </PxFloat>
21
+ </div>
22
+ </template>
23
+
24
+ <script>
25
+ import PxFloat from './PxFloat.vue';
26
+ import PxColorPanel from './PxColorPanel.vue';
27
+ import PxColorPaletteButton from './PxColorPaletteButton.vue';
28
+ import PxBaseColor from '../bases/PxBaseColor.vue';
29
+ import propsFilter from '../utils/propsFilter.js';
30
+ import { PxIcon, PxIconSrcOrAttrs } from './PxIcon.vue';
31
+ import EyeDropperSvg from '../assets/icons/eyedropper.svg';
32
+ import EllipsisSvg from '../assets/icons/ellipsis.svg';
33
+
34
+ const PxColorPicker = {
35
+ name: 'px-color-picker',
36
+ components: { PxFloat, PxColorPanel, PxColorPaletteButton, PxIcon },
37
+ mixins: [PxBaseColor],
38
+ props: {
39
+ /**
40
+ * An icon to display inside the button. If a string is provided, it will be interpreted as
41
+ * the src of the icon. If an object is provided, it will be passed directly to the icon
42
+ * component as props (with a filter applied to ensure no fallback attributes).
43
+ */
44
+ icon: { type: [String, Object], default: null },
45
+
46
+ /**
47
+ * Convenience prop for setting the icon if the icon prop hasn't been set. Allowed values
48
+ * are 'eyedropper' and 'ellipsis'.
49
+ */
50
+ iconFallback: { type: String, default: null },
51
+
52
+ /**
53
+ * Describes how to integrate the eyedropper into the color picker. Allowed values are:
54
+ * 'none' - don't show the eyedropper
55
+ * 'popup' - show the eyedropper in the popup
56
+ * 'only' - only show the eyedropper (instead of the standard dialog)
57
+ */
58
+ eyedropperMode: { type: String, default: 'none' },
59
+
60
+ /**
61
+ * Makes the color picker appear selected (passed to the PxColorPaletteButton component)
62
+ */
63
+ selected: { type: Boolean, default: false },
64
+
65
+ /**
66
+ * Gives the color picker a pending appearance (passed to the PxColorPaletteButton component)
67
+ */
68
+ pending: { type: Boolean, default: false },
69
+ },
70
+ data() {
71
+ return { popupVisible: false };
72
+ },
73
+ computed: {
74
+ calcIconAttrs() {
75
+ if (this.icon) {
76
+ return PxIconSrcOrAttrs(this.icon);
77
+ } else if (this.iconFallback) {
78
+ switch (this.iconFallback) {
79
+ case 'eyedropper':
80
+ return { src: EyeDropperSvg, size: 0.9 };
81
+ case 'ellipsis':
82
+ return { src: EllipsisSvg };
83
+ }
84
+ }
85
+ },
86
+ calcIconColor() {
87
+ if (this.pending) {
88
+ return null;
89
+ }
90
+ return this.contrastStr;
91
+ },
92
+ },
93
+ // watch: {},
94
+ // mounted() {},
95
+ methods: {
96
+ panelColorChange(hsva) {
97
+ this.setColorHsva(hsva);
98
+ },
99
+ click() {
100
+ this.$emit('click');
101
+ if (this.eyedropperMode === 'only') {
102
+ this.setColorFromEyedropper();
103
+ } else {
104
+ this.popupVisible = true;
105
+ }
106
+ },
107
+ },
108
+ };
109
+
110
+ // helper function that filters out props that are not defined in the component when using v-bind to
111
+ // set PxColorFilter props
112
+ function PxColorPickerPropsFilter(attrs) {
113
+ return propsFilter(PxColorPicker)(attrs);
114
+ }
115
+
116
+ // defaut export
117
+ export default PxColorPicker;
118
+
119
+ // named exports
120
+ export { PxColorPicker, PxColorPickerPropsFilter };
121
+ </script>
122
+
123
+ <style lang="scss">
124
+ @use '../styles/px.scss' as *;
125
+
126
+ @at-root {
127
+ :root {
128
+ --px-color-picker-popup-bg: #{clr(page-bg)};
129
+ }
130
+ }
131
+ .px-color-picker {
132
+ &__panel {
133
+ width: 10em;
134
+ }
135
+
136
+ &__popup {
137
+ padding: 0.66em;
138
+ border-radius: 0.75em;
139
+
140
+ background-color: var(--px-color-picker-popup-bg);
141
+ @include depth-shadow(20, 0.15);
142
+ }
143
+ }
144
+ </style>
@@ -32,7 +32,7 @@ export default {
32
32
  wrap: { type: Boolean, default: false },
33
33
 
34
34
  /** Equivalent to the align-items css property (aligns items relative to each other). */
35
- align: { type: String, default: 'center', validator: val => alignments.includes(val) },
35
+ align: { type: String, default: 'start', validator: val => alignments.includes(val) },
36
36
 
37
37
  /** Maps to the justify-content css property (justifies the content within the container). */
38
38
  justify: { type: String, default: 'start', validator: val => alignments.includes(val) },
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <component :class="bem()" v-if="svgComponent" :is="svgComponent" v-bind="bindAttrs" />
2
+ <PxSvgRender v-if="isSvgString" :class="bem()" :svg="svgString" v-bind="bindAttrs" />
3
3
  <inline-svg
4
4
  v-else
5
5
  :class="bem()"
@@ -13,6 +13,7 @@
13
13
  <script>
14
14
  import InlineSvg from 'vue-inline-svg';
15
15
  import PxBaseSvg from '../bases/PxBaseSvg.vue';
16
+ import PxSvgRender from './PxSvgRender.vue';
16
17
  import { cssNum, cssEmFallback, cssPx } from '../utils/cssStr.js';
17
18
  import propsFilter from '../utils/propsFilter.js';
18
19
 
@@ -70,7 +71,7 @@ export default defineNuxtPlugin(nuxtApp => {
70
71
  const PxIcon = {
71
72
  name: 'px-icon',
72
73
  mixins: [PxBaseSvg],
73
- components: { InlineSvg },
74
+ components: { InlineSvg, PxSvgRender },
74
75
  props: {
75
76
  /**
76
77
  * A string that either represents the key (name) of an app-level icon or the svg src for
@@ -183,8 +184,6 @@ const PxIcon = {
183
184
  return this.transformDefault(svg);
184
185
  },
185
186
  },
186
-
187
- // mounted() {},
188
187
  };
189
188
 
190
189
  // helper function that filters out props that are not defined in the component when using v-bind to
@@ -193,11 +192,26 @@ function PxIconPropsFilter(attrs) {
193
192
  return propsFilter(PxIcon)(attrs);
194
193
  }
195
194
 
195
+ // helper function that makes that creates PxIcon attributes from a src string or attrs object
196
+ function PxIconSrcOrAttrs(srcOrAttrs, size = 1) {
197
+ if (!srcOrAttrs) {
198
+ return null;
199
+ }
200
+
201
+ // if the src is a string, we assume it is the src attribute
202
+ if (typeof srcOrAttrs === 'string') {
203
+ return { src: srcOrAttrs, size };
204
+ }
205
+
206
+ // if the src is an object, we assume it is the attrs object
207
+ return PxIconPropsFilter(srcOrAttrs);
208
+ }
209
+
196
210
  // defaut export
197
211
  export default PxIcon;
198
212
 
199
213
  // named exports
200
- export { PxIcon, PxIconPropsFilter };
214
+ export { PxIcon, PxIconPropsFilter, PxIconSrcOrAttrs };
201
215
  </script>
202
216
 
203
217
  <style lang="scss">
@@ -14,7 +14,7 @@
14
14
  </template>
15
15
 
16
16
  <script>
17
- import { PxIcon, PxIconPropsFilter } from './PxIcon.vue';
17
+ import { PxIcon, PxIconSrcOrAttrs } from './PxIcon.vue';
18
18
  import PxBase from '../bases/PxBase.vue';
19
19
  import PxBaseToggle from '../bases/PxBaseToggle.vue';
20
20
 
@@ -75,14 +75,7 @@ export default {
75
75
  };
76
76
  }
77
77
 
78
- if (typeof this.icon === 'string') {
79
- return {
80
- src: this.icon,
81
- size: this.size || 1,
82
- };
83
- }
84
-
85
- return PxIconPropsFilter(this.icon);
78
+ return PxIconSrcOrAttrs(this.icon);
86
79
  },
87
80
 
88
81
  calcIconChecked() {
@@ -132,6 +125,8 @@ export default {
132
125
  .px-icon-button {
133
126
  // VARIANTS
134
127
 
128
+ @include control-reset();
129
+
135
130
  &--default {
136
131
  @include controls.button-icon(
137
132
  (
@@ -0,0 +1,54 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
4
+ <template>
5
+ <div :class="bem({ ...variantsMap })">
6
+ <label :for="calcInputId" :class="[bem('label'), labelClass]">
7
+ <slot name="label">
8
+ {{ label }}
9
+ </slot>
10
+ </label>
11
+ <slot name="default" :id="calcInputId" />
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import PxBaseUniqueId from '../bases/PxBaseUniqueId.vue';
17
+ import PxBaseVariants from '../bases/PxBaseVariants.vue';
18
+
19
+ export default {
20
+ name: 'px-labeled-input',
21
+ // components: { Component },
22
+ mixins: [PxBaseUniqueId, PxBaseVariants],
23
+ props: {
24
+ label: { type: String, default: 'Input' },
25
+ labelClass: { type: [String, Array], default: null },
26
+ prefix: { type: String, default: 'input' },
27
+ inputId: { type: String, default: null },
28
+ },
29
+ // data() { return { b: 0 }; },
30
+ computed: {
31
+ calcInputId() {
32
+ if (this.inputId) {
33
+ return this.inputId;
34
+ } else {
35
+ return this.uniqueId(this.prefix);
36
+ }
37
+ },
38
+ },
39
+ // watch: {},
40
+ // mounted() {},
41
+ // methods: {},
42
+ };
43
+ </script>
44
+
45
+ <style lang="scss">
46
+ @use '../styles/px.scss' as *;
47
+
48
+ .px-labeled-input {
49
+ display: flex;
50
+ align-items: flex-start;
51
+ flex-direction: column;
52
+ gap: 0.5em;
53
+ }
54
+ </style>