@thinkpixellab-public/px-vue 4.0.11 → 4.0.12

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 @@
1
+ <svg viewBox="0 0 24 24"><g transform="translate(4 11)" fill="#000" fill-rule="evenodd"><circle cx="1.5" cy="1.5" r="1.5"/><circle cx="8" cy="1.5" r="1.5"/><circle cx="14.5" cy="1.5" r="1.5"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="#000" stroke-width="2"><path d="M16.716 2.498a3.067 3.067 0 0 1 2.962-.793c1.059.283 2.654 1.878 2.937 2.937a3.067 3.067 0 0 1-.793 2.962l-4.152 4.152-.538-.537-9.72 9.72L1.6 22.72l1.78-5.811 9.721-9.721-.537-.538 4.152-4.152ZM11.315 5.402l7.526 7.526"/></g></svg>
@@ -0,0 +1,186 @@
1
+ <script>
2
+ import tinycolor from 'tinycolor2';
3
+ import {
4
+ hsvaEnsureObject,
5
+ hsvaIsEqual,
6
+ hsvaToHsla,
7
+ hslaToStr,
8
+ hsvaContrastColor,
9
+ } from '../utils/color.js';
10
+
11
+ const TINY_FORMATS = ['rgb', 'prgb', 'hex6', 'hex3', 'hex8', 'name', 'hsl', 'hsv'];
12
+
13
+ export default {
14
+ name: 'px-color-picker',
15
+
16
+ props: {
17
+ /**
18
+ * The color value.
19
+ */
20
+ color: { default: '#FF0000' },
21
+
22
+ /**
23
+ * The format of the color value (used for emitting events, v-model, and the default format
24
+ * for getColor). Allowed values are the same as those available from the tinycolor library
25
+ * (rgb, prgb, hex6, hex3, hex8, name, hsl, hsv) as well as 'auto' (which will attempt to
26
+ * use the same format as the last value set with the color prop) and 'tiny' (which will
27
+ * return a tinycolor object) and 'hsva' which will return hsva values as an object. The
28
+ * format has the advantage of not requiring a tinycolor object to be created when the color
29
+ * changes.
30
+ *
31
+ * @values rgb, prgb, hex6, hex3, hex8, name, hsl, hsv, auto, tiny
32
+ */
33
+ format: { type: String, default: 'hsva' },
34
+
35
+ /**
36
+ * Whether alpha colors are enabled.
37
+ */
38
+ alphaEnabled: { type: Boolean, default: false },
39
+ },
40
+
41
+ data() {
42
+ return { autoFormat: this.format, h: 0, s: 0, v: 0, a: 1 };
43
+ },
44
+
45
+ computed: {
46
+ hsva() {
47
+ return { h: this.h, s: this.s, v: this.v, a: this.alphaEnabled ? this.a : 1 };
48
+ },
49
+ hsla() {
50
+ return hsvaToHsla(this.hsva);
51
+ },
52
+ hslStr() {
53
+ return hslaToStr(this.hsla, false);
54
+ },
55
+ hslaStr() {
56
+ return hslaToStr(this.hsla, true);
57
+ },
58
+ hslaTransparentStr() {
59
+ return hslaToStr({ ...this.hsla, a: 0 }, true);
60
+ },
61
+ hueStr() {
62
+ return `hsl(${this.h}, 100%, 50%)`;
63
+ },
64
+ contrastStr() {
65
+ return hsvaContrastColor(this.hsva);
66
+ },
67
+ eyedropperSupported() {
68
+ return window?.EyeDropper;
69
+ },
70
+ },
71
+
72
+ watch: {
73
+ color(nv) {
74
+ // update colorValue when color changes
75
+ this.setColor(nv);
76
+ },
77
+ hsla() {
78
+ // emit change when this changes as a proxy for changes to hsva as individual props
79
+ this.emitChange();
80
+ },
81
+ },
82
+
83
+ mounted() {
84
+ if (this.color) {
85
+ this.setColor(this.color);
86
+ }
87
+
88
+ if (this.format) {
89
+ if (!TINY_FORMATS.includes(this.format)) {
90
+ console.warn(
91
+ `[PxBaseColor] Invalid format: "${this.format}". See comments in PxBaseColor.vue`,
92
+ );
93
+ }
94
+ }
95
+ },
96
+ methods: {
97
+ setColor(strOrHsva) {
98
+ if (typeof strOrHsva === 'string') {
99
+ this.setColorStr(strOrHsva);
100
+ }
101
+
102
+ if (typeof strOrHsva === 'object') {
103
+ this.setColorHsva(strOrHsva);
104
+ }
105
+ },
106
+
107
+ setColorStr(str) {
108
+ // this is for efficiency but also to prevent color creep as we convert between formats
109
+ if (this.lastEmit == str) {
110
+ return;
111
+ }
112
+
113
+ const tiny = tinycolor(str);
114
+ this.autoFormat = tiny.getFormat();
115
+
116
+ const hsva = tiny.toHsv();
117
+ this.setColorHsva(hsva);
118
+ },
119
+
120
+ setColorHsva(hsva) {
121
+ if (this.isCurrentHsva(hsva)) {
122
+ return;
123
+ }
124
+
125
+ this.h = hsva.h;
126
+ this.s = hsva.s;
127
+ this.v = hsva.v;
128
+ this.a = hsva.a;
129
+ },
130
+
131
+ emitChange() {
132
+ const clr = this.getColor(this.format);
133
+
134
+ this.lastEmit = clr;
135
+
136
+ this.$emit('update:color', clr);
137
+ this.$emit('change', clr);
138
+ },
139
+
140
+ getColor(format = null) {
141
+ format = format || this.format;
142
+
143
+ if (format == 'auto') {
144
+ format = this.autoFormat;
145
+ }
146
+
147
+ let clr = { h: this.h, s: this.s, v: this.v, a: this.a };
148
+
149
+ if (format == 'hsva') {
150
+ return clr;
151
+ }
152
+
153
+ if (format && [...TINY_FORMATS, 'tiny'].includes(format)) {
154
+ const tiny = tinycolor(clr);
155
+
156
+ if (format == 'tiny') {
157
+ clr = tiny;
158
+ } else {
159
+ clr = tiny.toString(format);
160
+ }
161
+ }
162
+
163
+ return clr;
164
+ },
165
+
166
+ colorStrToHsva(str) {
167
+ const tiny = tinycolor(str);
168
+ return tiny.toHsv();
169
+ },
170
+
171
+ isCurrentHsva(hOrObj, s, v, a) {
172
+ const hsvaCompare = hsvaEnsureObject(hOrObj, s, v, a);
173
+ return hsvaIsEqual(this.hsva, hsvaCompare);
174
+ },
175
+
176
+ setColorFromEyedropper() {
177
+ if (this.eyedropperSupported) {
178
+ const eyeDropper = new EyeDropper();
179
+ eyeDropper.open().then(result => {
180
+ this.setColorStr(result.sRGBHex);
181
+ });
182
+ }
183
+ },
184
+ },
185
+ };
186
+ </script>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { markRaw } from 'vue';
2
+ import isServer from '../utils/isServer';
3
3
  import { cssEmFallback } from '../utils/cssStr.js';
