@pequity/squirrel 6.0.7 → 6.0.8

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.
@@ -74,14 +74,14 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
74
74
  }
75
75
  },
76
76
  /**
77
- * Set property of **items**’s text value
77
+ * Set property of **items**'s text value
78
78
  */
79
79
  itemText: {
80
80
  type: String,
81
81
  default: "text"
82
82
  },
83
83
  /**
84
- * Set property of **items**’s value - must be primitive.
84
+ * Set property of **items**'s value - must be primitive.
85
85
  */
86
86
  itemValue: {
87
87
  type: [String, Number],
@@ -160,9 +160,16 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
160
160
  disabledBy: {
161
161
  type: String,
162
162
  default: "disabled"
163
+ },
164
+ /**
165
+ * Enables the ability to create new items when no search results are found
166
+ */
167
+ creatable: {
168
+ type: Boolean,
169
+ default: false
163
170
  }
164
171
  },
165
- emits: ["update:modelValue", "select"],
172
+ emits: ["update:modelValue", "select", "create"],
166
173
  setup(__props, { emit: __emit }) {
167
174
  const emit = __emit;
168
175
  const props = __props;
@@ -264,6 +271,10 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
264
271
  destroyNavigationSvc();
265
272
  (_b = (_a = formControl.value) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
266
273
  };
274
+ const handleCreate = () => {
275
+ emit("create", search.value);
276
+ dropdownShow.value = false;
277
+ };
267
278
  return (_ctx, _cache) => {
268
279
  const _directive_close_popper = vue.resolveDirective("close-popper");
269
280
  return vue.openBlock(), vue.createElementBlock("div", {
@@ -398,7 +409,18 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
398
409
  !vue.unref(computedItems).length ? vue.renderSlot(_ctx.$slots, "no-items", { key: 0 }, () => [
399
410
  vue.createElementVNode("div", {
400
411
  class: vue.normalizeClass(["flex items-center justify-center", vue.unref(pSelectList.SIZES)[__props.size]])
401
- }, "No items found", 2)
412
+ }, [
413
+ __props.creatable && vue.unref(search) ? (vue.openBlock(), vue.createElementBlock("button", {
414
+ key: 0,
415
+ class: "hover:text-primary-hover flex items-center gap-2 font-medium text-p-blue-40",
416
+ onClick: handleCreate
417
+ }, [
418
+ vue.createVNode(pIcon_vue_vue_type_script_setup_true_lang._sfc_main, { icon: "fe:plus-circle" }),
419
+ vue.createTextVNode(" Add '" + vue.toDisplayString(vue.unref(search)) + "' ", 1)
420
+ ])) : (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [
421
+ vue.createTextVNode("No items found")
422
+ ], 64))
423
+ ], 2)
402
424
  ]) : vue.createCommentVNode("", true)
403
425
  ], 6)
404
426
  ], 16)
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, useAttrs, computed, watch, onMounted, onUnmounted, resolveDirective, openBlock, createElementBlock, normalizeClass, unref, normalizeStyle, toDisplayString, createCommentVNode, createVNode, mergeProps, withCtx, createElementVNode, isRef, Fragment, renderList, withDirectives, renderSlot, withModifiers, vShow } from "vue";
1
+ import { defineComponent, ref, useAttrs, computed, watch, onMounted, onUnmounted, resolveDirective, openBlock, createElementBlock, normalizeClass, unref, normalizeStyle, toDisplayString, createCommentVNode, createVNode, mergeProps, withCtx, createElementVNode, isRef, Fragment, renderList, withDirectives, renderSlot, createTextVNode, withModifiers, vShow } from "vue";
2
2
  import PDropdown from "../p-dropdown.js";
3
3
  import { _ as _sfc_main$1 } from "./p-icon.js";
4
4
  import PInputSearch from "../p-input-search.js";
@@ -73,14 +73,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
73
73
  }
74
74
  },
75
75
  /**
76
- * Set property of **items**’s text value
76
+ * Set property of **items**'s text value
77
77
  */
78
78
  itemText: {
79
79
  type: String,
80
80
  default: "text"
81
81
  },
82
82
  /**
83
- * Set property of **items**’s value - must be primitive.
83
+ * Set property of **items**'s value - must be primitive.
84
84
  */
