@signal24/vue-foundation 3.6.0 → 3.7.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Common components, directives, and helpers for Vue 3 apps",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -49,6 +49,8 @@ export default {
49
49
  props: [
50
50
  'modelValue',
51
51
  'options',
52
+ 'prependOptions',
53
+ 'appendOptions',
52
54
  'preload',
53
55
  'url',
54
56
  'urlParams',
@@ -71,7 +73,7 @@ export default {
71
73
  data() {
72
74
  return {
73
75
  isLoaded: false,
74
- resolvedOptions: [],
76
+ loadedOptions: [],
75
77
  isSearching: false,
76
78
  searchText: '',
77
79
  selectedOption: null,
@@ -83,12 +85,11 @@ export default {
83
85
  },
84
86
 
85
87
  computed: {
86
- hasManualOptionsObject() {
87
- return this.options && typeof this.options === 'object' && !Array.isArray(this.options);
88
- },
89
-
88
+ /**
89
+ * EFFECTIVE PROPS
90
+ */
90
91
  effectiveDisabled() {
91
- return this.disabled || !this.resolvedOptions;
92
+ return this.disabled || !this.loadedOptions;
92
93
  },
93
94
 
94
95
  effectivePlaceholder() {
@@ -97,47 +98,6 @@ export default {
97
98
  return this.placeholder || '';
98
99
  },
99
100
 
100
- effectiveOptions() {
101
- let options = [...this.decoratedOptions];
102
-
103
- if (this.isSearching) {
104
- const strippedSearchText = this.searchText.trim().toLowerCase();
105
-
106
- if (strippedSearchText.length) {
107
- options = options.filter(option => option.searchContent.includes(strippedSearchText));
108
-
109
- const escapedSearchText = this.searchText.escapeHtml().replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
110
- const searchRe = new RegExp(`(${escapedSearchText})`, 'ig');
111
-
112
- options = options.map(option => {
113
- option = { ...option };
114
- option.titleHtml = option.titleHtml.replace(searchRe, '<mark>$1</mark>');
115
- if (option.subtitleHtml)
116
- option.subtitleHtml = option.subtitleHtml.replace(searchRe, '<mark>$1</mark>');
117
- return option;
118
- });
119
-
120
- if (this.shouldShowCreateOption) {
121
- const hasExactMatch =
122
- options.find(option => option.searchContent === strippedSearchText) !== undefined;
123
- if (!hasExactMatch) {
124
- options.push({
125
- key: createSymbol,
126
- titleHtml: 'Create <strong>' + this.searchText.trim() + '</strong>...'
127
- });
128
- }
129
- }
130
- }
131
- } else if (this.nullTitle) {
132
- options.unshift({
133
- key: nullSymbol,
134
- titleHtml: this.nullTitle
135
- });
136
- }
137
-
138
- return options;
139
- },
140
-
141
101
  effectiveIdKey() {
142
102
  return this.idKey || 'id';
143
103
  },
@@ -148,7 +108,7 @@ export default {
148
108
 
149
109
  effectiveValueKey() {
150
110
  if (this.valueKey) return this.valueKey;
151
- if (this.hasManualOptionsObject) return this.effectiveIdKey;
111
+ if (this.options && !Array.isArray(this.options)) return this.effectiveIdKey;
152
112
  return undefined;
153
113
  },
154
114
 
@@ -156,17 +116,28 @@ export default {
156
116
  return this.noResultsText || 'No options match your search.';
157
117
  },
158
118
 
159
- resolvedOptionsArray() {
160
- return this.hasManualOptionsObject
161
- ? Object.entries(this.resolvedOptions).map(entry => ({
162
- [this.effectiveIdKey]: entry[0],
163
- [this.effectiveTitleKey]: entry[1]
164
- }))
165
- : this.resolvedOptions;
119
+ /**
120
+ * OPTIONS GENERATION
121
+ */
122
+
123
+ loadedOptionsArray() {
124
+ return this.arrayifyOptions(this.loadedOptions);
166
125
  },
167
126
 
168
- decoratedOptions() {
169
- return this.resolvedOptionsArray.map((option, index) => {
127
+ prependOptionsArray() {
128
+ return this.prependOptions ? this.arrayifyOptions(this.prependOptions) : [];
129
+ },
130
+
131
+ appendOptionsArray() {
132
+ return this.appendOptions ? this.arrayifyOptions(this.appendOptions) : [];
133
+ },
134
+
135
+ fullOptionsArray() {
136
+ return [...this.prependOptionsArray, ...this.loadedOptionsArray, ...this.appendOptionsArray];
137
+ },
138
+
139
+ optionsDescriptors() {
140
+ return this.fullOptionsArray.map((option, index) => {
170
141
  const title = this.getOptionTitle(option);
171
142
  const subtitle = this.getOptionSubtitle(option);
172
143
  const strippedTitle = title ? title.text.trim().toLowerCase() : '';
@@ -190,6 +161,47 @@ export default {
190
161
  ref: option
191
162
  };
192
163
  });
164
+ },
165
+
166
+ effectiveOptions() {
167
+ let options = [...this.optionsDescriptors];
168
+
169
+ if (this.isSearching) {
170
+ const strippedSearchText = this.searchText.trim().toLowerCase();
171
+
172
+ if (strippedSearchText.length) {
173
+ options = options.filter(option => option.searchContent.includes(strippedSearchText));
174
+
175
+ const escapedSearchText = this.searchText.escapeHtml().replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
176
+ const searchRe = new RegExp(`(${escapedSearchText})`, 'ig');
177
+
178
+ options = options.map(option => {
179
+ option = { ...option };
180
+ option.titleHtml = option.titleHtml.replace(searchRe, '<mark>$1</mark>');
181
+ if (option.subtitleHtml)
182
+ option.subtitleHtml = option.subtitleHtml.replace(searchRe, '<mark>$1</mark>');
183
+ return option;
184
+ });
185
+
186
+ if (this.shouldShowCreateOption) {
187
+ const hasExactMatch =
188
+ options.find(option => option.searchContent === strippedSearchText) !== undefined;
189
+ if (!hasExactMatch) {
190
+ options.push({
191
+ key: createSymbol,
192
+ titleHtml: 'Create <strong>' + this.searchText.trim() + '</strong>...'
193
+ });
194
+ }
195
+ }
196
+ }
197
+ } else if (this.nullTitle) {
198
+ options.unshift({
199
+ key: nullSymbol,
200
+ titleHtml: this.nullTitle
201
+ });
202
+ }
203
+
204
+ return options;
193
205
  }
194
206
  },
195
207
 
@@ -201,7 +213,7 @@ export default {
201
213
  },
202
214
 
203
215
  options() {
204
- this.resolvedOptions = this.options;
216
+ this.loadedOptions = this.options;
205
217
  },
206
218
 
207
219
  url() {
@@ -214,7 +226,7 @@ export default {
214
226
 
215
227
  // data
216
228
 
217
- decoratedOptions() {
229
+ optionsDescriptors() {
218
230
  this.shouldDisplayOptions && setTimeout(this.highlightInitialOption, 0);
219
231
  },
220
232
 
@@ -245,8 +257,7 @@ export default {
245
257
  this.shouldShowCreateOption = this.$attrs['onCreateItem'] !== undefined;
246
258
 
247
259
  if (this.options) {
248
- this.resolvedOptions = this.options;
249
- // this.buildDecoratedOptions();
260
+ this.loadedOptions = this.options;
250
261
  this.isLoaded = true;
251
262
  } else if (this.$isPropTruthy(this.preload)) {
252
263
  this.performInitialLoad();
@@ -256,7 +267,9 @@ export default {
256
267
 
257
268
  this.$watch('selectedOption', () => {
258
269
  const newValue =
259
- this.selectedOption && this.effectiveValueKey ? this.selectedOption[this.effectiveValueKey] : this.selectedOption;
270
+ this.selectedOption && this.effectiveValueKey
271
+ ? this.selectedOption[this.effectiveValueKey]
272
+ : this.selectedOption;
260
273
  newValue === this.modelValue || this.$emit('update:modelValue', newValue);
261
274
  });
262
275
  },
@@ -264,7 +277,7 @@ export default {
264
277
  methods: {
265
278
  async performInitialLoad() {
266
279
  await this.reloadOptions();
267
- this.$emit('optionsLoaded', this.resolvedOptions);
280
+ this.$emit('optionsLoaded', this.loadedOptions);
268
281
 
269
282
  if (this.$isPropTruthy(this.remoteSearch)) {
270
283
  this.$watch('searchText', debounce(this.reloadOptionsIfSearching, 250));
@@ -275,7 +288,7 @@ export default {
275
288
  if (this.preload) return this.reloadOptions();
276
289
  if (!this.isLoaded) return;
277
290
  this.isLoaded = false;
278
- this.resolvedOptions = [];
291
+ this.loadedOptions = [];
279
292
  },
280
293
 
281
294
  async reloadOptions() {
@@ -284,7 +297,7 @@ export default {
284
297
  this.$isPropTruthy(this.remoteSearch) && this.searchText && (params.q = this.searchText);
285
298
 
286
299
  const result = await this.$http.get(this.url, { params: params });
287
- this.resolvedOptions = result.data;
300
+ this.loadedOptions = result.data;
288
301
  this.isLoaded = true;
289
302
  },
290
303
 
@@ -450,7 +463,7 @@ export default {
450
463
  this.selectedOptionTitle = null;
451
464
  this.$emit('createItem', createText);
452
465
  } else {
453
- const selectedDecoratedOption = this.decoratedOptions.find(
466
+ const selectedDecoratedOption = this.optionsDescriptors.find(
454
467
  decoratedOption => decoratedOption.key == option.key
455
468
  );
456
469
  const realOption = selectedDecoratedOption.ref;
@@ -465,7 +478,7 @@ export default {
465
478
  handleValueChanged() {
466
479
  if (this.modelValue) {
467
480
  if (this.effectiveValueKey) {
468
- this.selectedOption = this.resolvedOptionsArray.find(
481
+ this.selectedOption = this.fullOptionsArray.find(
469
482
  option => option[this.effectiveValueKey] === this.modelValue
470
483
  );
471
484
  } else {
@@ -530,7 +543,16 @@ export default {
530
543
  },
531
544
 
532
545
  addRemoteOption(option) {
533
- this.resolvedOptions.push(option);
546
+ this.loadedOptions.push(option);
547
+ },
548
+
549
+ arrayifyOptions(options) {
550
+ return Array.isArray(options)
551
+ ? options
552
+ : Object.entries(options).map(entry => ({
553
+ [this.effectiveIdKey]: entry[0],
554
+ [this.effectiveTitleKey]: entry[1]
555
+ }));
534
556
  }
535
557
  }
536
558
  };