@juspay/svelte-ui-components 2.123.0 → 2.125.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.
Files changed (59) hide show
  1. package/dist/Button/Button.svelte +4 -1
  2. package/dist/Button/properties.d.ts +13 -0
  3. package/dist/Chat/Chat.svelte +158 -0
  4. package/dist/Chat/Chat.svelte.d.ts +4 -0
  5. package/dist/Chat/controller.svelte.d.ts +24 -0
  6. package/dist/Chat/controller.svelte.js +190 -0
  7. package/dist/Chat/properties.d.ts +51 -0
  8. package/dist/Chat/properties.js +1 -0
  9. package/dist/Chat/roles.d.ts +2 -0
  10. package/dist/Chat/roles.js +3 -0
  11. package/dist/Chat/types.d.ts +45 -0
  12. package/dist/Chat/types.js +1 -0
  13. package/dist/ChatBubble/ChatBubble.svelte +419 -0
  14. package/dist/ChatBubble/ChatBubble.svelte.d.ts +4 -0
  15. package/dist/ChatBubble/properties.d.ts +43 -0
  16. package/dist/ChatBubble/properties.js +1 -0
  17. package/dist/ChatComposer/ChatComposer.svelte +289 -0
  18. package/dist/ChatComposer/ChatComposer.svelte.d.ts +4 -0
  19. package/dist/ChatComposer/properties.d.ts +33 -0
  20. package/dist/ChatComposer/properties.js +1 -0
  21. package/dist/ChatHeader/ChatHeader.svelte +169 -0
  22. package/dist/ChatHeader/ChatHeader.svelte.d.ts +4 -0
  23. package/dist/ChatHeader/properties.d.ts +19 -0
  24. package/dist/ChatHeader/properties.js +1 -0
  25. package/dist/ChatMessage/ChatMessage.svelte +330 -0
  26. package/dist/ChatMessage/ChatMessage.svelte.d.ts +4 -0
  27. package/dist/ChatMessage/properties.d.ts +29 -0
  28. package/dist/ChatMessage/properties.js +1 -0
  29. package/dist/ChatMessageList/ChatMessageList.svelte +181 -0
  30. package/dist/ChatMessageList/ChatMessageList.svelte.d.ts +4 -0
  31. package/dist/ChatMessageList/properties.d.ts +22 -0
  32. package/dist/ChatMessageList/properties.js +1 -0
  33. package/dist/ChatSuggestions/ChatSuggestions.svelte +46 -0
  34. package/dist/ChatSuggestions/ChatSuggestions.svelte.d.ts +4 -0
  35. package/dist/ChatSuggestions/properties.d.ts +16 -0
  36. package/dist/ChatSuggestions/properties.js +1 -0
  37. package/dist/ChatToolStatus/ChatToolStatus.svelte +63 -0
  38. package/dist/ChatToolStatus/ChatToolStatus.svelte.d.ts +4 -0
  39. package/dist/ChatToolStatus/properties.d.ts +10 -0
  40. package/dist/ChatToolStatus/properties.js +1 -0
  41. package/dist/Input/Input.svelte +32 -8
  42. package/dist/Input/properties.d.ts +6 -1
  43. package/dist/LineChart/LineChart.svelte +36 -15
  44. package/dist/Menu/Menu.svelte +39 -3
  45. package/dist/Resizable/Resizable.svelte +304 -0
  46. package/dist/Resizable/Resizable.svelte.d.ts +4 -0
  47. package/dist/Resizable/properties.d.ts +27 -0
  48. package/dist/Resizable/properties.js +1 -0
  49. package/dist/assets/attach.svg +3 -0
  50. package/dist/assets/chat.svg +3 -0
  51. package/dist/assets/mic.svg +5 -0
  52. package/dist/assets/retry.svg +4 -0
  53. package/dist/assets/send.svg +4 -0
  54. package/dist/assets/stop.svg +3 -0
  55. package/dist/assets/thumb-down.svg +4 -0
  56. package/dist/assets/thumb-up.svg +4 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.js +11 -0
  59. package/package.json +1 -1
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import Loader from '../Loader/Loader.svelte';
3
+ import type { ChatToolStatusProperties } from './properties';
4
+
5
+ let { label, icon, testId, classes }: ChatToolStatusProperties = $props();
6
+ </script>
7
+
8
+ <div
9
+ class="chat-tool-status {classes ?? ''}"
10
+ aria-live="polite"
11
+ data-pw={typeof testId === 'string' ? testId : null}
12
+ testID={typeof testId === 'string' ? testId : null}
13
+ >
14
+ <span class="indicator">
15
+ {#if typeof icon === 'function'}
16
+ {@render icon()}
17
+ {:else}
18
+ <Loader />
19
+ {/if}
20
+ </span>
21
+ <span class="label">{label}</span>
22
+ </div>
23
+
24
+ <style>
25
+ .chat-tool-status {
26
+ box-sizing: border-box;
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: var(--chat-tool-status-gap, 8px);
30
+ width: fit-content;
31
+ padding: var(--chat-tool-status-padding, 8px 14px);
32
+ background: var(--chat-tool-status-background, #ffffff);
33
+ border: var(--chat-tool-status-border, 1px solid #e4e4e7);
34
+ border-radius: var(--chat-tool-status-border-radius, 999px);
35
+ box-shadow: var(--chat-tool-status-box-shadow, 0 6px 20px rgba(0, 0, 0, 0.08));
36
+ color: var(--chat-tool-status-color, #52525b);
37
+ font-size: var(--chat-tool-status-font-size, 0.85rem);
38
+ font-weight: var(--chat-tool-status-font-weight, 500);
39
+ max-width: var(--chat-tool-status-max-width, 100%);
40
+ }
41
+
42
+ .indicator {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ flex-shrink: 0;
46
+ color: var(--chat-tool-status-indicator-color, currentColor);
47
+ --loader-foreground: var(--chat-tool-status-spinner-color, currentColor);
48
+ --loader-foreground-end: var(--chat-tool-status-spinner-color-end, transparent);
49
+ --loader-width: var(--chat-tool-status-spinner-size, 14px);
50
+ --loader-height: var(--chat-tool-status-spinner-size, 14px);
51
+ --loader-before-width: 7px;
52
+ --loader-before-height: 7px;
53
+ --loader-after-width: 11px;
54
+ --loader-after-height: 11px;
55
+ --loader-background: var(--chat-tool-status-background, #ffffff);
56
+ }
57
+
58
+ .label {
59
+ white-space: nowrap;
60
+ overflow: hidden;
61
+ text-overflow: ellipsis;
62
+ }
63
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { ChatToolStatusProperties } from './properties';
2
+ declare const ChatToolStatus: import("svelte").Component<ChatToolStatusProperties, {}, "">;
3
+ type ChatToolStatus = ReturnType<typeof ChatToolStatus>;
4
+ export default ChatToolStatus;
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type ChatToolStatusProperties = OptionalChatToolStatusProperties & MandatoryChatToolStatusProperties;
3
+ export type MandatoryChatToolStatusProperties = {
4
+ label: string;
5
+ };
6
+ export type OptionalChatToolStatusProperties = {
7
+ icon?: Snippet;
8
+ testId?: string;
9
+ classes?: string;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -115,6 +115,9 @@
115
115
  const hasRightIcon = $derived(typeof rightIcon === 'function');
116
116
 
117
117
  const charCount = $derived(value?.length ?? 0);
118
+ // Every numeric use below is either tel-only normalisation or the character
119
+ // counter; `null` means "no attribute", not "no ceiling on those paths".
120
+ const effectiveMaxLength = $derived(maxLength ?? 1000);
118
121
  const effectiveResize = $derived(autoResize ? 'none' : resize);
119
122
 
120
123
  // Grow the textarea to fit its content between minRows and maxRows.
@@ -130,8 +133,16 @@
130
133
  const border = parseFloat(styles.borderTopWidth) + parseFloat(styles.borderBottomWidth);
131
134
  const lower = minRows ?? rows ?? 2;
132
135
  const minHeight = lower * lineHeight + verticalPadding + border;
133
- const maxHeight =
136
+ const rowsCeiling =
134
137
  maxRows != null ? maxRows * lineHeight + verticalPadding + border : Number.POSITIVE_INFINITY;
138
+ // --input-max-height is a ceiling too. Reading only maxRows left it at Infinity, so the
139
+ // inline height grew past the CSS clamp and overflowY was set to `hidden` — the box
140
+ // stopped at the right size but its overflow became unreachable instead of scrollable.
141
+ const styleCeiling = parseFloat(styles.maxHeight);
142
+ const maxHeight = Math.min(
143
+ rowsCeiling,
144
+ Number.isFinite(styleCeiling) ? styleCeiling : Number.POSITIVE_INFINITY
145
+ );
135
146
  const nextHeight = Math.min(Math.max(el.scrollHeight, minHeight), maxHeight);
136
147
  el.style.height = `${nextHeight}px`;
137
148
  el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
@@ -163,16 +174,16 @@
163
174
  inputElement.value = value;
164
175
  return;
165
176
  }
166
- if (numberLength > maxLength) {
177
+ if (numberLength > effectiveMaxLength) {
167
178
  const existingInput = value;
168
- if (existingInput.length === maxLength) {
179
+ if (existingInput.length === effectiveMaxLength) {
169
180
  inputElement.value = applyTextPresentation(value);
170
181
  return;
171
182
  }
172
183
  /**
173
184
  * choose last max length number of digits if length is bigger than max length passed in props
174
185
  */
175
- currentValue = currentValue.substring(numberLength - maxLength);
186
+ currentValue = currentValue.substring(numberLength - effectiveMaxLength);
176
187
  }
177
188
  currentValue = applyTextPresentation(currentValue);
178
189
  inputElement.value = currentValue;
@@ -221,12 +232,12 @@
221
232
  /**
222
233
  * user pasted 10+ digit number , overrides all cases
223
234
  */
224
- if (filteredNumber.length > maxLength) {
235
+ if (filteredNumber.length > effectiveMaxLength) {
225
236
  /**
226
237
  * choose last max length number of digits if length is bigger than max length passed in props
227
238
  */
228
239
  const finalValue = applyTextPresentation(
229
- filteredNumber.substring(filteredNumberLength - maxLength)
240
+ filteredNumber.substring(filteredNumberLength - effectiveMaxLength)
230
241
  );
231
242
  // Adding reactivity
232
243
  value = finalValue;
@@ -390,8 +401,8 @@
390
401
  </div>
391
402
  {/if}
392
403
  {#if useTextArea && showCount && !actionInput}
393
- <div class="input-char-count" class:at-limit={charCount >= maxLength}>
394
- {charCount}/{maxLength}
404
+ <div class="input-char-count" class:at-limit={charCount >= effectiveMaxLength}>
405
+ {charCount}/{effectiveMaxLength}
395
406
  </div>
396
407
  {/if}
397
408
  </div>
@@ -401,6 +412,13 @@
401
412
  input {
402
413
  box-sizing: var(--input-box-sizing, border-box);
403
414
  height: var(--input-height, fit-content);
415
+
416
+ /* Both default to the CSS initial value, so a consumer that sets neither is
417
+ byte-identical to before. A textarea that grows with its content needs a
418
+ ceiling before it can scroll, and one used as a paste target needs a floor;
419
+ neither was reachable through the --input-* surface. */
420
+ min-height: var(--input-min-height, auto);
421
+ max-height: var(--input-max-height, none);
404
422
  background-color: var(--input-background, white);
405
423
  font-size: var(--input-font-size, 16px) !important;
406
424
  font-family: var(--input-font-family, inherit);
@@ -408,6 +426,12 @@
408
426
  outline: none;
409
427
  padding: var(--input-padding, 16px);
410
428
  font-weight: var(--input-font-weight, 500);
429
+
430
+ /* `normal` is what a textarea/input computes today regardless of any inherited
431
+ value — the UA sheet sets it, and inheritance loses to a UA declaration on the
432
+ element itself. So the default here is byte-identical for existing consumers,
433
+ and this is the only way a consumer can set it at all. */
434
+ line-height: var(--input-line-height, normal);
411
435
  width: var(--input-width, fit-content);
412
436
  margin: var(--input-margin, 0);
413
437
  appearance: none !important;
@@ -29,7 +29,12 @@ export type OptionalInputProperties = {
29
29
  validationPattern?: RegExp | null;
30
30
  inProgressPattern?: RegExp | null;
31
31
  addFocusColor?: boolean;
32
- maxLength?: number;
32
+ /**
33
+ * Native `maxlength`. Defaults to 1000. Pass `null` for no limit — a composer or
34
+ * paste target that silently truncates long input is worse than an unbounded one,
35
+ * and the attribute is rendered unconditionally otherwise.
36
+ */
37
+ maxLength?: number | null;
33
38
  minLength?: number;
34
39
  min?: number;
35
40
  max?: number;
@@ -228,6 +228,10 @@
228
228
  })
229
229
  );
230
230
 
231
+ // Markers paint back-to-front so the first series ends up on top — see the
232
+ // comment on the marker loop in the markup.
233
+ let markerPaintOrder = $derived(lines.map((_line, index) => index).reverse());
234
+
231
235
  let legendItems = $derived<LegendItem[]>(
232
236
  series.map((s, i) => ({
233
237
  label: s.name,
@@ -658,6 +662,38 @@
658
662
  stroke-dasharray={line.dash}
659
663
  fill="none"
660
664
  />
665
+ {#if showValues && pointLabelPlacements[si]}
666
+ {#each line.points as _point, pi (pi)}
667
+ {@const pl = pointLabelPlacements[si][pi]}
668
+ {#if pl?.visible && Number.isFinite(pl.x) && Number.isFinite(pl.y)}
669
+ <text
670
+ class="point-value"
671
+ x={pl.x}
672
+ y={pl.y}
673
+ text-anchor="middle"
674
+ dominant-baseline={pl.dominantBaseline}
675
+ >{formatNumber(series[si].data[pi].y)}</text
676
+ >
677
+ {/if}
678
+ {/each}
679
+ {/if}
680
+ {/if}
681
+ {/each}
682
+
683
+ <!--
684
+ Point markers are painted after every line, and in REVERSE series
685
+ order. Two series that share a value put their markers on identical
686
+ coordinates, and SVG has no z-index — whichever is written last wins.
687
+ Painting forwards meant the last series in the array hid the first,
688
+ so a chart passed [primary, comparison] lost the primary series'
689
+ marker wherever the two periods happened to agree, most visibly at
690
+ the first bucket. Reversing here makes the FIRST series win, which is
691
+ the one a reader is looking at; the legend keeps its original order
692
+ because it is derived from `series`, not from this loop.
693
+ -->
694
+ {#each markerPaintOrder as si (si)}
695
+ {@const line = lines[si]}
696
+ {#if !line.hidden}
661
697
  {#if line.points.length === 1 && !showDots && Number.isFinite(line.points[0].x) && Number.isFinite(line.points[0].y)}
662
698
  <circle
663
699
  class="single-point"
@@ -684,21 +720,6 @@
684
720
  {/if}
685
721
  {/each}
686
722
  {/if}
687
- {#if showValues && pointLabelPlacements[si]}
688
- {#each line.points as _point, pi (pi)}
689
- {@const pl = pointLabelPlacements[si][pi]}
690
- {#if pl?.visible && Number.isFinite(pl.x) && Number.isFinite(pl.y)}
691
- <text
692
- class="point-value"
693
- x={pl.x}
694
- y={pl.y}
695
- text-anchor="middle"
696
- dominant-baseline={pl.dominantBaseline}
697
- >{formatNumber(series[si].data[pi].y)}</text
698
- >
699
- {/if}
700
- {/each}
701
- {/if}
702
723
  {/if}
703
724
  {/each}
704
725
 
@@ -200,9 +200,28 @@
200
200
  focusedIndex = -1;
201
201
  typeaheadQuery = '';
202
202
  onclose?.();
203
- if (triggerEl !== null) {
204
- triggerEl.focus({ preventScroll: true });
203
+ focusTrigger();
204
+ }
205
+
206
+ /**
207
+ * Returns focus to whatever is actually focusable for this trigger. Under
208
+ * `interactiveTrigger` the wrapper carries no tabindex, so focusing it is a no-op and
209
+ * focus falls to <body> — the control the snippet rendered is the real target.
210
+ */
211
+ function focusTrigger() {
212
+ if (triggerEl === null) {
213
+ return;
205
214
  }
215
+ if (interactiveTrigger) {
216
+ const control = triggerEl.querySelector(
217
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
218
+ );
219
+ if (control instanceof HTMLElement) {
220
+ control.focus({ preventScroll: true });
221
+ return;
222
+ }
223
+ }
224
+ triggerEl.focus({ preventScroll: true });
206
225
  }
207
226
 
208
227
  function selectItem(item: MenuItem) {
@@ -240,6 +259,23 @@
240
259
  }
241
260
  }
242
261
 
262
+ /**
263
+ * Keydown wiring handed to an `interactiveTrigger` snippet. Enter and Space are
264
+ * deliberately NOT handled here: the snippet owns a real `<button>`, which already
265
+ * synthesises a click from both, and that click is already wired to `toggle`. Handling
266
+ * them again would open a menu the click then immediately closes. Only the arrow keys —
267
+ * which no native button implements — are added.
268
+ */
269
+ function handleInteractiveTriggerKeydown(event: KeyboardEvent) {
270
+ if (event.key === 'ArrowDown') {
271
+ event.preventDefault();
272
+ openMenu();
273
+ } else if (event.key === 'ArrowUp') {
274
+ event.preventDefault();
275
+ openMenu(selectableItems.length - 1);
276
+ }
277
+ }
278
+
243
279
  function handleMenuKeydown(event: KeyboardEvent) {
244
280
  switch (event.key) {
245
281
  case 'ArrowDown': {
@@ -358,7 +394,7 @@
358
394
  {#if typeof trigger === 'function'}
359
395
  {@render trigger({
360
396
  onclick: toggle,
361
- onkeydown: handleTriggerKeydown,
397
+ onkeydown: handleInteractiveTriggerKeydown,
362
398
  ariaHaspopup: 'menu',
363
399
  ariaExpanded: open
364
400
  })}
@@ -0,0 +1,304 @@
1
+ <script lang="ts">
2
+ import type { ResizableProperties, ResizeEdge } from './properties';
3
+
4
+ let {
5
+ width = $bindable(null),
6
+ height = $bindable(null),
7
+ minWidth = 0,
8
+ maxWidth = Number.POSITIVE_INFINITY,
9
+ minHeight = 0,
10
+ maxHeight = Number.POSITIVE_INFINITY,
11
+ handles = ['bottom-right'],
12
+ step = 16,
13
+ disabled = false,
14
+ handleLabel = 'Resize',
15
+ children,
16
+ onresize,
17
+ onresizestart,
18
+ onresizeend,
19
+ testId,
20
+ classes
21
+ }: ResizableProperties = $props();
22
+
23
+ const DIRS: Record<ResizeEdge, { x: -1 | 0 | 1; y: -1 | 0 | 1 }> = {
24
+ top: { x: 0, y: -1 },
25
+ right: { x: 1, y: 0 },
26
+ bottom: { x: 0, y: 1 },
27
+ left: { x: -1, y: 0 },
28
+ 'top-left': { x: -1, y: -1 },
29
+ 'top-right': { x: 1, y: -1 },
30
+ 'bottom-left': { x: -1, y: 1 },
31
+ 'bottom-right': { x: 1, y: 1 }
32
+ };
33
+
34
+ let container: HTMLElement | null = $state(null);
35
+ let active: {
36
+ edge: ResizeEdge;
37
+ pointerId: number;
38
+ startX: number;
39
+ startY: number;
40
+ startW: number;
41
+ startH: number;
42
+ } | null = $state(null);
43
+
44
+ let activeHandles = $derived(disabled ? [] : handles);
45
+
46
+ function clampWidth(value: number): number {
47
+ return Math.min(maxWidth, Math.max(minWidth, value));
48
+ }
49
+
50
+ function clampHeight(value: number): number {
51
+ return Math.min(maxHeight, Math.max(minHeight, value));
52
+ }
53
+
54
+ function measuredWidth(): number {
55
+ return typeof width === 'number' ? width : (container?.offsetWidth ?? 0);
56
+ }
57
+
58
+ function measuredHeight(): number {
59
+ return typeof height === 'number' ? height : (container?.offsetHeight ?? 0);
60
+ }
61
+
62
+ function axisOf(edge: ResizeEdge): 'x' | 'y' | 'both' {
63
+ const dir = DIRS[edge];
64
+ if (dir.x !== 0 && dir.y !== 0) {
65
+ return 'both';
66
+ }
67
+ return dir.x !== 0 ? 'x' : 'y';
68
+ }
69
+
70
+ function orientationOf(edge: ResizeEdge): 'horizontal' | 'vertical' | null {
71
+ const axis = axisOf(edge);
72
+ if (axis === 'x') {
73
+ return 'vertical';
74
+ }
75
+ if (axis === 'y') {
76
+ return 'horizontal';
77
+ }
78
+ return null;
79
+ }
80
+
81
+ function startResize(
82
+ event: PointerEvent & { currentTarget: HTMLElement },
83
+ edge: ResizeEdge
84
+ ): void {
85
+ if (disabled) {
86
+ return;
87
+ }
88
+ event.preventDefault();
89
+ const startW = clampWidth(measuredWidth());
90
+ const startH = clampHeight(measuredHeight());
91
+ width = startW;
92
+ height = startH;
93
+ active = {
94
+ edge,
95
+ pointerId: event.pointerId,
96
+ startX: event.clientX,
97
+ startY: event.clientY,
98
+ startW,
99
+ startH
100
+ };
101
+ event.currentTarget.setPointerCapture(event.pointerId);
102
+ onresizestart?.({ width: startW, height: startH });
103
+ }
104
+
105
+ function moveResize(event: PointerEvent): void {
106
+ if (active === null) {
107
+ return;
108
+ }
109
+ const dir = DIRS[active.edge];
110
+ if (dir.x !== 0) {
111
+ width = clampWidth(active.startW + dir.x * (event.clientX - active.startX));
112
+ }
113
+ if (dir.y !== 0) {
114
+ height = clampHeight(active.startH + dir.y * (event.clientY - active.startY));
115
+ }
116
+ onresize?.({ width: measuredWidth(), height: measuredHeight() });
117
+ }
118
+
119
+ function endResize(event: PointerEvent & { currentTarget: HTMLElement }): void {
120
+ if (active === null) {
121
+ return;
122
+ }
123
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
124
+ event.currentTarget.releasePointerCapture(event.pointerId);
125
+ }
126
+ active = null;
127
+ onresizeend?.({ width: measuredWidth(), height: measuredHeight() });
128
+ }
129
+
130
+ function keyResize(event: KeyboardEvent, edge: ResizeEdge): void {
131
+ if (disabled) {
132
+ return;
133
+ }
134
+ const dir = DIRS[edge];
135
+ let nextWidth = measuredWidth();
136
+ let nextHeight = measuredHeight();
137
+ let handled = false;
138
+
139
+ if (dir.x !== 0 && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) {
140
+ nextWidth = clampWidth(nextWidth + (event.key === 'ArrowRight' ? step : -step));
141
+ handled = true;
142
+ }
143
+ if (dir.y !== 0 && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
144
+ nextHeight = clampHeight(nextHeight + (event.key === 'ArrowDown' ? step : -step));
145
+ handled = true;
146
+ }
147
+
148
+ if (handled) {
149
+ event.preventDefault();
150
+ width = nextWidth;
151
+ height = nextHeight;
152
+ onresize?.({ width: nextWidth, height: nextHeight });
153
+ }
154
+ }
155
+
156
+ function valueNow(edge: ResizeEdge): number | null {
157
+ const axis = axisOf(edge);
158
+ if (axis === 'y') {
159
+ return typeof height === 'number' ? height : null;
160
+ }
161
+ return typeof width === 'number' ? width : null;
162
+ }
163
+
164
+ function valueMin(edge: ResizeEdge): number {
165
+ return axisOf(edge) === 'y' ? minHeight : minWidth;
166
+ }
167
+
168
+ function valueMax(edge: ResizeEdge): number | null {
169
+ const max = axisOf(edge) === 'y' ? maxHeight : maxWidth;
170
+ return Number.isFinite(max) ? max : null;
171
+ }
172
+ </script>
173
+
174
+ <div
175
+ class="resizable {classes ?? ''}"
176
+ class:resizing={active !== null}
177
+ data-pw={typeof testId === 'string' ? testId : null}
178
+ testID={typeof testId === 'string' ? testId : null}
179
+ bind:this={container}
180
+ style:width={typeof width === 'number' ? `${width}px` : null}
181
+ style:height={typeof height === 'number' ? `${height}px` : null}
182
+ >
183
+ {#if typeof children === 'function'}
184
+ {@render children()}
185
+ {/if}
186
+
187
+ {#each activeHandles as edge (edge)}
188
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
189
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
190
+ <div
191
+ class="handle"
192
+ data-edge={edge}
193
+ role="separator"
194
+ tabindex="0"
195
+ aria-label={handleLabel}
196
+ aria-orientation={orientationOf(edge)}
197
+ aria-valuenow={valueNow(edge)}
198
+ aria-valuemin={valueMin(edge)}
199
+ aria-valuemax={valueMax(edge)}
200
+ onpointerdown={(event) => startResize(event, edge)}
201
+ onpointermove={moveResize}
202
+ onpointerup={endResize}
203
+ onkeydown={(event) => keyResize(event, edge)}
204
+ ></div>
205
+ {/each}
206
+ </div>
207
+
208
+ <style>
209
+ .resizable {
210
+ position: relative;
211
+ box-sizing: border-box;
212
+ max-width: var(--resizable-max-width, none);
213
+ max-height: var(--resizable-max-height, none);
214
+ transition: var(--resizable-transition, none);
215
+ }
216
+
217
+ .resizable.resizing {
218
+ user-select: none;
219
+ transition: none;
220
+ }
221
+
222
+ @media (prefers-reduced-motion: reduce) {
223
+ .resizable {
224
+ transition: none;
225
+ }
226
+ }
227
+
228
+ .handle {
229
+ position: absolute;
230
+ touch-action: none;
231
+ z-index: var(--resizable-handle-z-index, 2);
232
+ background: var(--resizable-handle-color, transparent);
233
+ }
234
+
235
+ .handle:focus-visible {
236
+ outline: var(--resizable-handle-focus-outline, 2px solid #3b5bdb);
237
+ outline-offset: var(--resizable-handle-focus-outline-offset, -2px);
238
+ }
239
+
240
+ .handle[data-edge='top'],
241
+ .handle[data-edge='bottom'] {
242
+ left: 0;
243
+ right: 0;
244
+ height: var(--resizable-edge-size, 8px);
245
+ cursor: ns-resize;
246
+ }
247
+
248
+ .handle[data-edge='left'],
249
+ .handle[data-edge='right'] {
250
+ top: 0;
251
+ bottom: 0;
252
+ width: var(--resizable-edge-size, 8px);
253
+ cursor: ew-resize;
254
+ }
255
+
256
+ .handle[data-edge='top'] {
257
+ top: 0;
258
+ }
259
+
260
+ .handle[data-edge='bottom'] {
261
+ bottom: 0;
262
+ }
263
+
264
+ .handle[data-edge='left'] {
265
+ left: 0;
266
+ }
267
+
268
+ .handle[data-edge='right'] {
269
+ right: 0;
270
+ }
271
+
272
+ .handle[data-edge='top-left'],
273
+ .handle[data-edge='top-right'],
274
+ .handle[data-edge='bottom-left'],
275
+ .handle[data-edge='bottom-right'] {
276
+ width: var(--resizable-corner-size, 14px);
277
+ height: var(--resizable-corner-size, 14px);
278
+ z-index: var(--resizable-corner-z-index, 3);
279
+ }
280
+
281
+ .handle[data-edge='top-left'] {
282
+ top: 0;
283
+ left: 0;
284
+ cursor: nwse-resize;
285
+ }
286
+
287
+ .handle[data-edge='top-right'] {
288
+ top: 0;
289
+ right: 0;
290
+ cursor: nesw-resize;
291
+ }
292
+
293
+ .handle[data-edge='bottom-left'] {
294
+ bottom: 0;
295
+ left: 0;
296
+ cursor: nesw-resize;
297
+ }
298
+
299
+ .handle[data-edge='bottom-right'] {
300
+ bottom: 0;
301
+ right: 0;
302
+ cursor: nwse-resize;
303
+ }
304
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { ResizableProperties } from './properties';
2
+ declare const Resizable: import("svelte").Component<ResizableProperties, {}, "width" | "height">;
3
+ type Resizable = ReturnType<typeof Resizable>;
4
+ export default Resizable;
@@ -0,0 +1,27 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type ResizeEdge = 'top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
+ export type ResizeSize = {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ export type ResizableProperties = OptionalResizableProperties & ResizableEventProperties;
8
+ export type OptionalResizableProperties = {
9
+ width?: number | null;
10
+ height?: number | null;
11
+ minWidth?: number;
12
+ maxWidth?: number;
13
+ minHeight?: number;
14
+ maxHeight?: number;
15
+ handles?: ResizeEdge[];
16
+ step?: number;
17
+ disabled?: boolean;
18
+ handleLabel?: string;
19
+ children?: Snippet;
20
+ testId?: string;
21
+ classes?: string;
22
+ };
23
+ export type ResizableEventProperties = {
24
+ onresize?: (size: ResizeSize) => void;
25
+ onresizestart?: (size: ResizeSize) => void;
26
+ onresizeend?: (size: ResizeSize) => void;
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M16 7v9a4 4 0 0 1-8 0V6.5a2.5 2.5 0 0 1 5 0V15.5" />
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="9" y="2" width="6" height="12" rx="3" />
3
+ <path d="M5 11a7 7 0 0 0 14 0" />
4
+ <line x1="12" y1="18" x2="12" y2="22" />
5
+ </svg>