@sanity/workflow-components 0.0.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # @sanity/workflow-components
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6351232: Add reusable member, assignee, and date controls for Studio and App SDK integrations.
8
+
9
+ ### Patch Changes
10
+
11
+ - 775fb3c: Move focus into an opened date picker so calendar arrow-key navigation works immediately, and shrink picker result lists to their measured content height.
12
+ - Updated dependencies [239d9f6]
13
+ - Updated dependencies [3af2ca0]
14
+ - Updated dependencies [7276702]
15
+ - Updated dependencies [58f8211]
16
+ - Updated dependencies [00c089d]
17
+ - Updated dependencies [71cd58e]
18
+ - Updated dependencies [cd03973]
19
+ - Updated dependencies [d26cc1a]
20
+ - Updated dependencies [9126299]
21
+ - Updated dependencies [72018af]
22
+ - Updated dependencies [5cd8ffd]
23
+ - Updated dependencies [b82bda4]
24
+ - Updated dependencies [55a54b3]
25
+ - Updated dependencies [b82bda4]
26
+ - Updated dependencies [4490442]
27
+ - Updated dependencies [1540dce]
28
+ - @sanity/workflow-engine@0.18.0
package/dist/index.cjs CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: !0
5
5
  });
6
6
 
7
- var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), Close = require("@sanity/icons/Close"), ChevronLeft = require("@sanity/icons/ChevronLeft"), ChevronRight = require("@sanity/icons/ChevronRight"), addDays = require("date-fns/addDays"), addMonths = require("date-fns/addMonths"), setDate = require("date-fns/setDate"), setHours = require("date-fns/setHours"), setMinutes = require("date-fns/setMinutes"), setMonth = require("date-fns/setMonth"), setYear = require("date-fns/setYear"), react = require("react"), eachWeekOfInterval = require("date-fns/eachWeekOfInterval"), isSameDay = require("date-fns/isSameDay"), isSameMonth = require("date-fns/isSameMonth"), lastDayOfMonth = require("date-fns/lastDayOfMonth"), startOfMonth = require("date-fns/startOfMonth"), Checkmark = require("@sanity/icons/Checkmark"), Empty = require("@sanity/icons/Empty"), Users = require("@sanity/icons/Users");
7
+ var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), Close = require("@sanity/icons/Close"), ChevronLeft = require("@sanity/icons/ChevronLeft"), ChevronRight = require("@sanity/icons/ChevronRight"), addDays = require("date-fns/addDays"), addMonths = require("date-fns/addMonths"), setDate = require("date-fns/setDate"), setHours = require("date-fns/setHours"), setMinutes = require("date-fns/setMinutes"), setMonth = require("date-fns/setMonth"), setYear = require("date-fns/setYear"), react = require("react"), eachWeekOfInterval = require("date-fns/eachWeekOfInterval"), isSameDay = require("date-fns/isSameDay"), isSameMonth = require("date-fns/isSameMonth"), lastDayOfMonth = require("date-fns/lastDayOfMonth"), startOfMonth = require("date-fns/startOfMonth"), Checkmark = require("@sanity/icons/Checkmark"), Empty = require("@sanity/icons/Empty"), Users = require("@sanity/icons/Users"), reactVirtual = require("@tanstack/react-virtual");
8
8
 
9
9
  function roleListLabel(roles) {
10
10
  return `${roles.length === 1 ? "Role" : "Roles"}: ${roles.join(", ")}`;
@@ -152,7 +152,11 @@ function DatePicker({onSelect: onSelect, selectTime: selectTime = !1, value: val
152
152
  setFocusedDate(current => setDate.setDate(setMonth.setMonth(current, month), 1));
153
153
  }, []), moveFocusedDate = react.useCallback(byMonths => setFocusedDate(current => addMonths.addMonths(current, byMonths)), []), setFocusedYear = react.useCallback(year => setFocusedDate(current => setYear.setYear(current, year)), []), focusCurrentWeekDay = react.useCallback(() => {
154
154
  ref.current?.querySelector('[data-focused="true"]')?.focus();
155
- }, []), handleKeyDown = react.useCallback(event => {
155
+ }, []);
156
+ react.useEffect(() => {
157
+ ref.current?.querySelector("[data-calendar-grid]")?.focus();
158
+ }, []);
159
+ const handleKeyDown = react.useCallback(event => {
156
160
  if (!ARROW_KEYS.includes(event.key)) return;
157
161
  if (event.preventDefault(), event.target instanceof HTMLElement && event.target.hasAttribute("data-calendar-grid")) {
158
162
  focusCurrentWeekDay();
@@ -346,57 +350,81 @@ function MemberAvatarGroup({members: members, maxLength: maxLength = 3}) {
346
350
  });
347
351
  }
348
352
 
349
- function PickerStatus({loading: loading, error: error}) {
350
- return loading ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
351
- align: "center",
352
- gap: 2,
353
- padding: 3,
354
- children: [
355
- /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
356
- muted: !0,
357
- size: 1
358
- }),
359
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
360
- muted: !0,
361
- size: 1,
362
- children: "Loading members…"
363
- }) ]
364
- }) : error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
365
- padding: 3,
366
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
367
- muted: !0,
368
- size: 1,
369
- children: [ "Could not list members: ", error ]
370
- })
371
- }) : null;
353
+ const ESTIMATED_ROW_HEIGHT = 44, MAX_VISIBLE_ROWS = 8, MAX_VIEWPORT_HEIGHT = ESTIMATED_ROW_HEIGHT * MAX_VISIBLE_ROWS;
354
+
355
+ function hasTouchInput() {
356
+ return typeof navigator > "u" ? !1 : navigator.maxTouchPoints > 0 || globalThis.matchMedia?.("(pointer: coarse)").matches === !0;
357
+ }
358
+
359
+ function nextEnabledIndex({current: current, direction: direction, disabled: disabled, rows: rows}) {
360
+ for (let offset = 1; offset <= rows.length; offset += 1) {
361
+ const index = (current + direction * offset + rows.length) % rows.length, row = rows[index];
362
+ if (row !== void 0 && !disabled(row)) return index;
363
+ }
364
+ return -1;
372
365
  }