4
4
 
5
5
  export default {
@@ -133,24 +133,22 @@ export default {
133
133
  calcStyle() {
134
134
  return null;
135
135
  },
136
- },
137
- watch: {
138
- src: {
139
- immediate: true,
140
- handler(nv) {
141
- const isSvgStr = nv && nv.includes && nv.includes('<svg');
142
-
143
- if (isSvgStr) {
144
- const svgComponent = {
145
- template: this.transformSvgString(nv),
146
- };
147
- this.svgComponent = markRaw(svgComponent);
148
- }
149
- },
136
+ isSvgString() {
137
+ return this.src && this.src.includes('<svg');
138
+ },
139
+ svgString() {
140
+ if (this.isSvgString) {
141
+ return this.transformSvgString(this.src);
142
+ }
150
143
  },
151
144
  },
145
+
152
146
  methods: {
153
147
  transformSvgString(svg) {
148
+ if (isServer() || typeof DOMParser === undefined) {
149
+ return svg;
150
+ }
151
+
154
152
  const parser = new DOMParser();
155
153
  const svgElement = parser.parseFromString(svg, 'image/svg+xml').querySelector('svg');
156
154
  const transformed = this.transform(svgElement);
@@ -163,33 +161,31 @@ export default {
163
161
  return;
164
162
  }
165
163
 
166
- if (!svg?.viewBox) {
167
- return svg;
164
+ if (svg.viewBox) {
165
+ // only a valid viewbox if it has both width/height
166
+ let viewBox =
167
+ svg.viewBox.baseVal.width && svg.viewBox.baseVal.height
168
+ ? svg.viewBox.baseVal
169
+ : null;
170
+
171
+ // we only support width/height set in px (unitType = 1 or 5)
172
+ let width =
173
+ svg.width.baseVal.unitType == 1 || svg.width.baseVal.unitType == 5
174
+ ? parseFloat(svg.width.baseVal.value)
175
+ : viewBox.width;
176
+ let height =
177
+ svg.height.baseVal.unitType == 1 || svg.height.baseVal.unitType == 5
178
+ ? parseFloat(svg.height.baseVal.value)
179
+ : viewBox.height;
180
+
181
+ // save as data values
182
+ this.svgViewBox = viewBox
183
+ ? `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`
184
+ : `0 0 ${width} ${height}`;
185
+ this.svgWidth = width;
186
+ this.svgHeight = height;
168
187
  }
169
188
 
170
- // only a valid viewbox if it has both width/height
171
- let viewBox =
172
- svg.viewBox.baseVal.width && svg.viewBox.baseVal.height
173
- ? svg.viewBox.baseVal
174
- : null;
175
-
176
- // we only support width/height set in px (unitType = 1 or 5)
177
- let width =
178
- svg.width.baseVal.unitType == 1 || svg.width.baseVal.unitType == 5
179
- ? parseFloat(svg.width.baseVal.value)
180
- : viewBox.width;
181
- let height =
182
- svg.height.baseVal.unitType == 1 || svg.height.baseVal.unitType == 5
183
- ? parseFloat(svg.height.baseVal.value)
184
- : viewBox.height;
185
-
186
- // save as data values
187
- this.svgViewBox = viewBox
188
- ? `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`
189
- : `0 0 ${width} ${height}`;
190
- this.svgWidth = width;
191
- this.svgHeight = height;
192
-
193
189
  // string replace
194
190
  if (this.replacements) {
195
191
  let inner = svg.innerHTML;
@@ -1,11 +1,9 @@
1
- <!--
2
- #VUE3 Todo
3
- -->
4
-
5
1
  <script>
6
- // TODO: this is currently not compatible with SSR
7
-
8
2
  export default {
3
+ beforeCreate() {
4
+ const id = typeof useId === 'function' ? useId() : Math.floor(Math.random() * 0x10000);
5
+ this.$__uniqueId = id;
6
+ },
9
7
  methods: {
10
8
  /**
11
9
  * Get a unique id derived from vue's unique component identifier (this.$.uid) for use when
@@ -20,8 +18,7 @@ export default {
20
18
 
21
19
  uniqueId(prefix = null) {
22
20
  prefix = typeof prefix === 'string' ? prefix.trim() + '-' : '';
23
-
24
- return prefix + ('000000' + Number(this.$.uid).toString(16).toLowerCase()).slice(-4);
21
+ return prefix + this.$__uniqueId;
25
22
  },
26
23
  },
27
24
  };
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <div :class="bem()">
3
+ <pre
4
+ v-if="loaded"
5
+ ref="code"
6
+ class="px-code-sample"
7
+ :class="lang"
8
+ ><code v-html="highlightedCode"></code></pre>
9
+ <PxCopyText
10
+ v-if="showCopyButton"
11
+ :class="bem('copy-button')"
12
+ :text="code"
13
+ :showText="false"
14
+ ></PxCopyText>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import hljs from 'highlight.js/lib/core';
20
+ import javascript from 'highlight.js/lib/languages/javascript';
21
+ import xml from 'highlight.js/lib/languages/xml';
22
+ import css from 'highlight.js/lib/languages/css';
23
+ import PxCopyText from './PxCopyText.vue';
24
+
25
+ export default {
26
+ name: 'px-code-sample',
27
+ components: { PxCopyText },
28
+ // mixins: [ Mixin ],
29
+ props: {
30
+ code: { type: String, default: '' },
31
+ lang: { type: String, default: null },
32
+ showCopyButton: { type: Boolean, default: true },
33
+ },
34
+ data() {
35
+ return { loaded: false };
36
+ },
37
+ computed: {
38
+ highlightedCode() {
39
+ if (this.loaded) {
40
+ // remove trailing newline and spaces
41
+
42
+ const code = this.code.replace(/^\s+$/, '').replace(/^[\s\uFEFF\xA0]+/, '');
43
+
44
+ if (code) {
45
+ if (this.lang) {
46
+ return hljs.highlight(code, { language: this.lang }).value;
47
+ } else {
48
+ return hljs.highlightAuto(code).value;
49
+ }
50
+ }
51
+ }
52
+ return '';
53
+ },
54
+ },
55
+ // watch: {},
56
+ mounted() {
57
+ hljs.registerLanguage('javascript', javascript);
58
+ hljs.registerLanguage('xml', xml);
59
+ hljs.registerLanguage('css', css);
60
+
61
+ setTimeout(() => {
62
+ this.loaded = true;
63
+ });
64
+ },
65
+ // methods: {},
66
+ };
67
+ </script>
68
+
69
+ <style lang="scss">
70
+ @use '../styles/px.scss' as *;
71
+ @use '@thinkpixellab-public/px-styles/src/modules/highlightjs.scss' as *;
72
+ .px-code-sample {
73
+ position: relative;
74
+ pre {
75
+ @include highlightjs();
76
+ padding: 1em;
77
+ border-radius: 0.5em;
78
+ }
79
+
80
+ &__copy-button {
81
+ position: absolute;
82
+ top: 1em;
83
+ right: 1em;
84
+ color: white;
85
+ }
86
+ }
87
+ </style>
@@ -0,0 +1,118 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
4
+ <template>
5
+ <div :class="bem()">
6
+ <PxColorPaletteButton
7
+ v-for="(color, index) in calcPalette"
8
+ :key="index"
9
+ :hsva="color.hsva"
10
+ :selected="!(pickerActive && isCurrentHsva(pickerHsva)) && isCurrentHsva(color.hsva)"
11
+ @click="onPaletteClick(color)"
12
+ ></PxColorPaletteButton>
13
+ <PxColorPicker
14
+ v-if="showPicker"
15
+ v-bind="calcPickerAttrs"
16
+ :color="pickerHsva"
17
+ :selected="pickerActive && isCurrentHsva(pickerHsva)"
18
+ :pending="pickerPending"
19
+ @click="onPickerClick"
20
+ @change="onPickerChange"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script>
26
+ import PxBaseColor from '../bases/PxBaseColor.vue';
27
+ import tinycolor from 'tinycolor2';
28
+ import PxColorPaletteButton from './PxColorPaletteButton.vue';
29
+ import { PxColorPicker, PxColorPickerPropsFilter } from './PxColorPicker.vue';
30
+
31
+ export default {
32
+ name: 'px-color-palette',
33
+ components: { PxColorPaletteButton, PxColorPicker },
34
+ mixins: [PxBaseColor],
35
+ props: {
36
+ /**
37
+ * Array of colors to display in the palette
38
+ */
39
+ palette: { type: Array, default: null },
40
+
41
+ // none | picker | eyedropper | eyedropper-only
42
+ pickerMode: { type: String, default: 'none' },
43
+ pickerAttrs: { type: Object, default: () => {} },
44
+ pickerInitColor: { type: String, default: 'blue' },
45
+ pickerInitSelected: { type: Boolean, default: false },
46
+ },
47
+ data() {
48
+ return {
49
+ pickerActive: this.pickerInitSelected,
50
+ pickerPending: true,
51
+ pickerHsva: this.colorStrToHsva(this.pickerInitColor),
52
+ };
53
+ },
54
+ computed: {
55
+ calcPalette() {
56
+ return this.palette.map(color => {
57
+ const tiny = tinycolor(color);
58
+ return {
59
+ hsva: tiny.toHsv(),
60
+ css: tiny.toString(this.alphaEnabled ? 'hex6' : 'rgb'),
61
+ };
62
+ });
63
+ },
64
+ calcPickerAttrs() {
65
+ const eyedropperMode = {
66
+ none: 'none',
67
+ picker: 'none',
68
+ eyedropper: 'popup',
69
+ 'eyedropper-only': 'only',
70
+ }[this.pickerMode];
71
+
72
+ const iconFallback = {
73
+ none: null,
74
+ picker: 'ellipsis',
75
+ eyedropper: 'ellipsis',
76
+ 'eyedropper-only': 'eyedropper',
77
+ }[this.pickerMode];
78
+
79
+ return PxColorPickerPropsFilter({
80
+ iconFallback,
81
+ eyedropperMode,
82
+ alphaEnabled: this.alphaEnabled,
83
+ ...this.pickerAttrs,
84
+ });
85
+ },
86
+ showPicker() {
87
+ return this.pickerMode != 'none';
88
+ },
89
+ },
90
+ // watch: {},
91
+ mounted() {},
92
+ methods: {
93
+ onPaletteClick(color) {
94
+ this.pickerActive = false;
95
+ this.setColorHsva(color.hsva);
96
+ },
97
+ onPickerClick() {
98
+ this.pickerActive = true;
99
+ },
100
+ onPickerChange(e) {
101
+ if (this.pickerActive) {
102
+ this.pickerPending = false;
103
+ this.pickerHsva = e;
104
+ this.setColorHsva(e);
105
+ }
106
+ },
107
+ },
108
+ };
109
+ </script>
110
+
111
+ <style lang="scss">
112
+ @use '../styles/px.scss' as *;
113
+
114
+ .px-color-palette {
115
+ display: flex;
116
+ gap: 0.5em;
117
+ }
118
+ </style>
@@ -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>