85
85
  itemValue: {
86
86
  type: [String, Number],
@@ -159,9 +159,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
159
159
  disabledBy: {
160
160
  type: String,
161
161
  default: "disabled"
162
+ },
163
+ /**
164
+ * Enables the ability to create new items when no search results are found
165
+ */
166
+ creatable: {
167
+ type: Boolean,
168
+ default: false
162
169
  }
163
170
  },
164
- emits: ["update:modelValue", "select"],
171
+ emits: ["update:modelValue", "select", "create"],
165
172
  setup(__props, { emit: __emit }) {
166
173
  const emit = __emit;
167
174
  const props = __props;
@@ -263,6 +270,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
263
270
  destroyNavigationSvc();
264
271
  (_b = (_a = formControl.value) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
265
272
  };
273
+ const handleCreate = () => {
274
+ emit("create", search.value);
275
+ dropdownShow.value = false;
276
+ };
266
277
  return (_ctx, _cache) => {
267
278
  const _directive_close_popper = resolveDirective("close-popper");
268
279
  return openBlock(), createElementBlock("div", {
@@ -397,7 +408,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
397
408
  !unref(computedItems).length ? renderSlot(_ctx.$slots, "no-items", { key: 0 }, () => [
398
409
  createElementVNode("div", {
399
410
  class: normalizeClass(["flex items-center justify-center", unref(SIZES)[__props.size]])
400
- }, "No items found", 2)
411
+ }, [
412
+ __props.creatable && unref(search) ? (openBlock(), createElementBlock("button", {
413
+ key: 0,
414
+ class: "hover:text-primary-hover flex items-center gap-2 font-medium text-p-blue-40",
415
+ onClick: handleCreate
416
+ }, [
417
+ createVNode(_sfc_main$1, { icon: "fe:plus-circle" }),
418
+ createTextVNode(" Add '" + toDisplayString(unref(search)) + "' ", 1)
419
+ ])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
420
+ createTextVNode("No items found")
421
+ ], 64))
422
+ ], 2)
401
423
  ]) : createCommentVNode("", true)
402
424
  ], 6)
403
425
  ], 16)
@@ -262,14 +262,14 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
262
262
  validator(value: Size): boolean;
263
263
  };
264
264
  /**
265
- * Set property of **items**’s text value
265
+ * Set property of **items**'s text value
266
266
  */
267
267
  itemText: {
268
268
  type: StringConstructor;
269
269
  default: string;
270
270
  };
271
271
  /**
272
- * Set property of **items**’s value - must be primitive.
272
+ * Set property of **items**'s value - must be primitive.
273
273
  */
274
274
  itemValue: {
275
275
  type: (StringConstructor | NumberConstructor)[];
@@ -349,9 +349,17 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
349
349
  type: PropType<string>;
350
350
  default: string;
351
351
  };
352
+ /**
353
+ * Enables the ability to create new items when no search results are found
354
+ */
355
+ creatable: {
356
+ type: BooleanConstructor;
357
+ default: boolean;
358
+ };
352
359
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
353
360
  select: (...args: any[]) => void;
354
361
  "update:modelValue": (...args: any[]) => void;
362
+ create: (...args: any[]) => void;
355
363
  }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
356
364
  modelValue: {
357
365
  type: PropType<ModelValue>;
@@ -392,14 +400,14 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
392
400
  validator(value: Size): boolean;
393
401
  };
394
402
  /**
395
- * Set property of **items**’s text value
403
+ * Set property of **items**'s text value
396
404
  */
397
405
  itemText: {
398
406
  type: StringConstructor;
399
407
  default: string;
400
408
  };
401
409
  /**
402
- * Set property of **items**’s value - must be primitive.
410
+ * Set property of **items**'s value - must be primitive.
403
411
  */
404
412
  itemValue: {
405
413
  type: (StringConstructor | NumberConstructor)[];
@@ -479,9 +487,17 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
479
487
  type: PropType<string>;
480
488
  default: string;
481
489
  };