373
366
 
374
- const ActiveRowContext = react.createContext(void 0);
367
+ function arrowDirection(key) {
368
+ if (key === "ArrowDown") return 1;
369
+ if (key === "ArrowUp") return -1;
370
+ }
375
371
 
376
- function PickerShell({children: children, empty: empty, error: error, loading: loading, placeholder: placeholder, query: query, onQueryChange: onQueryChange}) {
377
- const listRef = react.useRef(null), listId = react.useId(), [activeIndex, setActiveIndex] = react.useState(-1), [activeRowId, setActiveRowId] = react.useState(), handleKeyDown = react.useCallback(event => {
378
- const rows = listRef.current?.querySelectorAll("[data-picker-row]") ?? [];
379
- if (rows.length === 0) return;
380
- if (event.key === "Enter" && activeIndex >= 0) {
381
- event.preventDefault(), rows[activeIndex]?.click();
372
+ function navigationStart(activeIndex, direction) {
373
+ return activeIndex >= 0 ? activeIndex : direction === 1 ? -1 : 0;
374
+ }
375
+
376
+ function CommandList({ariaLabel: ariaLabel, disabled: disabled = () => !1, emptyText: emptyText, getRowKey: getRowKey, onQueryChange: onQueryChange, onSelect: onSelect, placeholder: placeholder, query: query, renderRow: renderRow, rows: rows}) {
377
+ const inputRef = react.useRef(null), scrollRef = react.useRef(null), listId = react.useId(), [activeIndex, setActiveIndex] = react.useState(-1), estimatedViewportHeight = Math.min(rows.length, MAX_VISIBLE_ROWS) * ESTIMATED_ROW_HEIGHT, virtualizer = reactVirtual.useVirtualizer({
378
+ count: rows.length,
379
+ estimateSize: () => ESTIMATED_ROW_HEIGHT,
380
+ getItemKey: index => {
381
+ const row = rows[index];
382
+ return row === void 0 ? index : getRowKey(row);
383
+ },
384
+ getScrollElement: () => scrollRef.current,
385
+ initialRect: {
386
+ height: estimatedViewportHeight,
387
+ width: 300
388
+ },
389
+ initialMeasurementsCache: rows.map((row, index) => ({
390
+ end: (index + 1) * ESTIMATED_ROW_HEIGHT,
391
+ index: index,
392
+ key: getRowKey(row),
393
+ lane: 0,
394
+ size: ESTIMATED_ROW_HEIGHT,
395
+ start: index * ESTIMATED_ROW_HEIGHT
396
+ })),
397
+ overscan: 5,
398
+ observeElementRect: (_instance, callback) => (callback({
399
+ height: estimatedViewportHeight,
400
+ width: 300
401
+ }), () => {})
402
+ }), viewportHeight = Math.min(virtualizer.getTotalSize(), MAX_VIEWPORT_HEIGHT);
403
+ react.useEffect(() => {
404
+ hasTouchInput() || inputRef.current?.focus();
405
+ }, []), react.useEffect(() => setActiveIndex(-1), [ rows ]);
406
+ const moveActive = react.useCallback(direction => {
407
+ const start = navigationStart(activeIndex, direction), next = nextEnabledIndex({
408
+ current: start,
409
+ direction: direction,
410
+ disabled: disabled,
411
+ rows: rows
412
+ });
413
+ setActiveIndex(next), next >= 0 && virtualizer.scrollToIndex(next, {
414
+ align: "auto"
415
+ });
416
+ }, [ activeIndex, disabled, rows, virtualizer ]), handleKeyDown = react.useCallback(event => {
417
+ const direction = arrowDirection(event.key);
418
+ if (direction !== void 0) {
419
+ event.preventDefault(), moveActive(direction);
382
420
  return;
383
421
  }
384
- if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
385
- event.preventDefault();
386
- const direction = event.key === "ArrowDown" ? 1 : -1;
387
- setActiveIndex(current => {
388
- let startingIndex = current;
389
- current < 0 && (startingIndex = direction > 0 ? -1 : 0);
390
- const nextIndex = (startingIndex + direction + rows.length) % rows.length, nextRow = rows[nextIndex];
391
- return setActiveRowId(nextRow?.id), nextRow?.scrollIntoView?.({
392
- block: "nearest"
393
- }), nextIndex;
394
- });
395
- }, [ activeIndex ]);
396
- return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
397
- error: error,
398
- loading: loading
399
- }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
422
+ if (event.key !== "Enter") return;
423
+ const row = rows[activeIndex];
424
+ row === void 0 || disabled(row) || (event.preventDefault(), onSelect(row));
425
+ }, [ activeIndex, disabled, moveActive, onSelect, rows ]), activeRow = activeIndex < 0 ? void 0 : rows[activeIndex], activeId = activeRow === void 0 ? void 0 : `${listId}-${getRowKey(activeRow)}`;
426
+ /* @__PURE__ */
427
+ return jsxRuntime.jsxs(ui.Flex, {
400
428
  direction: "column",
401
429
  style: {
402
430
  width: 300
@@ -406,65 +434,106 @@ function PickerShell({children: children, empty: empty, error: error, loading: l
406
434
  padding: 1,
407
435
  paddingBottom: 0,
408
436
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
409
- "aria-activedescendant": activeRowId,
437
+ "aria-activedescendant": activeId,
410
438
  "aria-controls": listId,
411
439
  "aria-expanded": !0,
412
440
  "aria-label": placeholder,
413
441
  fontSize: 1,
414
- onChange: event => {
415
- setActiveIndex(-1), setActiveRowId(void 0), onQueryChange(event.currentTarget.value);
416
- },
442
+ onChange: event => onQueryChange(event.currentTarget.value),
417
443
  onKeyDown: handleKeyDown,
418
444
  placeholder: placeholder,
419
445
  radius: 2,
446
+ ref: inputRef,
420
447
  role: "combobox",
421
448
  value: query
422
449
  })
423
- }), empty ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
450
+ }), rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
424
451
  padding: 3,
425
452
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
426
453
  muted: !0,
427
454
  size: 1,
428
- children: "Nothing matches"
455
+ children: emptyText
429
456
  })
