@pequity/squirrel 7.1.1 → 7.1.3

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.
@@ -12,16 +12,28 @@ const lodashEs = require("lodash-es");
12
12
  const _imports_0 = "data:image/svg+xml,%3csvg%20width='18'%20height='12'%20viewBox='0%200%2018%2012'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M16.1383%200.166992L6.30411%209.83366L1.69828%205.27533L0.526611%206.46033L5.71578%2011.597C5.87174%2011.7509%206.08205%2011.8372%206.3012%2011.8372C6.52034%2011.8372%206.73065%2011.7509%206.88661%2011.597L17.3033%201.35366L16.1383%200.166992Z'%20fill='%231A123B'%20/%3e%3c/svg%3e";
13
13
  const _hoisted_1 = ["data-has-error"];
14
14
  const _hoisted_2 = { class: "truncate text-left text-p-gray-40" };
15
- const _hoisted_3 = { class: "truncate text-left" };
15
+ const _hoisted_3 = {
16
+ key: 0,
17
+ class: "flex flex-wrap gap-1.5 pr-8"
18
+ };
16
19
  const _hoisted_4 = {
20
+ class: "max-w-[200px] truncate",
21
+ "data-test": "pill-text"
22
+ };
23
+ const _hoisted_5 = ["onClick"];
24
+ const _hoisted_6 = {
25
+ key: 1,
26
+ class: "truncate text-left"
27
+ };
28
+ const _hoisted_7 = {
17
29
  key: 0,
18
30
  class: "mt-3 px-3"
19
31
  };
20
- const _hoisted_5 = { class: "text-p-purple-60" };
21
- const _hoisted_6 = { class: "flex flex-row" };
22
- const _hoisted_7 = ["onClick"];
23
- const _hoisted_8 = ["title"];
24
- const _hoisted_9 = {
32
+ const _hoisted_8 = { class: "text-p-purple-60" };
33
+ const _hoisted_9 = { class: "flex flex-row" };
34
+ const _hoisted_10 = ["onClick"];
35
+ const _hoisted_11 = ["title"];
36
+ const _hoisted_12 = {
25
37
  key: 0,
26
38
  class: "ml-auto fill-p-purple-60 pl-2",
27
39
  src: _imports_0
@@ -167,6 +179,13 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
167
179
  creatable: {
168
180
  type: Boolean,
169
181
  default: false
182
+ },
183
+ /**
184
+ * Shows selected items as pills in the button when multiple selection is enabled
185
+ */
186
+ pills: {
187
+ type: Boolean,
188
+ default: false
170
189
  }
171
190
  },
172
191
  emits: ["update:modelValue", "select", "create"],
@@ -184,9 +203,19 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
184
203
  enableArrowNavigation: false
185
204
  };
186
205
  const CLEAR_BUTTON_SPACING = {
187
- sm: "right-8",
188
- md: "right-9",
189
- lg: "right-10"
206
+ sm: "right-8 text-sm top-[0.3rem]",
207
+ md: "right-9 text-base top-[0.5rem]",
208
+ lg: "right-10 text-lg top-[0.7rem]"
209
+ };
210
+ const PILL_SIZE = {
211
+ sm: "text-sm",
212
+ md: "text-base",
213
+ lg: "text-lg"
214
+ };
215
+ const PILL_SELECT_SPACING = {
216
+ sm: "min-h-[2rem] py-1 bg-[position:right_1rem_top_0.7rem]",
217
+ md: "min-h-[2.5rem] py-2 bg-[position:right_1rem_top_0.9rem]",
218
+ lg: "min-h-[3rem] py-2 bg-[position:right_1rem_top_1.1rem]"
190
219
  };
191
220
  const width = vue.ref("auto");
192
221
  const listItemStyle = vue.ref({ paddingTop: 0, paddingBottom: 0 });
@@ -304,15 +333,15 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
304
333
  "p-select-list": "",
305
334
  role: "listbox"
306
335
  }), [
307
- __props.multiple || __props.searchable ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_4, [
336
+ __props.multiple || __props.searchable ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_7, [
308
337
  __props.multiple ? (vue.openBlock(), vue.createElementBlock("div", {
309
338
  key: 0,
310
339
  ref_key: "actionsContainer",
311
340
  ref: actionsContainer,
312
341
  class: "flex flex-row justify-between text-xs font-semibold text-primary"
313
342
  }, [
314
- vue.createElementVNode("p", _hoisted_5, vue.toDisplayString(vue.unref(computedItems).length) + " items", 1),
315
- vue.createElementVNode("div", _hoisted_6, [
343
+ vue.createElementVNode("p", _hoisted_8, vue.toDisplayString(vue.unref(computedItems).length) + " items", 1),
344
+ vue.createElementVNode("div", _hoisted_9, [
316
345
  vue.unref(computedItems).length === vue.unref(internalItems).length ? (vue.openBlock(), vue.createElementBlock("a", {
317
346
  key: 0,
318
347
  class: vue.normalizeClass([
@@ -399,11 +428,11 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
399
428
  class: vue.normalizeClass({ "bg-p-blue-20": index === 1 })
400
429
  }, vue.toDisplayString(str), 3);
401
430
  }), 128))
402
- ], 8, _hoisted_8)
431
+ ], 8, _hoisted_11)
403
432
  ]),
404
- vue.unref(isSelected)(vue.unref(getValue)(row.index)) ? (vue.openBlock(), vue.createElementBlock("img", _hoisted_9)) : vue.createCommentVNode("", true)
433
+ vue.unref(isSelected)(vue.unref(getValue)(row.index)) ? (vue.openBlock(), vue.createElementBlock("img", _hoisted_12)) : vue.createCommentVNode("", true)
405
434
  ], 2)
406
- ], 14, _hoisted_7)), [
435
+ ], 14, _hoisted_10)), [
407
436
  [_directive_close_popper, !__props.multiple]
408
437
  ])