490
+ /**
491
+ * Enables the ability to create new items when no search results are found
492
+ */
493
+ creatable: {
494
+ type: BooleanConstructor;
495
+ default: boolean;
496
+ };
482
497
  }>> & Readonly<{
483
498
  onSelect?: ((...args: any[]) => any) | undefined;
484
499
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
500
+ onCreate?: ((...args: any[]) => any) | undefined;
485
501
  }>, {
486
502
  size: "sm" | "md" | "lg";
487
503
  label: string;
@@ -505,6 +521,7 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
505
521
  placeholderSearch: string;
506
522
  selectedTopShown: boolean;
507
523
  disabledBy: string;
524
+ creatable: boolean;
508
525
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {
509
526
  formControl: HTMLDivElement;
510
527
  button: HTMLButtonElement;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "6.0.7",
4
+ "version": "6.0.8",
5
5
  "packageManager": "pnpm@9.15.4",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -713,4 +713,102 @@ describe('PDropdownSelect.vue', () => {
713
713
 
714
714
  cleanup(wrapper);
715
715
  });
716
+
717
+ describe('creatable functionality', () => {
718
+ it('shows create option when no items match search and creatable is true', async () => {
719
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
720
+ const wrapper = createWrapper({ selected: null }, { searchable: true, creatable: true });
721
+ await wrapper.find('button').trigger('click');
722
+ await sleep(200);
723
+ const searchInput = wrapper.find('input.text-night');
724
+ await searchInput.setValue('New Item');
725
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
726
+ expect(createButton.exists()).toBe(true);
727
+ expect(createButton.text()).toBe("Add 'New Item'");
728
+ cleanup(wrapper);
729
+ });
730
+
731
+ it('does not show create option when creatable is false', async () => {
732
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
733
+ const wrapper = createWrapper({ selected: null }, { searchable: true, creatable: false });
734
+ await wrapper.find('button').trigger('click');
735
+ await sleep(200);
736
+ const searchInput = wrapper.find('input.text-night');
737
+ await searchInput.setValue('New Item');
738
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
739
+ expect(createButton.exists()).toBe(false);
740
+ expect(wrapper.text()).toContain('No items found');
741
+ cleanup(wrapper);
742
+ });
743
+
744
+ it('emits create event when clicking create option', async () => {
745
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
746
+ const wrapper = createWrapper({ selected: null }, { searchable: true, creatable: true });
747
+ await wrapper.find('button').trigger('click');
748
+ await sleep(200);
749
+ const searchInput = wrapper.find('input.text-night');
750
+ await searchInput.setValue('New Item');
751
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
752
+ await createButton.trigger('click');
753
+ const pDropdownSelectCmp = wrapper.findComponent(PDropdownSelect);
754
+ expect(pDropdownSelectCmp.emitted().create[0]).toEqual(['New Item']);
755
+ expect(wrapper.find('.pdropdown-stub-popper').exists()).toBe(false);
756
+ cleanup(wrapper);
757
+ });
758
+
759
+ it('does not update model value when creating new item', async () => {
760
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
761
+ const wrapper = createWrapper({ selected: null }, { searchable: true, creatable: true });
762
+ await wrapper.find('button').trigger('click');
763
+ await sleep(200);
764
+ const searchInput = wrapper.find('input.text-night');
765
+ await searchInput.setValue('New Item');
766
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
767
+ await createButton.trigger('click');
768
+ const pDropdownSelectCmp = wrapper.findComponent(PDropdownSelect);
769
+ expect(pDropdownSelectCmp.emitted()['update:modelValue']).toBeFalsy();
770
+ expect(wrapper.vm.$data.selected).toBe(null);
771
+ cleanup(wrapper);
772
+ });
773
+
774
+ it('does not create object value when valueIsObject is true', async () => {
775
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
776
+ const wrapper = createWrapper({ selected: null }, { searchable: true, creatable: true, valueIsObject: true });
777
+ await wrapper.find('button').trigger('click');
778
+ await sleep(200);
779
+ const searchInput = wrapper.find('input.text-night');
780
+ await searchInput.setValue('New Item');
781
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
782
+ await createButton.trigger('click');
783
+ const pDropdownSelectCmp = wrapper.findComponent(PDropdownSelect);
784
+ expect(pDropdownSelectCmp.emitted().create[0]).toEqual(['New Item']);
785
+ expect(pDropdownSelectCmp.emitted()['update:modelValue']).toBeFalsy();
786
+ expect(wrapper.vm.$data.selected).toBe(null);
787
+ cleanup(wrapper);
788
+ });
789
+
790
+ it('emits create event with search value when using custom itemValue and itemText', async () => {
791
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
792
+ const wrapper = createWrapper(
793
+ { selected: null },
794
+ {
795
+ searchable: true,
796
+ creatable: true,
797
+ valueIsObject: true,
798
+ itemValue: 'customValue',
799
+ itemText: 'customText',
800
+ }
801
+ );
802
+ await wrapper.find('button').trigger('click');
803
+ await sleep(200);
804
+ const searchInput = wrapper.find('input.text-night');
805
+ await searchInput.setValue('New Item');
806
+ const createButton = wrapper.find('button.hover\\:text-primary-hover');
807
+ await createButton.trigger('click');
808
+ const pDropdownSelectCmp = wrapper.findComponent(PDropdownSelect);
809
+ expect(pDropdownSelectCmp.emitted().create[0]).toEqual(['New Item']);
810
+ expect(pDropdownSelectCmp.emitted()['update:modelValue']).toBeFalsy();
811
+ cleanup(wrapper);
812
+ });
813
+ });
716
814
  });