430
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
431
- as: "ul",
457
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
458
+ "aria-label": ariaLabel,
432
459
  id: listId,
433
- ref: listRef,
460
+ overflow: "auto",
461
+ ref: scrollRef,
434
462
  role: "listbox",
435
- padding: 1,
436
- space: 1,
437
463
  style: {
438
- listStyle: "none",
439
- maxHeight: 360,
440
- overflowY: "auto"
464
+ height: viewportHeight
441
465
  },
442
- children: /* @__PURE__ */ jsxRuntime.jsx(ActiveRowContext, {
443
- value: activeRowId,
444
- children: children
466
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
467
+ style: {
468
+ height: virtualizer.getTotalSize(),
469
+ position: "relative",
470
+ width: "100%"
471
+ },
472
+ children: virtualizer.getVirtualItems().map(item => {
473
+ const row = rows[item.index];
474
+ if (row === void 0) return null;
475
+ const rowId = `${listId}-${getRowKey(row)}`;
476
+ /* @__PURE__ */
477
+ return jsxRuntime.jsx(ui.Box, {
478
+ "data-index": item.index,
479
+ ref: virtualizer.measureElement,
480
+ style: {
481
+ position: "absolute",
482
+ transform: `translateY(${item.start}px)`,
483
+ width: "100%"
484
+ },
485
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
486
+ id: rowId,
487
+ children: renderRow(row, item.index === activeIndex)
488
+ })
489
+ }, item.key);
490
+ })
445
491
  })
446
492
  }) ]
447
493
  });
448
494
  }
449
495
 
450
- function PickerRow({icon: icon, label: label, onSelect: onSelect, rowId: rowId, selected: selected}) {
451
- const active = react.useContext(ActiveRowContext) === rowId;
496
+ function PickerStatus({loading: loading, error: error}) {
497
+ return loading ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
498
+ align: "center",
499
+ gap: 2,
500
+ padding: 3,
501
+ children: [
502
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
503
+ muted: !0,
504
+ size: 1
505
+ }),
506
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
507
+ muted: !0,
508
+ size: 1,
509
+ children: "Loading members…"
510
+ }) ]
511
+ }) : error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
512
+ padding: 3,
513
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
514
+ muted: !0,
515
+ size: 1,
516
+ children: [ "Could not list members: ", error ]
517
+ })
518
+ }) : null;
519
+ }
520
+
521
+ function PickerRow({active: active, icon: icon, label: label, onSelect: onSelect, selected: selected}) {
452
522
  /* @__PURE__ */
453
523
  return jsxRuntime.jsx(ui.Box, {
454
- as: "li",
524
+ paddingX: 1,
525
+ paddingY: .5,
455
526
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
456
527
  "aria-pressed": selected,
457
528
  "aria-selected": active,
458
- "data-picker-row": !0,
459
- id: rowId,
460
529
  justify: "flex-start",
461
530
  mode: active ? "ghost" : "bleed",
462
531
  onClick: onSelect,
463
532
  padding: 2,
533
+ role: "option",
464
534
  style: {
465
535
  width: "100%"
466
536
  },
467
- role: "option",
468
537
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
469
538
  align: "center",
470
539
  gap: 2,
@@ -496,41 +565,45 @@ function Glyph({children: children}) {
496
565
  }
497
566
 
498
567
  function MemberPicker({error: error, loading: loading, members: members, onSelect: onSelect, selectedIds: selectedIds, unassignRow: unassignRow = !1}) {
499
- const [query, setQuery] = react.useState(""), matchingMembers = react.useMemo(() => members.filter(member => matchesMemberSearch(member, query)), [ members, query ]), showUnassigned = unassignRow && query.trim() === "";
500
- /* @__PURE__ */
501
- return jsxRuntime.jsxs(PickerShell, {
502
- empty: !showUnassigned && matchingMembers.length === 0,
568
+ const [query, setQuery] = react.useState(""), rows = react.useMemo(() => {
569
+ const matches = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
570
+ kind: "member",
571
+ member: member
572
+ }));
573
+ return unassignRow && query.trim() === "" ? [ {
574
+ kind: "unassigned"
575
+ }, ...matches ] : matches;
576
+ }, [ members, query, unassignRow ]), selectRow = react.useCallback(row => onSelect(row.kind === "member" ? row.member : null), [ onSelect ]);
577
+ return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
503
578
  error: error,
504
- loading: loading,
505
- members: members,
579
+ loading: loading
580
+ }) : /* @__PURE__ */ jsxRuntime.jsx(CommandList, {
581
+ ariaLabel: "Project members",
582
+ emptyText: "Nothing matches",
583
+ getRowKey: row => row.kind === "member" ? `member-${row.member.id}` : "unassigned",
506
584
  onQueryChange: setQuery,
585
+ onSelect: selectRow,
507
586
  placeholder: "Search members…",
508
587
  query: query,
509
- children: [ showUnassigned ? /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
510
- icon: /* @__PURE__ */ jsxRuntime.jsx(Glyph, {
588
+ renderRow: (row, active) => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
589
+ active: active,
590
+ icon: row.kind === "member" ? /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
591
+ imageUrl: row.member.imageUrl,
592
+ name: row.member.displayName
593
+ }) : /* @__PURE__ */ jsxRuntime.jsx(Glyph, {
511
594
  children: /* @__PURE__ */ jsxRuntime.jsx(Empty.EmptyIcon, {})
512
595
  }),
513
- label: "No assignee",
514
- onSelect: () => onSelect(null),
515
- rowId: "workflow-member-unassigned",
516
- selected: selectedIds.size === 0
517
- }) : null, matchingMembers.map(member => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
518
- icon: /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
519
- imageUrl: member.imageUrl,
520
- name: member.displayName
521
- }),
522
- label: member.displayName,
523
- onSelect: () => onSelect(member),
524
- rowId: `workflow-member-${encodeURIComponent(member.id)}`,
525
- selected: selectedIds.has(member.id)
526
- }, member.id)) ]
596
+ label: row.kind === "member" ? row.member.displayName : "No assignee",
597
+ onSelect: () => selectRow(row),
598
+ selected: row.kind === "member" ? selectedIds.has(row.member.id) : selectedIds.size === 0
599
+ }),
600
+ rows: rows
527
601
  });