409
438
  ], 4);
@@ -434,7 +463,13 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
434
463
  vue.createElementVNode("button", vue.mergeProps(attrs.value, {
435
464
  ref: "button",
436
465
  type: "button",
437
- class: ["w-full", vue.unref(selectClasses), dropdownShow.value ? "border-primary" : ""],
466
+ class: [
467
+ "w-full",
468
+ vue.unref(selectClasses),
469
+ dropdownShow.value ? "border-primary" : "",
470
+ { "box-border h-auto items-start": __props.multiple && __props.pills && vue.unref(selectedItems).length > 0 },
471
+ PILL_SELECT_SPACING[__props.size]
472
+ ],
438
473
  role: "button",
439
474
  "aria-haspopup": "listbox",
440
475
  onClick: _cache[1] || (_cache[1] = ($event) => dropdownShow.value = !dropdownShow.value)
@@ -445,11 +480,33 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
445
480
  key: 1,
446
481
  item: __props.multiple ? vue.unref(selectedItems) : vue.unref(selectedItems)[0]
447
482
  }, () => [
448
- vue.createElementVNode("div", _hoisted_3, vue.toDisplayString(__props.multiple ? vue.unref(selectedItems).length === selectableItemsCount.value ? "All options selected" : `${vue.unref(selectedItems).length} option${vue.unref(selectedItems).length > 1 ? "s" : ""} selected` : vue.unref(selectedItems)[0][__props.itemText]), 1)
483
+ __props.multiple && __props.pills ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3, [
484
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(selectedItems), (item) => {
485
+ return vue.openBlock(), vue.createElementBlock("div", {
486
+ key: String(item[__props.itemValue]),
487
+ class: vue.normalizeClass(["flex items-center gap-1 rounded bg-p-gray-10 px-2 text-p-gray-50", [PILL_SIZE[__props.size]]]),
488
+ "data-test": "selected-pill"
489
+ }, [
490
+ vue.createElementVNode("span", _hoisted_4, vue.toDisplayString(item[__props.itemText]), 1),
491
+ vue.createElementVNode("button", {
492
+ type: "button",
493
+ class: "flex items-center justify-center text-p-gray-40 hover:text-p-gray-60",
494
+ "aria-label": "Remove item",
495
+ "data-test": "pill-remove",
496
+ onClick: vue.withModifiers(($event) => vue.unref(select)($event, item[__props.itemValue]), ["stop"])
497
+ }, [
498
+ vue.createVNode(pIcon_vue_vue_type_script_setup_true_lang._sfc_main, {
499
+ icon: "fe:close",
500
+ class: "h-3.5 w-3.5"
501
+ })
502
+ ], 8, _hoisted_5)
503
+ ], 2);
504
+ }), 128))
505
+ ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_6, vue.toDisplayString(__props.multiple ? vue.unref(selectedItems).length === selectableItemsCount.value ? "All options selected" : `${vue.unref(selectedItems).length} option${vue.unref(selectedItems).length > 1 ? "s" : ""} selected` : vue.unref(selectedItems)[0][__props.itemText]), 1))
449
506
  ]),
