@thinkpixellab-public/px-vue 4.0.17 → 4.0.19

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.
@@ -1 +1,7 @@
1
- <svg viewBox="0 0 24 24"><g transform="translate(4 11)" fill="currentColor" 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>
1
+ <svg viewBox="0 0 24 24">
2
+ <g transform="translate(4 11)" fill="currentColor" fill-rule="evenodd">
3
+ <circle cx="1.5" cy="1.5" r="1.5"/>
4
+ <circle cx="8" cy="1.5" r="1.5"/>
5
+ <circle cx="14.5" cy="1.5" r="1.5"/>
6
+ </g>
7
+ </svg>
@@ -1 +1,5 @@
1
- <svg viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" 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>
1
+ <svg viewBox="0 0 24 24">
2
+ <g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="2">
3
+ <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"/>
4
+ </g>
5
+ </svg>
@@ -43,6 +43,7 @@ export default {
43
43
  return {
44
44
  selectedKeys: [],
45
45
  baseUpdateMode: 'single',
46
+ isMounted: false,
46
47
  };
47
48
  },
48
49
 
@@ -157,11 +158,16 @@ export default {
157
158
  },
158
159
 
159
160
  mounted() {
161
+ this.isMounted = true;
160
162
  this.$nextTick(() => {
161
163
  this.tryAutoSelect();
162
164
  });
163
165
  },
164
166
 