528
602
  }
529
603
 
530
604
  function GroupHeading({children: children}) {
531
605
  /* @__PURE__ */
532
606
  return jsxRuntime.jsx(ui.Card, {
533
- as: "li",
534
607
  borderTop: !0,
535
608
  padding: 2,
536
609
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
@@ -543,44 +616,63 @@ function GroupHeading({children: children}) {
543
616
  }
544
617
 
545
618
  function AssigneePicker({error: error, loading: loading, members: members, onToggle: onToggle, value: value}) {
546
- const [query, setQuery] = react.useState(""), normalized = query.trim().toLowerCase(), matchingRoles = react.useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]).filter(role => role.toLowerCase().includes(normalized)), matchingMembers = members.filter(member => matchesMemberSearch(member, query)), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : []));
547
- /* @__PURE__ */
548
- return jsxRuntime.jsxs(PickerShell, {
549
- empty: matchingRoles.length === 0 && matchingMembers.length === 0,
619
+ const [query, setQuery] = react.useState(""), roles = react.useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]), rows = react.useMemo(() => {
620
+ const normalized = query.trim().toLowerCase(), roleRows = roles.filter(role => role.toLowerCase().includes(normalized)).map(role => ({
621
+ kind: "role",
622
+ role: role
623
+ })), memberRows = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
624
+ kind: "member",
625
+ member: member
626
+ }));
627
+ return [ ...roleRows.length > 0 ? [ {
628
+ kind: "heading",
629
+ label: "Roles"
630
+ } ] : [], ...roleRows, ...memberRows.length > 0 ? [ {
631
+ kind: "heading",
632
+ label: "Members"
633
+ } ] : [], ...memberRows ];
634
+ }, [ members, query, roles ]), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : [])), selectRow = react.useCallback(row => {
635
+ row.kind === "role" && onToggle({
636
+ type: "role",
637
+ role: row.role
638
+ }), row.kind === "member" && onToggle({
639
+ type: "user",
640
+ id: row.member.id
641
+ });
642
+ }, [ onToggle ]);
643
+ return loading || error ? /* @__PURE__ */ jsxRuntime.jsx(PickerStatus, {
550
644
  error: error,
551
- loading: loading,
552
- members: members,
645
+ loading: loading
646
+ }) : /* @__PURE__ */ jsxRuntime.jsx(CommandList, {
647
+ ariaLabel: "Roles and members",
648
+ disabled: row => row.kind === "heading",
649
+ emptyText: "Nothing matches",
650
+ getRowKey: row => row.kind === "heading" ? `heading-${row.label}` : row.kind === "role" ? `role-${row.role}` : `member-${row.member.id}`,
553
651
  onQueryChange: setQuery,
652
+ onSelect: selectRow,
554
653
  placeholder: "Search roles and members…",
555
654
  query: query,
556
- children: [ matchingRoles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
557
- children: "Roles"
558
- }) : null, matchingRoles.map(role => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
655
+ renderRow: (row, active) => row.kind === "heading" ? /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
656
+ children: row.label
657
+ }) : row.kind === "role" ? /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
658
+ active: active,
559
659
  icon: /* @__PURE__ */ jsxRuntime.jsx(Glyph, {
560
660
  children: /* @__PURE__ */ jsxRuntime.jsx(Users.UsersIcon, {})
561
661
  }),
562
- label: role,
563
- onSelect: () => onToggle({
564
- type: "role",
565
- role: role
566
- }),
567
- rowId: `workflow-role-${encodeURIComponent(role)}`,
568
- selected: selectedRoles.has(role)
569
- }, `role:${role}`)), matchingMembers.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
570
- children: "Members"
571
- }) : null, matchingMembers.map(member => /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
662
+ label: row.role,
663
+ onSelect: () => selectRow(row),
664
+ selected: selectedRoles.has(row.role)
665
+ }) : /* @__PURE__ */ jsxRuntime.jsx(PickerRow, {
666
+ active: active,
572
667
  icon: /* @__PURE__ */ jsxRuntime.jsx(MemberAvatar, {
573
- imageUrl: member.imageUrl,
574
- name: member.displayName
575
- }),
576
- label: member.displayName,
577
- onSelect: () => onToggle({
578
- type: "user",
579
- id: member.id
668
+ imageUrl: row.member.imageUrl,
669
+ name: row.member.displayName
580
670
  }),
581
- rowId: `workflow-assignee-${encodeURIComponent(member.id)}`,
582
- selected: selectedUsers.has(member.id)
583
- }, `user:${member.id}`)) ]
671
+ label: row.member.displayName,
672
+ onSelect: () => selectRow(row),
673
+ selected: selectedUsers.has(row.member.id)
674
+ }),
675
+ rows: rows
584
676
  });
585
677
  }
586
678
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
 