450
507
  __props.clearable && vue.unref(selectedItems).length ? (vue.openBlock(), vue.createElementBlock("button", {
451
508
  key: 2,
452
- class: vue.normalizeClass(["absolute top-1/2 flex -translate-y-1/2 items-center justify-center text-p-gray-40 hover:text-p-gray-60", [vue.unref(pSelectList.SIZES)[__props.size], CLEAR_BUTTON_SPACING[__props.size]]]),
509
+ class: vue.normalizeClass(["absolute right-9 flex h-6 items-center justify-center text-base text-p-gray-40 hover:text-p-gray-60", [CLEAR_BUTTON_SPACING[__props.size]]]),
453
510
  "aria-label": "Clear selection",
454
511
  onClick: _cache[0] || (_cache[0] = vue.withModifiers(
455
512
  //@ts-ignore
@@ -47,7 +47,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
47
47
  lg: "bg-[length:2rem_2rem] w-[2rem] h-[2rem] right-[46px] top-2"
48
48
  };
49
49
  const showEnterIconOnFocus = vue.ref(false);
50
- const searchInput = vue.useTemplateRef("searchInput");
50
+ const input = vue.useTemplateRef("pInput");
51
51
  const query = vue.ref(props.modelValue);
52
52
  vue.watch(
53
53
  () => props.modelValue,
@@ -62,7 +62,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
62
62
  query.value = "";
63
63
  requestAnimationFrame(() => {
64
64
  var _a;
65
- (_a = searchInput.value) == null ? void 0 : _a.$el.querySelector("input").focus();
65
+ (_a = input.value) == null ? void 0 : _a.$el.querySelector("input").focus();
66
66
  });
67
67
  };
68
68
  const keydownEnter = () => {
@@ -70,13 +70,10 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
70
70
  };
71
71
  return (_ctx, _cache) => {
72
72
  const _directive_tooltip = vue.resolveDirective("tooltip");
73
- return vue.openBlock(), vue.createBlock(pInput_vue_vue_type_script_setup_true_lang._sfc_main, vue.mergeProps({
74
- ref_key: "searchInput",
75
- ref: searchInput,
73
+ return vue.openBlock(), vue.createBlock(pInput_vue_vue_type_script_setup_true_lang._sfc_main, vue.mergeProps({ ref: "pInput" }, _ctx.$attrs, {
76
74
  modelValue: query.value,
77
75
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => query.value = $event),
78
- size: __props.size
79
- }, _ctx.$attrs, {
76
+ size: __props.size,
80
77
  role: "searchbox",
81
78
  rounded: "",
82
79
  onFocus: _cache[1] || (_cache[1] = ($event) => showEnterIconOnFocus.value = true),
@@ -119,5 +116,5 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
119
116
  };
120
117
  }
121
118
  });
122
- const PInputSearch = /* @__PURE__ */ _pluginVue_exportHelper._export_sfc(_sfc_main, [["__scopeId", "data-v-ebc375ff"]]);
119
+ const PInputSearch = /* @__PURE__ */ _pluginVue_exportHelper._export_sfc(_sfc_main, [["__scopeId", "data-v-0e51ff56"]]);
123
120
  module.exports = PInputSearch;
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, useAttrs, computed, watch, onMounted, onUnmounted, resolveDirective, createElementBlock, openBlock, normalizeStyle, normalizeClass, unref, createCommentVNode, createVNode, withDirectives, toDisplayString, mergeProps, withCtx, createElementVNode, renderSlot, withModifiers, isRef, Fragment, renderList, createTextVNode, vShow } from "vue";
1
+ import { defineComponent, ref, useAttrs, computed, watch, onMounted, onUnmounted, resolveDirective, createElementBlock, openBlock, normalizeStyle, normalizeClass, unref, createCommentVNode, createVNode, withDirectives, toDisplayString, mergeProps, withCtx, createElementVNode, renderSlot, Fragment, renderList, withModifiers, isRef, createTextVNode, 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";
@@ -11,16 +11,28 @@ import { omit } from "lodash-es";
11
11
  const _imports_0 = "data:image/svg+xml,%3csvg%20width='18'%20height='12'%20viewBox='0%200%2018%2012'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M16.1383%200.166992L6.30411%209.83366L1.69828%205.27533L0.526611%206.46033L5.71578%2011.597C5.87174%2011.7509%206.08205%2011.8372%206.3012%2011.8372C6.52034%2011.8372%206.73065%2011.7509%206.88661%2011.597L17.3033%201.35366L16.1383%200.166992Z'%20fill='%231A123B'%20/%3e%3c/svg%3e";
12
12
  const _hoisted_1 = ["data-has-error"];
13
13
  const _hoisted_2 = { class: "truncate text-left text-p-gray-40" };
14
- const _hoisted_3 = { class: "truncate text-left" };
14
+ const _hoisted_3 = {
15
+ key: 0,
16
+ class: "flex flex-wrap gap-1.5 pr-8"
17
+ };
15
18
  const _hoisted_4 = {
19
+ class: "max-w-[200px] truncate",
20
+ "data-test": "pill-text"
21
+ };
22
+ const _hoisted_5 = ["onClick"];
23
+ const _hoisted_6 = {
24
+ key: 1,
25
+ class: "truncate text-left"
26
+ };
27
+ const _hoisted_7 = {
16
28
  key: 0,
17
29
  class: "mt-3 px-3"
18
30
  };
19
- const _hoisted_5 = { class: "text-p-purple-60" };
20
- const _hoisted_6 = { class: "flex flex-row" };
21
- const _hoisted_7 = ["onClick"];
22
- const _hoisted_8 = ["title"];
23
- const _hoisted_9 = {
31
+ const _hoisted_8 = { class: "text-p-purple-60" };
32
+ const _hoisted_9 = { class: "flex flex-row" };
33
+ const _hoisted_10 = ["onClick"];
34
+ const _hoisted_11 = ["title"];
35
+ const _hoisted_12 = {
24
36
  key: 0,
25
37
  class: "ml-auto fill-p-purple-60 pl-2",
26
38
  src: _imports_0
@@ -166,6 +178,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
166
178
  creatable: {
167
179
  type: Boolean,
168
180
  default: false
181
+ },
182
+ /**
183
+ * Shows selected items as pills in the button when multiple selection is enabled
184
+ */
185
+ pills: {
186
+ type: Boolean,
187
+ default: false
169
188
  }
170
189
  },
171
190
  emits: ["update:modelValue", "select", "create"],
@@ -183,9 +202,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
183
202
  enableArrowNavigation: false
184
203
  };
185
204
  const CLEAR_BUTTON_SPACING = {
186
- sm: "right-8",
187
- md: "right-9",
188
- lg: "right-10"
205
+ sm: "right-8 text-sm top-[0.3rem]",
206
+ md: "right-9 text-base top-[0.5rem]",
207
+ lg: "right-10 text-lg top-[0.7rem]"
208
+ };
209
+ const PILL_SIZE = {
210
+ sm: "text-sm",
211
+ md: "text-base",
212
+ lg: "text-lg"
213
+ };
214
+ const PILL_SELECT_SPACING = {
215
+ sm: "min-h-[2rem] py-1 bg-[position:right_1rem_top_0.7rem]",
216
+ md: "min-h-[2.5rem] py-2 bg-[position:right_1rem_top_0.9rem]",
217
+ lg: "min-h-[3rem] py-2 bg-[position:right_1rem_top_1.1rem]"
189
218
  };
190
219
  const width = ref("auto");
191
220
  const listItemStyle = ref({ paddingTop: 0, paddingBottom: 0 });
@@ -303,15 +332,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
303
332
  "p-select-list": "",
304
333
  role: "listbox"
305
334
  }), [
306
- __props.multiple || __props.searchable ? (openBlock(), createElementBlock("div", _hoisted_4, [
335
+ __props.multiple || __props.searchable ? (openBlock(), createElementBlock("div", _hoisted_7, [
307
336
  __props.multiple ? (openBlock(), createElementBlock("div", {
308
337
  key: 0,
309
338
  ref_key: "actionsContainer",
310
339
  ref: actionsContainer,
311
340
  class: "flex flex-row justify-between text-xs font-semibold text-primary"
312
341
  }, [
313
- createElementVNode("p", _hoisted_5, toDisplayString(unref(computedItems).length) + " items", 1),
314
- createElementVNode("div", _hoisted_6, [
342
+ createElementVNode("p", _hoisted_8, toDisplayString(unref(computedItems).length) + " items", 1),
343
+ createElementVNode("div", _hoisted_9, [
315
344
  unref(computedItems).length === unref(internalItems).length ? (openBlock(), createElementBlock("a", {
316
345
  key: 0,
317
346
  class: normalizeClass([
@@ -398,11 +427,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
398
427
  class: normalizeClass({ "bg-p-blue-20": index === 1 })
399
428
  }, toDisplayString(str), 3);
400
429
  }), 128))
401
- ], 8, _hoisted_8)
430
+ ], 8, _hoisted_11)
402
431
  ]),
403
- unref(isSelected)(unref(getValue)(row.index)) ? (openBlock(), createElementBlock("img", _hoisted_9)) : createCommentVNode("", true)
432
+ unref(isSelected)(unref(getValue)(row.index)) ? (openBlock(), createElementBlock("img", _hoisted_12)) : createCommentVNode("", true)
404
433
  ], 2)
405
- ], 14, _hoisted_7)), [
434
+ ], 14, _hoisted_10)), [
406
435
  [_directive_close_popper, !__props.multiple]
407
436
  ])
408
437
  ], 4);
@@ -433,7 +462,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
433
462
  createElementVNode("button", mergeProps(attrs.value, {
434
463
  ref: "button",
435
464
  type: "button",
436
- class: ["w-full", unref(selectClasses), dropdownShow.value ? "border-primary" : ""],
465
+ class: [
466
+ "w-full",
467
+ unref(selectClasses),
468
+ dropdownShow.value ? "border-primary" : "",
469
+ { "box-border h-auto items-start": __props.multiple && __props.pills && unref(selectedItems).length > 0 },
470
+ PILL_SELECT_SPACING[__props.size]
471
+ ],
437
472
  role: "button",
438
473
  "aria-haspopup": "listbox",
439
474
  onClick: _cache[1] || (_cache[1] = ($event) => dropdownShow.value = !dropdownShow.value)
@@ -444,11 +479,33 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
444
479
  key: 1,
445
480
  item: __props.multiple ? unref(selectedItems) : unref(selectedItems)[0]
446
481
  }, () => [
447
- createElementVNode("div", _hoisted_3, toDisplayString(__props.multiple ? unref(selectedItems).length === selectableItemsCount.value ? "All options selected" : `${unref(selectedItems).length} option${unref(selectedItems).length > 1 ? "s" : ""} selected` : unref(selectedItems)[0][__props.itemText]), 1)
482
+ __props.multiple && __props.pills ? (openBlock(), createElementBlock("div", _hoisted_3, [
483
+ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(selectedItems), (item) => {
484
+ return openBlock(), createElementBlock("div", {
485
+ key: String(item[__props.itemValue]),
486
+ class: normalizeClass(["flex items-center gap-1 rounded bg-p-gray-10 px-2 text-p-gray-50", [PILL_SIZE[__props.size]]]),
487
+ "data-test": "selected-pill"
488
+ }, [
489
+ createElementVNode("span", _hoisted_4, toDisplayString(item[__props.itemText]), 1),
490
+ createElementVNode("button", {
491
+ type: "button",
492
+ class: "flex items-center justify-center text-p-gray-40 hover:text-p-gray-60",
493
+ "aria-label": "Remove item",
494
+ "data-test": "pill-remove",
495
+ onClick: withModifiers(($event) => unref(select)($event, item[__props.itemValue]), ["stop"])
496
+ }, [
497
+ createVNode(_sfc_main$1, {
498
+ icon: "fe:close",
499
+ class: "h-3.5 w-3.5"
500
+ })
501
+ ], 8, _hoisted_5)
502
+ ], 2);
503
+ }), 128))
504
+ ])) : (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(__props.multiple ? unref(selectedItems).length === selectableItemsCount.value ? "All options selected" : `${unref(selectedItems).length} option${unref(selectedItems).length > 1 ? "s" : ""} selected` : unref(selectedItems)[0][__props.itemText]), 1))
448
505
  ]),