167
+ unmounted() {
168
+ this.isMounted = false;
169
+ },
170
+
165
171
  methods: {
166
172
  tryAutoSelect() {
167
173
  if (this.autoSelectFirst && isKeySetEmpty(this.selected) && this.items?.length) {
@@ -173,6 +179,10 @@ export default {
173
179
  * Emit the change / update:selected event
174
180
  */
175
181
  emitUpdateEvent() {
182
+ if (!this.isMounted) {
183
+ return;
184
+ }
185
+
176
186
  let updateArg;
177
187
 
178
188
  if (this.updateMode == 'single') {
@@ -207,7 +207,8 @@ export default {
207
207
  &__button {
208
208
  position: relative;
209
209
  align-self: center;
210
- padding: 1em 0.75em;
210
+ padding: 0;
211
+ height: 1.25em;
211
212
 
212
213
  &--start {
213
214
  grid-area: arrow-start;
@@ -242,6 +243,9 @@ export default {
242
243
  padding-left: 0;
243
244
  padding-right: 0;
244
245
  }
246
+
247
+ min-height: 0.8em;
248
+ height: 100%;
245
249
  }
246
250
  }
247
251
 
@@ -22,6 +22,9 @@
22
22
  {{ text }}
23
23
  </slot>
24
24
  </component>
25
+
26
+ <!-- after -->
27
+ <slot name="after"></slot>
25
28
  </component>
26
29
  </template>
27
30
 
@@ -0,0 +1,187 @@
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
+ // TODO: default is hsva, but that isn't in the TINY_FORMATS list
89
+ // if (this.format) {
90
+ // if (!TINY_FORMATS.includes(this.format)) {
91
+ // console.warn(
92
+ // `[PxBaseColor] Invalid format: "${this.format}". See comments in PxBaseColor.vue`,
93
+ // );
94
+ // }
95
+ // }
96
+ },
97
+ methods: {
98
+ setColor(strOrHsva) {
99
+ if (typeof strOrHsva === 'string') {
100
+ this.setColorStr(strOrHsva);
101
+ }
102
+
103
+ if (typeof strOrHsva === 'object') {
104
+ this.setColorHsva(strOrHsva);
105
+ }
106
+ },
107
+
108
+ setColorStr(str) {
109
+ // this is for efficiency but also to prevent color creep as we convert between formats
110
+ if (this.lastEmit == str) {
111
+ return;
112
+ }
113
+
114
+ const tiny = tinycolor(str);
115
+ this.autoFormat = tiny.getFormat();
116
+
117
+ const hsva = tiny.toHsv();
118
+ this.setColorHsva(hsva);
119
+ },
120
+
121
+ setColorHsva(hsva) {
122
+ if (this.isCurrentHsva(hsva)) {
123
+ return;
124
+ }
125
+
126
+ this.h = hsva.h;
127
+ this.s = hsva.s;
128
+ this.v = hsva.v;
129
+ this.a = hsva.a;
130
+ },
131
+
132
+ emitChange() {
133
+ const clr = this.getColor(this.format);
134
+
135
+ this.lastEmit = clr;
136
+
137
+ this.$emit('update:color', clr);
138
+ this.$emit('change', clr);
139
+ },
140
+
141
+ getColor(format = null) {
142
+ format = format || this.format;
143
+
144
+ if (format == 'auto') {
145
+ format = this.autoFormat;
146
+ }
147
+
148
+ let clr = { h: this.h, s: this.s, v: this.v, a: this.a };
149
+
150
+ if (format == 'hsva') {
151
+ return clr;
152
+ }
153
+
154
+ if (format && [...TINY_FORMATS, 'tiny'].includes(format)) {
155
+ const tiny = tinycolor(clr);
156
+
157
+ if (format == 'tiny') {
158
+ clr = tiny;
159
+ } else {
160
+ clr = tiny.toString(format);
161
+ }
162
+ }
163
+
164
+ return clr;
165
+ },
166
+
167
+ colorStrToHsva(str) {
168
+ const tiny = tinycolor(str);
169
+ return tiny.toHsv();
170
+ },
171
+
172
+ isCurrentHsva(hOrObj, s, v, a) {
173
+ const hsvaCompare = hsvaEnsureObject(hOrObj, s, v, a);
174
+ return hsvaIsEqual(this.hsva, hsvaCompare);
175
+ },
176
+
177
+ setColorFromEyedropper() {
178
+ if (this.eyedropperSupported) {
179
+ const eyeDropper = new EyeDropper();
180
+ eyeDropper.open().then(result => {
181
+ this.setColorStr(result.sRGBHex);
182
+ });
183
+ }
184
+ },
185
+ },
186
+ };
187
+ </script>
@@ -1,3 +1,6 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
1
4
  <template>
2
5
  <div :class="bem()">
3
6
  <PxColorPaletteButton
@@ -20,10 +23,10 @@
20
23
  </template>
21
24
 
22
25
  <script>
23
- import PxBaseColor from '../bases/PxBaseColor.vue';
26
+ import PxBaseColor from './PxBaseColor.vue';
24
27
  import tinycolor from 'tinycolor2';
25
28
  import PxColorPaletteButton from './PxColorPaletteButton.vue';
26
- import { PxColorPicker, PxColorPickerPropsFilter } from './PxColorPicker.vue';
29
+ import PxColorPicker from './PxColorPicker.vue';
27
30
 
28
31
  export default {
29
32
  name: 'px-color-palette',
@@ -74,12 +77,12 @@ export default {
74
77
  'eyedropper-only': 'eyedropper',
75
78
  }[this.pickerMode];
76
79
 
77
- return PxColorPickerPropsFilter({
80
+ return {
78
81
  iconFallback,
79
82
  eyedropperMode,
80
83
  alphaEnabled: this.alphaEnabled,
81
84
  ...this.pickerAttrs,
82
- });
85
+ };
83
86
  },
84
87
  showPicker() {
85
88
  return this.pickerMode != 'none';
@@ -138,9 +138,6 @@ export default {
138
138
  return this.getItemLabel(this.selectedItem);
139
139
  },
140
140
  },
141
- mounted() {
142
- this.select;
143
- },
144
141
  methods: {
145
142
  itemClick(item) {
146
143
  this.selectItemOnly(item);
@@ -9,7 +9,7 @@
9
9
  "
10
10
  @click="tryToggleChecked"
11
11
  >
12
- <px-icon v-bind="calcIconAttrs" />
12
+ <px-icon :class="iconClass" v-bind="calcIconAttrs" />
13
13
  </button>
14
14
  </template>
15
15
 
@@ -65,6 +65,8 @@ export default {
65
65
  * Set this to true to enable toggling on the button (see PxBaseToggle for details).
66
66
  **/
67
67
  toggles: { type: Boolean, default: false },
68
+
69
+ iconClass: { type: String, default: null },
68
70
  },
69
71
  computed: {
70
72
  calcIcon() {
@@ -128,7 +128,13 @@ export default {
128
128
  return;
129
129
  }
130
130
 
131
- const selected = this.$refs[this.getTabContentRefName(this.selectedItem)][0];
131
+ const refName = this.getTabContentRefName(this.selectedItem);
132
+
133
+ if (!this.$refs[refName]) {
134
+ return;
135
+ }
136
+
137
+ const selected = this.$refs[refName][0];
132
138
  const selectedRect = getClientRect(selected, this.$refs.tabs);
133
139
 
134
140
  if (selected) {
@@ -57,9 +57,6 @@ export default {
57
57
  mixins: [PxBaseItemsSelect, PxBaseVariants],
58
58
  components: { PxToggleButton, PxRadioButton },
59
59
  props: {
60
- // // spacing between items (see PxFlex for details)
61
- // gap: { default: 0.5 },
62
-
63
60
  /**
64
61
  * The icon that gets displayed when a button is checked
65
62
  */
@@ -118,7 +115,7 @@ export default {
118
115
 
119
116
  // variant: connected
120
117
 
121
- gap: 0.25em;
118
+ gap: 0.6em;
122
119
  display: flex;
123
120
 
124
121
  &--connected {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "4.0.17",
3
+ "version": "4.0.19",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": {
6
6
  "name": "Pixel Lab"
@@ -23,7 +23,7 @@
23
23
  "@floating-ui/dom": "^1.6.7",
24
24
  "@popperjs/core": "^2.11.8",
25
25
  "@storybook/test": "^8.2.6",
26
- "@thinkpixellab-public/px-styles": "^4.0.2",
26
+ "@thinkpixellab-public/px-styles": "^4.0.3",
27
27
  "@thinkpixellab-public/px-vue-tester": "^2.0.0",
28
28
  "@thinkpixellab-public/vue-resize-directive": "^1.2.2",
29
29
  "body-scroll-lock": "^3.1.5",
@@ -0,0 +1,213 @@
1
+ class HeightGroup {
2
+ constructor(id, ops = {}) {
3
+ this.id = id;
4
+
5
+ this.ops = {
6
+ // set to null to disable css variables
7
+ varsParent: window.document.body,
8
+
9
+ // debounce time in ms
10
+ debounce: 0,
11
+
12
+ // css variable prefix (e.g. --hg-groupId-h)
13
+ prefix: 'hg',
14
+
15
+ // ignore heights that are larger than this value in pixels
16
+ ignoreMax: null,
17
+
18
+ // ignore heights that are larger than this value as a standard deviation of the mean height
19
+ ignoreVariance: null,
20
+
21
+ // debug mode (adds some loging that can help with debugging)
22
+ debug: false,
23
+
24
+ changeCallback: null,
25
+
26
+ ...ops,
27
+ };
28
+
29
+ this.max = null;
30
+ this.elements = [];
31
+ this.currentMaxElements = [];
32
+ }
33
+
34
+ updateOptions(ops) {
35
+ this.ops = { ...this.ops, ...ops };
36
+ }
37
+
38
+ getVariableName() {
39
+ return '--' + (this.ops.prefix ? this.ops.prefix + '-' : '') + this.id + '-h';
40
+ }
41
+
42
+ trySync() {
43
+ clearTimeout(this.lastTimeout);
44
+
45
+ this.lastTimeout = setTimeout(() => {
46
+ let heights = this.elements.map(el => el.height);
47
+ let ignore = Number.MAX_VALUE;
48
+
49
+ if (this.ops.ignoreVariance) {
50
+ const mean = heights.reduce((sum, height) => sum + height, 0) / heights.length;
51
+ const variance =
52
+ heights.reduce((sum, height) => sum + Math.pow(height - mean, 2), 0) /
53
+ heights.length;
54
+ const stdDev = Math.sqrt(variance);
55
+
56
+ if (this.ops.debug) {
57
+ console.log(`stdDev: ${stdDev}`);
58
+ }
59
+
60
+ ignore = this.ops.ignoreVariance * stdDev + mean;
61
+ }
62
+
63
+ if (this.ops.ignoreMax) {
64
+ ignore = Math.min(ignore, this.ops.ignoreMax);
65
+ }
66
+
67
+ const max = Math.max(...heights.filter(height => height <= ignore));
68
+
69
+ if (this.max !== max) {
70
+ this.max = max;
71
+
72
+ // store the elements that have the max height
73
+ this.currentMaxElements = this.elements.filter(el => el.height === max);
74
+
75
+ if (this.ops.varsParent) {
76
+ this.ops.varsParent.style.setProperty(this.getVariableName(), max + 'px');
77
+ }
78
+
79
+ if (this.ops.changeCallback) {
80
+ this.ops.changeCallback(this.id, this.max);
81
+ }
82
+ }
83
+
84
+ if (this.ops.debug) {
85
+ console.log(`ignore: ${ignore}`);
86
+ console.log(
87
+ 'valid heights:',
88
+ heights.filter(height => height <= ignore)
89
+ );
90
+ console.log(
91
+ 'invalid heights:',
92
+ heights.filter(height => height > ignore)
93
+ );
94
+ }
95
+ }, this.ops.debounce);
96
+ }
97
+
98
+ updateElement(element, height) {
99
+ if (!element) {
100
+ return;
101
+ }
102
+
103
+ let el = this.elements.find(el => el.element == element);
104
+
105
+ if (!el) {
106
+ el = { element, height: null };
107
+ this.elements.push(el);
108
+ }
109
+
110
+ if (el.height !== height) {
111
+ el.height = height;
112
+
113
+ if (this.currentMaxElements.includes(el)) {
114
+ // this element was the max height, so we need to recalculate the max height even if smaller
115
+ this.max = null;
116
+ }
117
+
118
+ this.trySync();
119
+ }
120
+ }
121
+
122
+ removeElement(element) {
123
+ const el = this.elements.find(el => el.element === element);
124
+ if (el) {
125
+ this.elements = this.elements.filter(el => el.element !== element);
126
+
127
+ if (this.currentMaxElements.includes(el.element)) {
128
+ this.currentMaxElements = [];
129
+ this.max = null;
130
+ }
131
+
132
+ this.trySync();
133
+ }
134
+ }
135
+ }
136
+
137
+ export default {
138
+ getGroups() {
139
+ if (!window.$heightGroups) {
140
+ window.$heightGroups = {};
141
+ }
142
+
143
+ return window.$heightGroups;
144
+ },
145
+
146
+ getGroup(id) {
147
+ if (!id) {
148
+ return null;
149
+ }
150
+
151
+ const groups = this.getGroups();
152
+
153
+ if (id in groups) {
154
+ return groups[id];
155
+ }
156
+
157
+ return null;
158
+ },
159
+
160
+ addGroup(group) {
161
+ const groups = this.getGroups();
162
+ groups[group.id] = group;
163
+ },
164
+
165
+ createGroup(id, ops) {
166
+ if (!id) {
167
+ return null;
168
+ }
169
+
170
+ const group = new HeightGroup(id, ops);
171
+ this.addGroup(group);
172
+
173
+ return group;
174
+ },
175
+ };
176
+
177
+ export class SharedHeightScopeManager {
178
+ constructor(scopeElement, changeCallback) {
179
+ this.groups = {};
180
+ this.scopeElement = scopeElement;
181
+ this.groupMaxChanged = changeCallback;
182
+ }
183
+
184
+ getGroup(id) {
185
+ if (!id) {
186
+ return null;
187
+ }
188
+
189
+ if (id in this.groups) {
190
+ return this.groups[id];
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ addGroup(group) {
197
+ this.groups[group.id] = group;
198
+ }
199
+
200
+ createGroup(id, ops = {}) {
201
+ if (!id) {
202
+ return null;
203
+ }
204
+
205
+ ops.varsParent = this.scopeElement;
206
+ ops.changeCallback = this.groupMaxChanged;
207
+
208
+ const group = new HeightGroup(id, ops);
209
+ this.addGroup(group);
210
+
211
+ return group;
212
+ }
213
+ }
@@ -36,4 +36,4 @@ const getFontFromElement = element => {
36
36
  };
37
37
 
38
38
  export default textWidth;
39
- export { getFontFromElement };
39
+ export { textWidth, getFontFromElement };