3
- import { Badge, Flex, Grid, Box, Text, Card, TextInput, Select, Button, Avatar, AvatarStack, Stack, Spinner } from "@sanity/ui";
3
+ import { Badge, Flex, Grid, Box, Text, Card, TextInput, Select, Button, Avatar, AvatarStack, Spinner } from "@sanity/ui";
4
4
 
5
5
  import { CloseIcon } from "@sanity/icons/Close";
6
6
 
@@ -22,7 +22,7 @@ import { setMonth } from "date-fns/setMonth";
22
22
 
23
23
  import { setYear } from "date-fns/setYear";
24
24
 
25
- import { forwardRef, useState, useCallback, useMemo, useRef, useEffect, createContext, useId, useContext } from "react";
25
+ import { forwardRef, useState, useCallback, useMemo, useRef, useEffect, useId } from "react";
26
26
 
27
27
  import { eachWeekOfInterval } from "date-fns/eachWeekOfInterval";
28
28
 
@@ -40,6 +40,8 @@ import { EmptyIcon } from "@sanity/icons/Empty";
40
40
 
41
41
  import { UsersIcon } from "@sanity/icons/Users";
42
42
 
43
+ import { useVirtualizer } from "@tanstack/react-virtual";
44
+
43
45
  function roleListLabel(roles) {
44
46
  return `${roles.length === 1 ? "Role" : "Roles"}: ${roles.join(", ")}`;
45
47
  }