449
506
  __props.clearable && unref(selectedItems).length ? (openBlock(), createElementBlock("button", {
450
507
  key: 2,
451
- class: normalizeClass(["absolute top-1/2 flex -translate-y-1/2 items-center justify-center text-p-gray-40 hover:text-p-gray-60", [unref(SIZES)[__props.size], CLEAR_BUTTON_SPACING[__props.size]]]),
508
+ class: normalizeClass(["absolute right-9 flex h-6 items-center justify-center text-base text-p-gray-40 hover:text-p-gray-60", [CLEAR_BUTTON_SPACING[__props.size]]]),
452
509
  "aria-label": "Clear selection",
453
510
  onClick: _cache[0] || (_cache[0] = withModifiers(
454
511
  //@ts-ignore
@@ -46,7 +46,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
46
46
  lg: "bg-[length:2rem_2rem] w-[2rem] h-[2rem] right-[46px] top-2"
47
47
  };
48
48
  const showEnterIconOnFocus = ref(false);
49
- const searchInput = useTemplateRef("searchInput");
49
+ const input = useTemplateRef("pInput");
50
50
  const query = ref(props.modelValue);
51
51
  watch(
52
52
  () => props.modelValue,
@@ -61,7 +61,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
61
61
  query.value = "";
62
62
  requestAnimationFrame(() => {
63
63
  var _a;
64
- (_a = searchInput.value) == null ? void 0 : _a.$el.querySelector("input").focus();
64
+ (_a = input.value) == null ? void 0 : _a.$el.querySelector("input").focus();
65
65
  });
66
66
  };
67
67
  const keydownEnter = () => {
@@ -69,13 +69,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
69
69
  };
70
70
  return (_ctx, _cache) => {
71
71
  const _directive_tooltip = resolveDirective("tooltip");
72
- return openBlock(), createBlock(_sfc_main$1, mergeProps({
73
- ref_key: "searchInput",
74
- ref: searchInput,
72
+ return openBlock(), createBlock(_sfc_main$1, mergeProps({ ref: "pInput" }, _ctx.$attrs, {
75
73
  modelValue: query.value,
76
74
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => query.value = $event),
77
- size: __props.size
78
- }, _ctx.$attrs, {
75
+ size: __props.size,
79
76
  role: "searchbox",
80
77
  rounded: "",
81
78
  onFocus: _cache[1] || (_cache[1] = ($event) => showEnterIconOnFocus.value = true),
@@ -118,7 +115,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
118
115
  };
119
116
  }
120
117
  });
121
- const PInputSearch = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-ebc375ff"]]);
118
+ const PInputSearch = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-0e51ff56"]]);
122
119
  export {
123
120
  PInputSearch as default
124
121
  };
@@ -148,6 +148,13 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
148
148
  type: BooleanConstructor;
149
149
  default: boolean;
150
150
  };
151
+ /**
152
+ * Shows selected items as pills in the button when multiple selection is enabled
153
+ */
154
+ pills: {
155
+ type: BooleanConstructor;
156
+ default: boolean;
157
+ };
151
158
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
152
159
  select: (...args: any[]) => void;
153
160
  "update:modelValue": (...args: any[]) => void;
@@ -286,6 +293,13 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
286
293
  type: BooleanConstructor;
287
294
  default: boolean;
288
295
  };
296
+ /**
297
+ * Shows selected items as pills in the button when multiple selection is enabled
298
+ */
299
+ pills: {
300
+ type: BooleanConstructor;
301
+ default: boolean;
302
+ };
289
303
  }>> & Readonly<{
290
304
  onSelect?: ((...args: any[]) => any) | undefined;
291
305
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
@@ -314,6 +328,7 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
314
328
  selectedTopShown: boolean;
315
329
  disabledBy: string;
316
330
  creatable: boolean;
331
+ pills: boolean;
317
332
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
318
333
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
319
334
  export default _default;
package/dist/squirrel.css CHANGED
@@ -263,9 +263,11 @@ from {
263
263
  to {
264
264
  opacity: 0;
265
265
  }
266
- }.enter[data-v-ebc375ff] {
266
+ }
267
+ .enter[data-v-0e51ff56] {
267
268
  background-image: url("data:image/svg+xml,%3csvg%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cg%20clip-path='url(%23clip0_1627_786)'%3e%3crect%20width='24'%20height='24'%20rx='5'%20fill='%23ECF1FF'/%3e%3cpath%20d='M19.17%201.03744e-05H4.83C4.19619%20-0.00130459%203.56834%200.122398%202.98239%200.364037C2.39645%200.605676%201.86392%200.960506%201.41528%201.40822C0.966641%201.85592%200.610708%202.38772%200.367855%202.97316C0.125002%203.55861%20-1.36406e-06%204.1862%200%204.82001L0%2019.17C0%2020.451%200.508874%2021.6795%201.41467%2022.5853C2.32048%2023.4911%203.549%2024%204.83%2024H19.17C20.4502%2023.9974%2021.6772%2023.4876%2022.5824%2022.5824C23.4876%2021.6772%2023.9974%2020.4502%2024%2019.17V4.82001C23.9974%203.54075%2023.4873%202.31479%2022.5818%201.41115C21.6763%200.507508%2020.4493%207.63258e-06%2019.17%201.03744e-05ZM22%2019.17C22%2019.9206%2021.7018%2020.6404%2021.1711%2021.1711C20.6404%2021.7018%2019.9206%2022%2019.17%2022H4.83C4.07944%2022%203.35962%2021.7018%202.82889%2021.1711C2.29816%2020.6404%202%2019.9206%202%2019.17V4.82001C2.00265%204.07118%202.30197%203.35393%202.83242%202.82536C3.36286%202.29679%204.08117%202.00001%204.83%202.00001H19.17C19.9188%202.00001%2020.6371%202.29679%2021.1676%202.82536C21.698%203.35393%2021.9974%204.07118%2022%204.82001V19.17Z'%20fill='%234750EB'/%3e%3cpath%20d='M17.5%207.5C17.2348%207.5%2016.9804%207.60536%2016.7929%207.79289C16.6054%207.98043%2016.5%208.23478%2016.5%208.5V11.25C16.5%2011.3163%2016.4737%2011.3799%2016.4268%2011.4268C16.3799%2011.4737%2016.3163%2011.5%2016.25%2011.5H10.75C10.6837%2011.5%2010.6201%2011.4737%2010.5732%2011.4268C10.5263%2011.3799%2010.5%2011.3163%2010.5%2011.25V9.5C10.5012%209.30138%2010.4431%209.10691%2010.3333%208.94139C10.2235%208.77587%2010.0669%208.6468%209.88348%208.57063C9.70004%208.49446%209.49807%208.47465%209.30332%208.51372C9.10857%208.55279%208.92987%208.64897%208.79%208.79L5.79%2011.79C5.60375%2011.9774%205.49921%2012.2308%205.49921%2012.495C5.49921%2012.7592%205.60375%2013.0126%205.79%2013.2L8.79%2016.2C8.88261%2016.2945%208.99306%2016.3697%209.11493%2016.4212C9.23681%2016.4727%209.36769%2016.4995%209.5%2016.5C9.76522%2016.5%2010.0196%2016.3946%2010.2071%2016.2071C10.3946%2016.0196%2010.5%2015.7652%2010.5%2015.5V13.75C10.5%2013.6837%2010.5263%2013.6201%2010.5732%2013.5732C10.6201%2013.5263%2010.6837%2013.5%2010.75%2013.5H16.5C17.0304%2013.5%2017.5391%2013.2893%2017.9142%2012.9142C18.2893%2012.5391%2018.5%2012.0304%2018.5%2011.5V8.5C18.5%208.23478%2018.3946%207.98043%2018.2071%207.79289C18.0196%207.60536%2017.7652%207.5%2017.5%207.5Z'%20fill='%234750EB'/%3e%3c/g%3e%3cdefs%3e%3cclipPath%20id='clip0_1627_786'%3e%3crect%20width='24'%20height='24'%20rx='5'%20fill='white'/%3e%3c/clipPath%3e%3c/defs%3e%3c/svg%3e");
268
- }.fadeInDown[data-v-9ad56d4f] {
269
+ }
270
+ .fadeInDown[data-v-9ad56d4f] {
269
271
  animation-duration: 0.4s;
270
272
  animation-fill-mode: both;
271
273
  animation-name: fadeInDown-9ad56d4f;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "7.1.1",
4
+ "version": "7.1.3",
5
5
  "packageManager": "pnpm@9.15.9",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -857,4 +857,111 @@ describe('PDropdownSelect.vue', () => {
857
857
  cleanup(wrapper);
858
858
  });
859
859
  });
860
+
861
+ describe('pills functionality', () => {
862
+ it('renders selected items as pills when pills prop is true and multiple is true', async () => {
863
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
864
+
865
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: true });
866
+
867
+ const button = wrapper.find('button');
868
+ const pillsContainer = button.find('div.flex.flex-wrap.gap-1\\.5');
869
+ expect(pillsContainer.exists()).toBe(true);
870
+
871
+ const pills = wrapper.findAll('[data-test="selected-pill"]');
872
+ expect(pills.length).toBe(2);
873
+ expect(pills[0].find('[data-test="pill-text"]').text()).toBe('d39ad899-709e-4ed1-b48f-3ef160a617b1');
874
+ expect(pills[1].find('[data-test="pill-text"]').text()).toBe('42e1aeff-2147-485d-9265-fd79abc897b5');
875
+
876
+ cleanup(wrapper);
877
+ });
878
+
879
+ it('does not render pills when pills prop is false', async () => {
880
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
881
+
882
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: false });
883
+
884
+ const button = wrapper.find('button');
885
+ const pills = wrapper.findAll('[data-test="selected-pill"]');
886
+ expect(pills.length).toBe(0);
887
+ expect(button.text()).toBe('2 options selected');
888
+
889
+ cleanup(wrapper);
890
+ });
891
+
892
+ it('does not render pills when multiple is false, regardless of pills prop', async () => {
893
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
894
+
895
+ const wrapper = createWrapper({ selected: 4 }, { multiple: false, pills: true });
896
+
897
+ const button = wrapper.find('button');
898
+ const pills = wrapper.findAll('[data-test="selected-pill"]');
899
+ expect(pills.length).toBe(0);
900
+ expect(button.text()).toBe('d39ad899-709e-4ed1-b48f-3ef160a617b1');
901
+
902
+ cleanup(wrapper);
903
+ });
904
+
905
+ it('allows removing individual items by clicking the pill close button', async () => {
906
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
907
+
908
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: true });
909
+
910
+ const pills = wrapper.findAll('[data-test="selected-pill"]');
911
+ const closeButton = pills[0].find('[data-test="pill-remove"]');
912
+ await closeButton.trigger('click');
913
+
914
+ expect(wrapper.vm.$data.selected).toEqual([5]);
915
+ expect(wrapper.findAll('[data-test="selected-pill"]').length).toBe(1);
916
+
917
+ cleanup(wrapper);
918
+ });
919
+
920
+ it('shows pills with correct layout', async () => {
921
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
922
+
923
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: true });
924
+
925
+ const button = wrapper.find('button');
926
+ expect(button.classes()).toContain('h-auto');
927
+ expect(button.classes()).toContain('min-h-[2.5rem]');
928
+ expect(button.classes()).toContain('items-start');
929
+ expect(button.classes()).toContain('py-2');
930
+
931
+ const pillsContainer = wrapper.find('div.flex.flex-wrap.gap-1\\.5');
932
+ expect(pillsContainer.exists()).toBe(true);
933
+
934
+ cleanup(wrapper);
935
+ });
936
+
937
+ it('truncates long text in pills', async () => {
938
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
939
+
940
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: true });
941
+
942
+ const pillText = wrapper.find('[data-test="pill-text"]');
943
+ expect(pillText.classes()).toContain('max-w-[200px]');
944
+ expect(pillText.classes()).toContain('truncate');
945
+
946
+ cleanup(wrapper);
947
+ });
948
+
949
+ it('maintains pills after dropdown interaction', async () => {
950
+ useVirtualizer.mockImplementation(() => createMockedVirtualizer(20));
951
+
952
+ const wrapper = createWrapper({ selected: [4, 5] }, { multiple: true, pills: true });
953
+
954
+ const button = wrapper.find('button');
955
+ await button.trigger('click');
956
+
957
+ const item = wrapper.findAll('[p-select-list-option-item]')[6];
958
+ await item.trigger('click');
959
+
960
+ const pills = wrapper.findAll('[data-test="selected-pill"]');
961
+ expect(pills.length).toBe(3);
962
+ expect(wrapper.vm.$data.selected).toEqual([4, 5, 7]);
963
+
964
+ cleanup(wrapper);
965
+ });
966
+ });
860
967
  });
