@finsweet/webflow-apps-utils 1.0.52 → 1.0.53

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.
@@ -60,6 +60,14 @@ declare const meta: {
60
60
  control: "boolean";
61
61
  description: string;
62
62
  };
63
+ showRemoveIcon: {
64
+ control: "boolean";
65
+ description: string;
66
+ };
67
+ expandOnClick: {
68
+ control: "boolean";
69
+ description: string;
70
+ };
63
71
  width: {
64
72
  control: "text";
65
73
  description: string;
@@ -99,5 +107,8 @@ export declare const KeywordsTags: Story;
99
107
  export declare const FormIntegration: Story;
100
108
  export declare const ManyTags: Story;
101
109
  export declare const LongTags: Story;
110
+ export declare const ShowRemoveIcon: Story;
111
+ export declare const ExpandOnClick: Story;
112
+ export declare const ShowRemoveIconAndExpandOnClick: Story;
102
113
  export declare const SpecialCharacters: Story;
103
114
  export declare const UnicodeSupport: Story;
@@ -60,6 +60,14 @@ const meta = {
60
60
  control: 'boolean',
61
61
  description: 'Whether to trim whitespace from tags'
62
62
  },
63
+ showRemoveIcon: {
64
+ control: 'boolean',
65
+ description: 'Whether to always show the remove icon on tags'
66
+ },
67
+ expandOnClick: {
68
+ control: 'boolean',
69
+ description: 'Whether clicking a tag expands it to show full content'
70
+ },
63
71
  width: {
64
72
  control: 'text',
65
73
  description: 'Custom width for the component'
@@ -407,6 +415,53 @@ export const LongTags = {
407
415
  }
408
416
  }
409
417
  };