@@ -186,7 +188,11 @@ function DatePicker({onSelect: onSelect, selectTime: selectTime = !1, value: val
186
188
  setFocusedDate(current => setDate(setMonth(current, month), 1));
187
189
  }, []), moveFocusedDate = useCallback(byMonths => setFocusedDate(current => addMonths(current, byMonths)), []), setFocusedYear = useCallback(year => setFocusedDate(current => setYear(current, year)), []), focusCurrentWeekDay = useCallback(() => {
188
190
  ref.current?.querySelector('[data-focused="true"]')?.focus();
189
- }, []), handleKeyDown = useCallback(event => {
191
+ }, []);
192
+ useEffect(() => {
193
+ ref.current?.querySelector("[data-calendar-grid]")?.focus();
194
+ }, []);
195
+ const handleKeyDown = useCallback(event => {
190
196
  if (!ARROW_KEYS.includes(event.key)) return;
191
197
  if (event.preventDefault(), event.target instanceof HTMLElement && event.target.hasAttribute("data-calendar-grid")) {
192
198
  focusCurrentWeekDay();
@@ -380,57 +386,81 @@ function MemberAvatarGroup({members: members, maxLength: maxLength = 3}) {
380
386
  });
381
387
  }
382
388
 
383
- function PickerStatus({loading: loading, error: error}) {
384
- return loading ? /* @__PURE__ */ jsxs(Flex, {
385
- align: "center",
386
- gap: 2,
387
- padding: 3,
388
- children: [
389
- /* @__PURE__ */ jsx(Spinner, {
390
- muted: !0,
391
- size: 1
392
- }),
393
- /* @__PURE__ */ jsx(Text, {
394
- muted: !0,
395
- size: 1,
396
- children: "Loading members…"
397
- }) ]
398
- }) : error ? /* @__PURE__ */ jsx(Box, {
399
- padding: 3,
400
- children: /* @__PURE__ */ jsxs(Text, {
401
- muted: !0,
402
- size: 1,
403
- children: [ "Could not list members: ", error ]
404
- })
405
- }) : null;
389
+ const ESTIMATED_ROW_HEIGHT = 44, MAX_VISIBLE_ROWS = 8, MAX_VIEWPORT_HEIGHT = ESTIMATED_ROW_HEIGHT * MAX_VISIBLE_ROWS;
390
+
391
+ function hasTouchInput() {
392
+ return typeof navigator > "u" ? !1 : navigator.maxTouchPoints > 0 || globalThis.matchMedia?.("(pointer: coarse)").matches === !0;
406
393
  }
407
394
 
408
- const ActiveRowContext = createContext(void 0);
395
+ function nextEnabledIndex({current: current, direction: direction, disabled: disabled, rows: rows}) {
396
+ for (let offset = 1; offset <= rows.length; offset += 1) {
397
+ const index = (current + direction * offset + rows.length) % rows.length, row = rows[index];
398
+ if (row !== void 0 && !disabled(row)) return index;
399
+ }
400
+ return -1;
401
+ }
402
+
403
+ function arrowDirection(key) {
404
+ if (key === "ArrowDown") return 1;
405
+ if (key === "ArrowUp") return -1;
406
+ }
409
407
 
410
- function PickerShell({children: children, empty: empty, error: error, loading: loading, placeholder: placeholder, query: query, onQueryChange: onQueryChange}) {
411
- const listRef = useRef(null), listId = useId(), [activeIndex, setActiveIndex] = useState(-1), [activeRowId, setActiveRowId] = useState(), handleKeyDown = useCallback(event => {
412
- const rows = listRef.current?.querySelectorAll("[data-picker-row]") ?? [];
413
- if (rows.length === 0) return;
414
- if (event.key === "Enter" && activeIndex >= 0) {
415
- event.preventDefault(), rows[activeIndex]?.click();
408
+ function navigationStart(activeIndex, direction) {
409
+ return activeIndex >= 0 ? activeIndex : direction === 1 ? -1 : 0;
410
+ }
411
+
412
+ function CommandList({ariaLabel: ariaLabel, disabled: disabled = () => !1, emptyText: emptyText, getRowKey: getRowKey, onQueryChange: onQueryChange, onSelect: onSelect, placeholder: placeholder, query: query, renderRow: renderRow, rows: rows}) {
413
+ const inputRef = useRef(null), scrollRef = useRef(null), listId = useId(), [activeIndex, setActiveIndex] = useState(-1), estimatedViewportHeight = Math.min(rows.length, MAX_VISIBLE_ROWS) * ESTIMATED_ROW_HEIGHT, virtualizer = useVirtualizer({
414
+ count: rows.length,
415
+ estimateSize: () => ESTIMATED_ROW_HEIGHT,
416
+ getItemKey: index => {
417
+ const row = rows[index];
418
+ return row === void 0 ? index : getRowKey(row);
419
+ },
420
+ getScrollElement: () => scrollRef.current,
421
+ initialRect: {
422
+ height: estimatedViewportHeight,
423
+ width: 300
424
+ },
425
+ initialMeasurementsCache: rows.map((row, index) => ({
426
+ end: (index + 1) * ESTIMATED_ROW_HEIGHT,
427
+ index: index,
428
+ key: getRowKey(row),
429
+ lane: 0,
430
+ size: ESTIMATED_ROW_HEIGHT,
431
+ start: index * ESTIMATED_ROW_HEIGHT
432
+ })),
433
+ overscan: 5,
434
+ observeElementRect: (_instance, callback) => (callback({
435
+ height: estimatedViewportHeight,
436
+ width: 300
437
+ }), () => {})
438
+ }), viewportHeight = Math.min(virtualizer.getTotalSize(), MAX_VIEWPORT_HEIGHT);
439
+ useEffect(() => {
440
+ hasTouchInput() || inputRef.current?.focus();
441
+ }, []), useEffect(() => setActiveIndex(-1), [ rows ]);
442
+ const moveActive = useCallback(direction => {
443
+ const start = navigationStart(activeIndex, direction), next = nextEnabledIndex({
444
+ current: start,
445
+ direction: direction,
446
+ disabled: disabled,
447
+ rows: rows
448
+ });
449
+ setActiveIndex(next), next >= 0 && virtualizer.scrollToIndex(next, {
450
+ align: "auto"
451
+ });
452
+ }, [ activeIndex, disabled, rows, virtualizer ]), handleKeyDown = useCallback(event => {
453
+ const direction = arrowDirection(event.key);
454
+ if (direction !== void 0) {
455
+ event.preventDefault(), moveActive(direction);
416
456
  return;
417
457
  }
418
- if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
419
- event.preventDefault();
420
- const direction = event.key === "ArrowDown" ? 1 : -1;
421
- setActiveIndex(current => {
422
- let startingIndex = current;
423
- current < 0 && (startingIndex = direction > 0 ? -1 : 0);
424
- const nextIndex = (startingIndex + direction + rows.length) % rows.length, nextRow = rows[nextIndex];
425
- return setActiveRowId(nextRow?.id), nextRow?.scrollIntoView?.({
426
- block: "nearest"
427
- }), nextIndex;
428
- });
429
- }, [ activeIndex ]);
430
- return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
431
- error: error,
432
- loading: loading
433
- }) : /* @__PURE__ */ jsxs(Flex, {
458
+ if (event.key !== "Enter") return;
459
+ const row = rows[activeIndex];
460
+ row === void 0 || disabled(row) || (event.preventDefault(), onSelect(row));
461
+ }, [ activeIndex, disabled, moveActive, onSelect, rows ]), activeRow = activeIndex < 0 ? void 0 : rows[activeIndex], activeId = activeRow === void 0 ? void 0 : `${listId}-${getRowKey(activeRow)}`;
462
+ /* @__PURE__ */
463
+ return jsxs(Flex, {
434
464
  direction: "column",
435
465
  style: {
436
466
  width: 300
@@ -440,65 +470,106 @@ function PickerShell({children: children, empty: empty, error: error, loading: l
440
470
  padding: 1,
441
471
  paddingBottom: 0,
442
472
  children: /* @__PURE__ */ jsx(TextInput, {
443
- "aria-activedescendant": activeRowId,
473
+ "aria-activedescendant": activeId,
444
474
  "aria-controls": listId,
445
475
  "aria-expanded": !0,
446
476
  "aria-label": placeholder,
447
477
  fontSize: 1,
448
- onChange: event => {
449
- setActiveIndex(-1), setActiveRowId(void 0), onQueryChange(event.currentTarget.value);
450
- },
478
+ onChange: event => onQueryChange(event.currentTarget.value),
451
479
  onKeyDown: handleKeyDown,
452
480
  placeholder: placeholder,
453
481
  radius: 2,
482
+ ref: inputRef,
454
483
  role: "combobox",
455
484
  value: query
456
485
  })
457
- }), empty ? /* @__PURE__ */ jsx(Box, {
486
+ }), rows.length === 0 ? /* @__PURE__ */ jsx(Box, {
458
487
  padding: 3,
459
488
  children: /* @__PURE__ */ jsx(Text, {
460
489
  muted: !0,
461
490
  size: 1,
462
- children: "Nothing matches"
491
+ children: emptyText
463
492
  })
464
- }) : /* @__PURE__ */ jsx(Stack, {
465
- as: "ul",
493
+ }) : /* @__PURE__ */ jsx(Box, {
494
+ "aria-label": ariaLabel,
466
495
  id: listId,
467
- ref: listRef,
496
+ overflow: "auto",
497
+ ref: scrollRef,
468
498
  role: "listbox",
469
- padding: 1,
470
- space: 1,
471
499
  style: {
472
- listStyle: "none",
473
- maxHeight: 360,
474
- overflowY: "auto"
500
+ height: viewportHeight
475
501
  },
476
- children: /* @__PURE__ */ jsx(ActiveRowContext, {
477
- value: activeRowId,
478
- children: children
502
+ children: /* @__PURE__ */ jsx(Box, {
503
+ style: {
504
+ height: virtualizer.getTotalSize(),
505
+ position: "relative",
506
+ width: "100%"
507
+ },
508
+ children: virtualizer.getVirtualItems().map(item => {
509
+ const row = rows[item.index];
510
+ if (row === void 0) return null;
511
+ const rowId = `${listId}-${getRowKey(row)}`;
512
+ /* @__PURE__ */
513
+ return jsx(Box, {
514
+ "data-index": item.index,
515
+ ref: virtualizer.measureElement,
516
+ style: {
517
+ position: "absolute",
518
+ transform: `translateY(${item.start}px)`,
519
+ width: "100%"
520
+ },
521
+ children: /* @__PURE__ */ jsx(Box, {
522
+ id: rowId,
523
+ children: renderRow(row, item.index === activeIndex)
524
+ })
525
+ }, item.key);
526
+ })
479
527
  })
480
528
  }) ]
481
529
  });
482
530
  }
483
531
 
484
- function PickerRow({icon: icon, label: label, onSelect: onSelect, rowId: rowId, selected: selected}) {
485
- const active = useContext(ActiveRowContext) === rowId;
532
+ function PickerStatus({loading: loading, error: error}) {
533
+ return loading ? /* @__PURE__ */ jsxs(Flex, {
534
+ align: "center",
535
+ gap: 2,
536
+ padding: 3,
537
+ children: [
538
+ /* @__PURE__ */ jsx(Spinner, {
539
+ muted: !0,
540
+ size: 1
541
+ }),
542
+ /* @__PURE__ */ jsx(Text, {
543
+ muted: !0,
544
+ size: 1,
545
+ children: "Loading members…"
546
+ }) ]
547
+ }) : error ? /* @__PURE__ */ jsx(Box, {
548
+ padding: 3,
549
+ children: /* @__PURE__ */ jsxs(Text, {
550
+ muted: !0,
551
+ size: 1,
552
+ children: [ "Could not list members: ", error ]
553
+ })
554
+ }) : null;
555
+ }
556
+
557
+ function PickerRow({active: active, icon: icon, label: label, onSelect: onSelect, selected: selected}) {
486
558
  /* @__PURE__ */
487
559
  return jsx(Box, {
488
- as: "li",
560
+ paddingX: 1,
561
+ paddingY: .5,
489
562
  children: /* @__PURE__ */ jsx(Button, {
490
563
  "aria-pressed": selected,
491
564
  "aria-selected": active,
492
- "data-picker-row": !0,
493
- id: rowId,
494
565
  justify: "flex-start",
495
566
  mode: active ? "ghost" : "bleed",
496
567
  onClick: onSelect,
497
568
  padding: 2,
569
+ role: "option",
498
570
  style: {
499
571
  width: "100%"
500
572
  },
501
- role: "option",
502
573
  children: /* @__PURE__ */ jsxs(Flex, {
503
574
  align: "center",
504
575
  gap: 2,
@@ -530,41 +601,45 @@ function Glyph({children: children}) {
530
601
  }
531
602
 
532
603
  function MemberPicker({error: error, loading: loading, members: members, onSelect: onSelect, selectedIds: selectedIds, unassignRow: unassignRow = !1}) {
533
- const [query, setQuery] = useState(""), matchingMembers = useMemo(() => members.filter(member => matchesMemberSearch(member, query)), [ members, query ]), showUnassigned = unassignRow && query.trim() === "";
534
- /* @__PURE__ */
535
- return jsxs(PickerShell, {
536
- empty: !showUnassigned && matchingMembers.length === 0,
604
+ const [query, setQuery] = useState(""), rows = useMemo(() => {
605
+ const matches = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
606
+ kind: "member",
607
+ member: member
608
+ }));
609
+ return unassignRow && query.trim() === "" ? [ {
610
+ kind: "unassigned"
611
+ }, ...matches ] : matches;
612
+ }, [ members, query, unassignRow ]), selectRow = useCallback(row => onSelect(row.kind === "member" ? row.member : null), [ onSelect ]);
613
+ return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
537
614
  error: error,
538
- loading: loading,
539
- members: members,
615
+ loading: loading
616
+ }) : /* @__PURE__ */ jsx(CommandList, {
617
+ ariaLabel: "Project members",
618
+ emptyText: "Nothing matches",
619
+ getRowKey: row => row.kind === "member" ? `member-${row.member.id}` : "unassigned",
540
620
  onQueryChange: setQuery,
621
+ onSelect: selectRow,
541
622
  placeholder: "Search members…",
542
623
  query: query,
543
- children: [ showUnassigned ? /* @__PURE__ */ jsx(PickerRow, {
544
- icon: /* @__PURE__ */ jsx(Glyph, {
624
+ renderRow: (row, active) => /* @__PURE__ */ jsx(PickerRow, {
625
+ active: active,
626
+ icon: row.kind === "member" ? /* @__PURE__ */ jsx(MemberAvatar, {
627
+ imageUrl: row.member.imageUrl,
628
+ name: row.member.displayName
629
+ }) : /* @__PURE__ */ jsx(Glyph, {
545
630
  children: /* @__PURE__ */ jsx(EmptyIcon, {})
546
631
  }),
547
- label: "No assignee",
548
- onSelect: () => onSelect(null),
549
- rowId: "workflow-member-unassigned",
550
- selected: selectedIds.size === 0
551
- }) : null, matchingMembers.map(member => /* @__PURE__ */ jsx(PickerRow, {
552
- icon: /* @__PURE__ */ jsx(MemberAvatar, {
553
- imageUrl: member.imageUrl,
554
- name: member.displayName
555
- }),
556
- label: member.displayName,
557
- onSelect: () => onSelect(member),
558
- rowId: `workflow-member-${encodeURIComponent(member.id)}`,
559
- selected: selectedIds.has(member.id)
560
- }, member.id)) ]
632
+ label: row.kind === "member" ? row.member.displayName : "No assignee",
633
+ onSelect: () => selectRow(row),
634
+ selected: row.kind === "member" ? selectedIds.has(row.member.id) : selectedIds.size === 0
635
+ }),
636
+ rows: rows
561
637
  });
562
638
  }
563
639
 
564
640
  function GroupHeading({children: children}) {
565
641
  /* @__PURE__ */
566
642
  return jsx(Card, {
567
- as: "li",
568
643
  borderTop: !0,
569
644
  padding: 2,
570
645
  children: /* @__PURE__ */ jsx(Text, {
@@ -577,44 +652,63 @@ function GroupHeading({children: children}) {
577
652
  }
578
653
 
579
654
  function AssigneePicker({error: error, loading: loading, members: members, onToggle: onToggle, value: value}) {
580
- const [query, setQuery] = useState(""), normalized = query.trim().toLowerCase(), matchingRoles = useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]).filter(role => role.toLowerCase().includes(normalized)), matchingMembers = members.filter(member => matchesMemberSearch(member, query)), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : []));
581
- /* @__PURE__ */
582
- return jsxs(PickerShell, {
583
- empty: matchingRoles.length === 0 && matchingMembers.length === 0,
655
+ const [query, setQuery] = useState(""), roles = useMemo(() => [ ...new Set(members.flatMap(member => member.roles)) ].toSorted(), [ members ]), rows = useMemo(() => {
656
+ const normalized = query.trim().toLowerCase(), roleRows = roles.filter(role => role.toLowerCase().includes(normalized)).map(role => ({
657
+ kind: "role",
658
+ role: role
659
+ })), memberRows = members.filter(member => matchesMemberSearch(member, query)).map(member => ({
660
+ kind: "member",
661
+ member: member
662
+ }));
663
+ return [ ...roleRows.length > 0 ? [ {
664
+ kind: "heading",
665
+ label: "Roles"
666
+ } ] : [], ...roleRows, ...memberRows.length > 0 ? [ {
667
+ kind: "heading",
668
+ label: "Members"
669
+ } ] : [], ...memberRows ];
670
+ }, [ members, query, roles ]), selectedRoles = new Set(value.flatMap(item => item.type === "role" ? [ item.role ] : [])), selectedUsers = new Set(value.flatMap(item => item.type === "user" ? [ item.id ] : [])), selectRow = useCallback(row => {
671
+ row.kind === "role" && onToggle({
672
+ type: "role",
673
+ role: row.role
674
+ }), row.kind === "member" && onToggle({
675
+ type: "user",
676
+ id: row.member.id
677
+ });
678
+ }, [ onToggle ]);
679
+ return loading || error ? /* @__PURE__ */ jsx(PickerStatus, {
584
680
  error: error,
585
- loading: loading,
586
- members: members,
681
+ loading: loading
682
+ }) : /* @__PURE__ */ jsx(CommandList, {
683
+ ariaLabel: "Roles and members",
684
+ disabled: row => row.kind === "heading",
685
+ emptyText: "Nothing matches",
686
+ getRowKey: row => row.kind === "heading" ? `heading-${row.label}` : row.kind === "role" ? `role-${row.role}` : `member-${row.member.id}`,
587
687
  onQueryChange: setQuery,
688
+ onSelect: selectRow,
588
689
  placeholder: "Search roles and members…",
589
690
  query: query,
590
- children: [ matchingRoles.length > 0 ? /* @__PURE__ */ jsx(GroupHeading, {
591
- children: "Roles"
592
- }) : null, matchingRoles.map(role => /* @__PURE__ */ jsx(PickerRow, {
691
+ renderRow: (row, active) => row.kind === "heading" ? /* @__PURE__ */ jsx(GroupHeading, {
692
+ children: row.label
693
+ }) : row.kind === "role" ? /* @__PURE__ */ jsx(PickerRow, {
694
+ active: active,
593
695
  icon: /* @__PURE__ */ jsx(Glyph, {
594
696
  children: /* @__PURE__ */ jsx(UsersIcon, {})
595
697
  }),
596
- label: role,
597
- onSelect: () => onToggle({
598
- type: "role",
599
- role: role
600
- }),
601
- rowId: `workflow-role-${encodeURIComponent(role)}`,
602
- selected: selectedRoles.has(role)
603
- }, `role:${role}`)), matchingMembers.length > 0 ? /* @__PURE__ */ jsx(GroupHeading, {
604
- children: "Members"
605
- }) : null, matchingMembers.map(member => /* @__PURE__ */ jsx(PickerRow, {
698
+ label: row.role,
699
+ onSelect: () => selectRow(row),
700
+ selected: selectedRoles.has(row.role)
701
+ }) : /* @__PURE__ */ jsx(PickerRow, {
702
+ active: active,
606
703
  icon: /* @__PURE__ */ jsx(MemberAvatar, {
607
- imageUrl: member.imageUrl,
608
- name: member.displayName
609
- }),
610
- label: member.displayName,
611
- onSelect: () => onToggle({
612
- type: "user",
613
- id: member.id
704
+ imageUrl: row.member.imageUrl,
705
+ name: row.member.displayName
614
706
  }),
615
- rowId: `workflow-assignee-${encodeURIComponent(member.id)}`,
616
- selected: selectedUsers.has(member.id)
617
- }, `user:${member.id}`)) ]
707
+ label: row.member.displayName,
708
+ onSelect: () => selectRow(row),
709
+ selected: selectedUsers.has(row.member.id)
710
+ }),
711
+ rows: rows
618
712
  });
619
713
  }
620
714
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-components",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Reusable React components for Editorial Workflows member, assignee, and date fields.",
5
5
  "keywords": [
6
6
  "react",
@@ -43,8 +43,9 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@sanity/icons": "^4.1.0",
46
+ "@tanstack/react-virtual": "^3.14.6",
46
47
  "date-fns": "^4.4.0",
47
- "@sanity/workflow-engine": "0.17.0"
48
+ "@sanity/workflow-engine": "0.18.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@sanity/pkg-utils": "^10.5.3",