@@ -280,6 +280,27 @@ export const MultipleSearchable = {
280
280
  },
281
281
  };
282
282
 
283
+ export const MultipleWithPills = {
284
+ render: createRenderFunction({ selectedVal: [33, 34, 36] }),
285
+ parameters: {
286
+ docs: {
287
+ description: {
288
+ story: 'Multiple selection with selected items displayed as pills in the button',
289
+ },
290
+ },
291
+ },
292
+ args: {
293
+ ...Default.args,
294
+ label: 'Multiple with pills',
295
+ items: items4,
296
+ multiple: true,
297
+ searchable: true,
298
+ pills: true,
299
+ placeholder: 'Select an item from the list',
300
+ placeholderSearch: 'Search an item...',
301
+ },
302
+ };
303
+
283
304
  export const SelectedOnTop = {
284
305
  render: createRenderFunction({ selectedVal: [33, 34, 36] }),
285
306
  args: {
@@ -19,7 +19,13 @@
19
19
  v-bind="attrs"
20
20
  ref="button"
21
21
  type="button"
22
- :class="['w-full', selectClasses, dropdownShow ? 'border-primary' : '']"
22
+ :class="[
23
+ 'w-full',
24
+ selectClasses,
25
+ dropdownShow ? 'border-primary' : '',
26
+ { 'box-border h-auto items-start': multiple && pills && selectedItems.length > 0 },
27
+ PILL_SELECT_SPACING[size],
28
+ ]"
23
29
  role="button"
24
30
  aria-haspopup="listbox"
25
31
  @click="dropdownShow = !dropdownShow"
@@ -30,7 +36,27 @@
30
36
  </div>
31
37
  </slot>
32
38
  <slot v-else name="selected-item" :item="multiple ? selectedItems : selectedItems[0]">
33
- <div class="truncate text-left">
39
+ <div v-if="multiple && pills" class="flex flex-wrap gap-1.5 pr-8">
40
+ <div
41
+ v-for="item in selectedItems"
42
+ :key="String(item[itemValue])"
43
+ class="flex items-center gap-1 rounded bg-p-gray-10 px-2 text-p-gray-50"
44
+ :class="[PILL_SIZE[size]]"
45
+ data-test="selected-pill"
46
+ >
47
+ <span class="max-w-[200px] truncate" data-test="pill-text">{{ item[itemText] }}</span>
48
+ <button
49
+ type="button"
50
+ class="flex items-center justify-center text-p-gray-40 hover:text-p-gray-60"
51
+ aria-label="Remove item"
52
+ data-test="pill-remove"
53
+ @click.stop="select($event, item[itemValue])"
54
+ >
55
+ <PIcon icon="fe:close" class="h-3.5 w-3.5" />
56
+ </button>
57
+ </div>
58
+ </div>
59
+ <div v-else class="truncate text-left">
34
60
  {{
35
61
  multiple
36
62
  ? selectedItems.length === selectableItemsCount
@@ -43,8 +69,8 @@
43
69
  <!-- Clear selection button -->
44
70
  <button
45
71
  v-if="clearable && selectedItems.length"
46
- class="absolute top-1/2 flex -translate-y-1/2 items-center justify-center text-p-gray-40 hover:text-p-gray-60"
47
- :class="[SIZES[size], CLEAR_BUTTON_SPACING[size]]"
72
+ class="absolute right-9 flex h-6 items-center justify-center text-base text-p-gray-40 hover:text-p-gray-60"
73
+ :class="[CLEAR_BUTTON_SPACING[size]]"
48
74
  aria-label="Clear selection"
49
75
  @click.stop="clearAll"
50
76
  >
@@ -349,6 +375,13 @@ const props = defineProps({
349
375
  type: Boolean,
350
376
  default: false,
351
377
  },
378
+ /**
379
+ * Shows selected items as pills in the button when multiple selection is enabled
380
+ */
381
+ pills: {
382
+ type: Boolean,
383
+ default: false,
384
+ },
352
385
  });
353
386
 
354
387
  // Async helpers
@@ -366,9 +399,21 @@ const P_DROPDOWN_DEFAULTS = {
366
399
  };
367
400
 
368
401
  const CLEAR_BUTTON_SPACING = {
369
- sm: 'right-8',
370
- md: 'right-9',
371
- lg: 'right-10',
402
+ sm: 'right-8 text-sm top-[0.3rem]',
403
+ md: 'right-9 text-base top-[0.5rem]',
404
+ lg: 'right-10 text-lg top-[0.7rem]',
405
+ };
406
+
407
+ const PILL_SIZE = {
408
+ sm: 'text-sm',
409
+ md: 'text-base',
410
+ lg: 'text-lg',
411
+ };
412
+
413
+ const PILL_SELECT_SPACING = {
414
+ sm: 'min-h-[2rem] py-1 bg-[position:right_1rem_top_0.7rem]',
415
+ md: 'min-h-[2.5rem] py-2 bg-[position:right_1rem_top_0.9rem]',
416
+ lg: 'min-h-[3rem] py-2 bg-[position:right_1rem_top_1.1rem]',
372
417
  };
373
418
 
374
419
  const width = ref('auto');
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <PInput
3
- ref="searchInput"
3
+ ref="pInput"
4
+ v-bind="$attrs"
4
5
  v-model="query"
5
6
  :size="size"
6
- v-bind="$attrs"
7
7
  role="searchbox"
8
8
  rounded
9
9
  @focus="showEnterIconOnFocus = true"
@@ -87,7 +87,7 @@ const enterIconClasses = {
87
87
  lg: 'bg-[length:2rem_2rem] w-[2rem] h-[2rem] right-[46px] top-2',
88
88
  } as const;
89
89
  const showEnterIconOnFocus = ref(false);
90
- const searchInput = useTemplateRef<PInputInstance>('searchInput');
90
+ const input = useTemplateRef<PInputInstance>('pInput');
91
91
  const query = ref(props.modelValue);
92
92
 
93
93
  // Watchers
@@ -107,7 +107,7 @@ const clearSearch = () => {
107
107
  query.value = '';
108
108
 
109
109
  requestAnimationFrame(() => {
110
- searchInput.value?.$el.querySelector('input').focus();
110
+ input.value?.$el.querySelector('input').focus();
111
111
  });
112
112
  };
113
113
 
@@ -116,7 +116,7 @@ const keydownEnter = () => {
116
116
  };
117
117
  </script>
118
118
 
119
- <style scoped lang="scss">
119
+ <style scoped>
120
120
  .enter {
121
121
  background-image: url('@squirrel/assets/keyboard-press-enter.svg');
122
122
  }