@@ -312,3 +312,39 @@ export const ClearableMultiple = {
312
312
  clearable: true,
313
313
  },
314
314
  };
315
+
316
+ export const Creatable = {
317
+ render: (args) => ({
318
+ components: { PDropdownSelect },
319
+ setup() {
320
+ const value = ref(null);
321
+ return { args, value };
322
+ },
323
+ template: `
324
+ <div>
325
+ <PDropdownSelect
326
+ v-model="value"
327
+ v-bind="args"
328
+ @create="(searchTerm) => alert('Create item: ' + searchTerm)"
329
+ />
330
+ <div class="mt-4">Selected: {{ value }}</div>
331
+ </div>
332
+ `,
333
+ }),
334
+ parameters: {
335
+ docs: {
336
+ description: {
337
+ story:
338
+ 'Example with creatable functionality enabled. When no search results are found, an option to create a new item is shown.',
339
+ },
340
+ },
341
+ },
342
+ args: {
343
+ ...Default.args,
344
+ label: 'Creatable dropdown',
345
+ items: items2,
346
+ searchable: true,
347
+ creatable: true,
348
+ placeholder: 'Search or create an item...',
349
+ },
350
+ };
@@ -148,7 +148,18 @@
148
148
  </div>
149
149
  </div>
150
150
  <slot v-if="!computedItems.length" name="no-items">
151
- <div :class="['flex items-center justify-center', SIZES[size]]">No items found</div>
151
+ <div :class="['flex items-center justify-center', SIZES[size]]">
152
+ <template v-if="creatable && search">
153
+ <button
154
+ class="hover:text-primary-hover flex items-center gap-2 font-medium text-p-blue-40"
155
+ @click="handleCreate"
156
+ >
157
+ <PIcon icon="fe:plus-circle" />
158
+ Add '{{ search }}'
159
+ </button>
160
+ </template>
161
+ <template v-else>No items found</template>
162
+ </div>
152
163
  </slot>
153
164
  </div>
154
165
  </div>
@@ -197,7 +208,7 @@ defineSlots<{
197
208
  item(props: { item: any; isItemSelected: boolean; itemTextSplit: string[] }): unknown;
198
209
  }>();
199
210
 
200
- const emit = defineEmits(['update:modelValue', 'select']);
211
+ const emit = defineEmits(['update:modelValue', 'select', 'create']);
201
212
 
202
213
  const props = defineProps({
203
214
  modelValue: {
@@ -241,14 +252,14 @@ const props = defineProps({
241
252
  },
242
253
  },
243
254
  /**
244
- * Set property of **items**’s text value
255
+ * Set property of **items**'s text value
245
256
  */
246
257
  itemText: {
247
258
  type: String,
248
259
  default: 'text',
249
260
  },
250
261
  /**
251
- * Set property of **items**’s value - must be primitive.
262
+ * Set property of **items**'s value - must be primitive.
252
263
  */
253
264
  itemValue: {
254
265
  type: [String, Number],
@@ -328,6 +339,13 @@ const props = defineProps({
328
339
  type: String as PropType<string>,
329
340
  default: 'disabled',
330
341
  },
342
+ /**
343
+ * Enables the ability to create new items when no search results are found
344
+ */
345
+ creatable: {
346
+ type: Boolean,
347
+ default: false,
348
+ },
331
349
  });
332
350
 
333
351
  // Async helpers
@@ -453,4 +471,9 @@ const onHide = () => {
453
471
  destroyNavigationSvc();
454
472
  (formControl.value?.querySelector('button') as HTMLElement)?.focus();
455
473
  };
474
+
475
+ const handleCreate = () => {
476
+ emit('create', search.value);
477
+ dropdownShow.value = false;
478
+ };
456
479
  </script>