418
+ export const ShowRemoveIcon = {
419
+ args: {
420
+ value: ['JavaScript', 'TypeScript', 'Svelte'],
421
+ showRemoveIcon: true,
422
+ placeholder: 'Remove icon always visible...'
423
+ },
424
+ parameters: {
425
+ docs: {
426
+ description: {
427
+ story: 'When showRemoveIcon is true, the X button is always visible inline with 4px gap.'
428
+ }
429
+ }
430
+ }
431
+ };
432
+ export const ExpandOnClick = {
433
+ args: {
434
+ value: [
435
+ 'This is a very long tag that will be truncated',
436
+ 'Another long tag content here',
437
+ 'Short'
438
+ ],
439
+ expandOnClick: true,
440
+ placeholder: 'Click tags to expand...'
441
+ },
442
+ parameters: {
443
+ docs: {
444
+ description: {
445
+ story: 'When expandOnClick is true, clicking a tag expands it to show full content.'
446
+ }
447
+ }
448
+ }
449
+ };
450
+ export const ShowRemoveIconAndExpandOnClick = {
451
+ args: {
452
+ value: ['This is a very long tag name', 'TypeScript', 'Click me to expand'],
453
+ showRemoveIcon: true,
454
+ expandOnClick: true,
455
+ placeholder: 'Both features enabled...'
456
+ },
457
+ parameters: {
458
+ docs: {
459
+ description: {
460
+ story: 'Both showRemoveIcon and expandOnClick enabled together.'
461
+ }
462
+ }
463
+ }
464
+ };
410
465
  export const SpecialCharacters = {
411
466
  args: {
412
467
  value: ['C++', 'C#', '.NET', '@angular', '#svelte'],
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+
2
4
  import { TimesIcon } from '../../icons';
3
5
 
4
6
  import Loader from '../Loader.svelte';
@@ -21,6 +23,8 @@
21
23
  allowDuplicates = false,
22
24
  validateTag,
23
25
  trimTags = true,
26
+ showRemoveIcon = false,
27
+ expandOnClick = false,
24
28
  width = '100%',
25
29
  height = 'auto',
26
30
  class: className = '',
@@ -39,6 +43,7 @@
39
43
  let inputElement: HTMLInputElement | undefined = $state();
40
44
  let inputValue = $state('');
41
45
  let isFocused = $state(false);
46
+ let expandedTags = new SvelteSet<number>();
42
47
 
43
48
  // Derived states
44
49
  let isDisabled = $derived(disabled || loading);
@@ -67,12 +72,40 @@
67
72
  ${isSuccessAlert ? 'success' : ''}
68
73
  ${isFocused ? 'focused' : ''}
69
74
  ${loading ? 'loading' : ''}
75
+ ${showRemoveIcon ? 'show-remove-icon' : ''}
70
76
  ${className}
71
77
  `
72
78
  .trim()
73
79
  .replace(/\s+/g, ' ')
74
80
  );
75
81
 
82
+ /**
83
+ * Collapses all expanded tags
84
+ */
85
+ const collapseAllTags = () => {
86
+ expandedTags.clear();
87
+ };
88
+
89
+ /**
90
+ * Toggles the expanded state of a tag
91
+ */
92
+ const toggleTagExpand = (index: number) => {
93
+ if (!expandOnClick) return;
94
+
95
+ if (expandedTags.has(index)) {
96
+ expandedTags.delete(index);
97
+ } else {
98
+ expandedTags.add(index);
99
+ }
100
+ };
101
+
102
+ /**
103
+ * Checks if a tag is expanded
104
+ */
105
+ const isTagExpanded = (index: number): boolean => {
106
+ return expandedTags.has(index);
107
+ };
108
+
76
109
  /**
77
110
  * Focus the input element
78
111
  */
@@ -232,9 +265,13 @@
232
265
  };
233
266
 
234
267
  /**
235
- * Handles wrapper click to focus input
268
+ * Handles wrapper click to focus input and collapse expanded tags
236
269
  */
237
270
  const handleWrapperClick = () => {
271
+ // Collapse all expanded tags when clicking on the wrapper area
272
+ if (expandOnClick && expandedTags.size > 0) {
273
+ collapseAllTags();
274
+ }
238
275
  focusInput();
239
276
  };
240
277
 
@@ -268,7 +305,24 @@
268
305
  >
269
306
  <div class="tags-input-content">
270
307
  {#each value as tag, index (`${index}-${tag}`)}
271
- <span class="tag" role="listitem">
308
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
309
+ <span
310
+ class="tag {expandOnClick ? 'expandable' : ''} {isTagExpanded(index) ? 'expanded' : ''}"
311
+ role="listitem"
312
+ onclick={(e) => {
313
+ if (expandOnClick) {
314
+ e.stopPropagation();
315
+ toggleTagExpand(index);
316
+ }
317
+ }}
318
+ onkeydown={(e) => {
319
+ if (expandOnClick && e.key === 'Enter') {
320
+ e.stopPropagation();
321
+ toggleTagExpand(index);
322
+ }
323
+ }}
324
+ tabindex={expandOnClick ? 0 : undefined}
325
+ >
272
326
  <span class="tag-text">{tag}</span>
273
327
  {#if !readonly && !isDisabled}
274
328
  <button
@@ -391,10 +445,14 @@
391
445
  flex-wrap: wrap;
392
446
  align-items: flex-start;
393
447
  align-content: flex-start;
394
- gap: 4px;
448
+ gap: 0;
395
449
  width: 100%;
396
450
  }
397
451
 
452
+ .tags-input-wrapper.show-remove-icon .tags-input-content {
453
+ gap: 4px;
454
+ }
455
+
398
456
  .tag {
399
457
  position: relative;
400
458
  display: flex;
@@ -414,6 +472,22 @@
414
472
  user-select: none;
415
473
  max-width: 100%;
416
474
  min-width: 0;
475
+ margin: 2px;
476
+ }
477
+
478
+ .tags-input-wrapper.show-remove-icon .tag {
479
+ margin: 0;
480
+ }
481
+
482
+ .tag.expandable {
483
+ cursor: pointer;
484
+ }
485
+
486
+ .tag.expanded .tag-text {
487
+ overflow: visible;
488
+ text-overflow: unset;
489
+ white-space: normal;
490
+ word-break: break-word;
417
491
  }
418
492
 
419
493
  .tag-text {
@@ -447,6 +521,20 @@
447
521
  pointer-events: auto;
448
522
  }
449
523
 
524
+ /* Show remove icon mode - inline button */
525
+ .tags-input-wrapper.show-remove-icon .tag-remove {
526
+ position: relative;
527
+ top: auto;
528
+ right: auto;
529
+ bottom: auto;
530
+ padding: 0;
531
+ margin-left: 4px;
532
+ background: transparent;
533
+ opacity: 1;
534
+ pointer-events: auto;
535
+ border-radius: 0;
536
+ }
537
+
450
538
  .tag-remove:not(:disabled) {
451
539
  color: var(--text1);
452
540
  }
@@ -62,6 +62,18 @@ export interface TagsInputProps {
62
62
  * Whether to trim whitespace from tags (default: true)
63
63
  */
64
64
  trimTags?: boolean;
65
+ /**
66
+ * Whether to always show the remove icon on tags (default: false)
67
+ * When true: remove button is inline, 4px gap, no padding on close button
68
+ * When false: remove button is absolute positioned, appears on hover
69
+ */
70
+ showRemoveIcon?: boolean;
71
+ /**
72
+ * Whether clicking a tag expands it to show full content (default: false)
73
+ * When true: clicking a tag removes ellipsis and shows full text
74
+ * When false: long tags are always truncated with ellipsis
75
+ */
76
+ expandOnClick?: boolean;
65
77
  /**
66
78
  * Custom width for the component
67
79
  */
@@ -42,7 +42,3 @@
42
42
  </script>
43
43
 
44
44
  {#if children}{@render children()}{/if}
45
-
46
- <style>
47
- /* Global provider styles - currently using display: contents */
48
- </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {