@fluid-app/portal-sdk 0.1.217 → 0.1.219

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 (81) hide show
  1. package/dist/{ContactsScreen-D-g7XEB-.cjs → ContactsScreen-DYVR-UvO.cjs} +1 -1
  2. package/dist/{FluidProvider-02beRTpR.mjs → FluidProvider-DTttgSXi.mjs} +8 -8
  3. package/dist/{FluidProvider-02beRTpR.mjs.map → FluidProvider-DTttgSXi.mjs.map} +1 -1
  4. package/dist/{FluidProvider-BgFmXtHo.cjs → FluidProvider-Dm4BhF_t.cjs} +8 -8
  5. package/dist/{FluidProvider-BgFmXtHo.cjs.map → FluidProvider-Dm4BhF_t.cjs.map} +1 -1
  6. package/dist/{ListWidget-C0ltFhxE.cjs → ListWidget-BE7uA4W4.cjs} +1 -2
  7. package/dist/{ListWidget-Ct8IDkio.mjs → ListWidget-D7y8bfvT.mjs} +252 -120
  8. package/dist/ListWidget-D7y8bfvT.mjs.map +1 -0
  9. package/dist/{ListWidget-C7ouNpEo.cjs → ListWidget-DJ-_dMOK.cjs} +251 -119
  10. package/dist/ListWidget-DJ-_dMOK.cjs.map +1 -0
  11. package/dist/{MessagingScreen-CJlVOY5R.mjs → MessagingScreen-CwEXQJlW.mjs} +2 -2
  12. package/dist/{MessagingScreen-CJlVOY5R.mjs.map → MessagingScreen-CwEXQJlW.mjs.map} +1 -1
  13. package/dist/{MessagingScreen-BkLcqJbs.cjs → MessagingScreen-DLQ5V0m4.cjs} +2 -2
  14. package/dist/{MessagingScreen-BkLcqJbs.cjs.map → MessagingScreen-DLQ5V0m4.cjs.map} +1 -1
  15. package/dist/{MessagingScreen-sN7aBvGk.cjs → MessagingScreen-YBXJL7a9.cjs} +6 -6
  16. package/dist/{MySiteScreen-DUN5TTvU.mjs → MySiteScreen-CyzM9hX3.mjs} +11 -30
  17. package/dist/{MySiteScreen-DUN5TTvU.mjs.map → MySiteScreen-CyzM9hX3.mjs.map} +1 -1
  18. package/dist/{MySiteScreen-B0aOIzU4.cjs → MySiteScreen-DPQ66oRs.cjs} +2 -2
  19. package/dist/{MySiteScreen-B1L8coGs.cjs → MySiteScreen-DxLcG3-S.cjs} +11 -30
  20. package/dist/MySiteScreen-DxLcG3-S.cjs.map +1 -0
  21. package/dist/{MySiteWidget-CDQjyrRA.cjs → MySiteWidget-8UsfEK-w.cjs} +1 -1
  22. package/dist/{MySiteWidget-CDQjyrRA.cjs.map → MySiteWidget-8UsfEK-w.cjs.map} +1 -1
  23. package/dist/{MySiteWidget-BlUbduit.mjs → MySiteWidget-D46TVWnf.mjs} +1 -1
  24. package/dist/{MySiteWidget-BlUbduit.mjs.map → MySiteWidget-D46TVWnf.mjs.map} +1 -1
  25. package/dist/{NestedWidget-ChAWwsRP.cjs → NestedWidget-BjT-UrSi.cjs} +2 -2
  26. package/dist/{NestedWidget-ChAWwsRP.cjs.map → NestedWidget-BjT-UrSi.cjs.map} +1 -1
  27. package/dist/{NestedWidget-Dw0QHoqw.mjs → NestedWidget-CEeQeCq0.mjs} +2 -2
  28. package/dist/{NestedWidget-Dw0QHoqw.mjs.map → NestedWidget-CEeQeCq0.mjs.map} +1 -1
  29. package/dist/{NestedWidget--suTLM6l.cjs → NestedWidget-D4amXykk.cjs} +2 -2
  30. package/dist/{PortalContentApiProvider-RXBp8FNj.cjs → PortalContentApiProvider-DXnplIOD.cjs} +273 -82
  31. package/dist/PortalContentApiProvider-DXnplIOD.cjs.map +1 -0
  32. package/dist/{PortalContentApiProvider-C9FeVwRb.mjs → PortalContentApiProvider-x81DXmOR.mjs} +273 -82
  33. package/dist/PortalContentApiProvider-x81DXmOR.mjs.map +1 -0
  34. package/dist/{ProductsScreen-DNpzJ6lh.cjs → ProductsScreen-3yjIMjYY.cjs} +2 -2
  35. package/dist/{ProductsScreen-pkOeOW8M.mjs → ProductsScreen-BVyLOe2K.mjs} +2 -2
  36. package/dist/{ProductsScreen-BD53vh6Y.cjs → ProductsScreen-R0MfWYZJ.cjs} +2 -2
  37. package/dist/{ProductsScreen-BD53vh6Y.cjs.map → ProductsScreen-R0MfWYZJ.cjs.map} +1 -1
  38. package/dist/{ProductsScreen-BtUZxJCt.mjs → ProductsScreen-z9hXfFeJ.mjs} +2 -2
  39. package/dist/{ProductsScreen-BtUZxJCt.mjs.map → ProductsScreen-z9hXfFeJ.mjs.map} +1 -1
  40. package/dist/{ProfileScreen-rPqgsNCc.cjs → ProfileScreen-B0WRifk_.cjs} +2 -2
  41. package/dist/{ProfileScreen-rPqgsNCc.cjs.map → ProfileScreen-B0WRifk_.cjs.map} +1 -1
  42. package/dist/{ProfileScreen-CNYqUDNB.mjs → ProfileScreen-BuejQU_V.mjs} +2 -2
  43. package/dist/{ProfileScreen-CNYqUDNB.mjs.map → ProfileScreen-BuejQU_V.mjs.map} +1 -1
  44. package/dist/{ProfileScreen-Bgo6iTKe.cjs → ProfileScreen-g3se9Jw-.cjs} +6 -6
  45. package/dist/{ShareablesScreen-DC8xXUo4.cjs → ShareablesScreen-Co_gFZva.cjs} +3 -3
  46. package/dist/{ShareablesScreen-DC8xXUo4.cjs.map → ShareablesScreen-Co_gFZva.cjs.map} +1 -1
  47. package/dist/{ShareablesScreen-D1J2Kljk.mjs → ShareablesScreen-DpwFh0ky.mjs} +3 -3
  48. package/dist/{ShareablesScreen-smU5pGyH.cjs → ShareablesScreen-YlMqyP-B.cjs} +3 -3
  49. package/dist/{ShareablesScreen-CW1e9x4K.mjs → ShareablesScreen-tkaf9R5N.mjs} +3 -3
  50. package/dist/{ShareablesScreen-CW1e9x4K.mjs.map → ShareablesScreen-tkaf9R5N.mjs.map} +1 -1
  51. package/dist/{ShopScreen-B9lHJ8L2.mjs → ShopScreen-B4afB5sE.mjs} +2 -2
  52. package/dist/{ShopScreen-B9lHJ8L2.mjs.map → ShopScreen-B4afB5sE.mjs.map} +1 -1
  53. package/dist/{ShopScreen-g6FQ4R1_.cjs → ShopScreen-BOjri6Dm.cjs} +6 -6
  54. package/dist/{ShopScreen-DUHzufCU.cjs → ShopScreen-Untg6XBY.cjs} +2 -2
  55. package/dist/{ShopScreen-DUHzufCU.cjs.map → ShopScreen-Untg6XBY.cjs.map} +1 -1
  56. package/dist/index.cjs +27 -27
  57. package/dist/index.d.cts.map +1 -1
  58. package/dist/index.d.mts.map +1 -1
  59. package/dist/index.mjs +25 -25
  60. package/dist/{portal_tenant_content-0zpnjBot.cjs → portal_tenant_content-BvYxmADB.cjs} +18 -1
  61. package/dist/portal_tenant_content-BvYxmADB.cjs.map +1 -0
  62. package/dist/{portal_tenant_content-DzIQtSLE.mjs → portal_tenant_content-nHEI2qEY.mjs} +13 -2
  63. package/dist/portal_tenant_content-nHEI2qEY.mjs.map +1 -0
  64. package/dist/{scroll-arrows-fK_MFlSX.mjs → scroll-arrows-COrfvJk9.mjs} +1 -1
  65. package/dist/{scroll-arrows-fK_MFlSX.mjs.map → scroll-arrows-COrfvJk9.mjs.map} +1 -1
  66. package/dist/{scroll-arrows-DVwMDTt3.cjs → scroll-arrows-i0E2N-ct.cjs} +1 -1
  67. package/dist/{scroll-arrows-DVwMDTt3.cjs.map → scroll-arrows-i0E2N-ct.cjs.map} +1 -1
  68. package/dist/{use-mysite-portal-BV-BP3CE.mjs → use-mysite-portal-D29HLD1z.mjs} +3 -2
  69. package/dist/use-mysite-portal-D29HLD1z.mjs.map +1 -0
  70. package/dist/{use-mysite-portal-DzDYRU0u.cjs → use-mysite-portal-DxNQ3uTi.cjs} +3 -2
  71. package/dist/use-mysite-portal-DxNQ3uTi.cjs.map +1 -0
  72. package/package.json +9 -9
  73. package/dist/ListWidget-C7ouNpEo.cjs.map +0 -1
  74. package/dist/ListWidget-Ct8IDkio.mjs.map +0 -1
  75. package/dist/MySiteScreen-B1L8coGs.cjs.map +0 -1
  76. package/dist/PortalContentApiProvider-C9FeVwRb.mjs.map +0 -1
  77. package/dist/PortalContentApiProvider-RXBp8FNj.cjs.map +0 -1
  78. package/dist/portal_tenant_content-0zpnjBot.cjs.map +0 -1
  79. package/dist/portal_tenant_content-DzIQtSLE.mjs.map +0 -1
  80. package/dist/use-mysite-portal-BV-BP3CE.mjs.map +0 -1
  81. package/dist/use-mysite-portal-DzDYRU0u.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"NestedWidget-Dw0QHoqw.mjs","names":[],"sources":["../../widgets/src/widgets/NestedWidget.tsx"],"sourcesContent":["import { useRef, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport {\n MediaRenderer,\n getMediaPropsFromShareable,\n} from \"../components/MediaRenderer\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n AlignOptions,\n GapOptions,\n BackgroundValue,\n} from \"@fluid-app/portal-core/types\";\nimport {\n getHeightField,\n type WidgetPropertySchema,\n} from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getGapField,\n getPaddingField,\n gapValues,\n} from \"../core/fields\";\nimport { ScrollArrows } from \"../ui/scroll-arrows\";\nimport { type ShareableItem } from \"@fluid-app/portal-core/types\";\nimport { useWidgetInteraction } from \"../contexts/WidgetInteractionContext\";\n\nconst DEFAULT_SHAREABLES: ShareableItem[] = [];\n\ntype NestedWidgetProps = ComponentProps<\"div\"> & {\n // Content\n resource?: ShareableItem;\n titleEnabled?: boolean;\n titleText?: string;\n shareables?: ShareableItem[];\n\n // Layout\n gap?: GapOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n primaryMediaHeight?: string;\n\n // Title styling\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n titleAlignment?: AlignOptions;\n\n // Nested media styling\n nestedTextColor?: ColorOptions;\n background?: BackgroundValue;\n\n // Overlay\n overlayEnabled?: boolean;\n overlayType?: \"solid\" | \"gradient\";\n overlayIntensity?: number;\n};\n\nexport function NestedWidget({\n resource,\n titleEnabled = true,\n titleText = \"Featured Collection\",\n shareables = DEFAULT_SHAREABLES,\n gap = \"md\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n primaryMediaHeight = \"400px\",\n titleFontSize = \"xl\",\n titleColor = \"background\",\n titleAlignment = { horizontal: \"left\", vertical: \"bottom\" },\n nestedTextColor = \"foreground\",\n background = {\n type: \"solid\",\n color: \"background\",\n },\n overlayEnabled = true,\n overlayType = \"gradient\",\n overlayIntensity = 50,\n className,\n ...props\n}: NestedWidgetProps): React.JSX.Element {\n const scrollContainerRef = useRef<HTMLDivElement>(null);\n\n const scrollByAmount = (direction: \"prev\" | \"next\") => {\n const container = scrollContainerRef.current;\n if (!container) return;\n\n const computedGap = parseFloat(getComputedStyle(container).gap) || 0;\n const firstItem = container.firstElementChild as HTMLElement | null;\n const itemWidth = firstItem?.offsetWidth ?? 0;\n const scrollAmount = itemWidth + computedGap;\n\n container.scrollTo({\n left:\n container.scrollLeft +\n (direction === \"next\" ? scrollAmount : -scrollAmount),\n behavior: \"smooth\",\n });\n };\n\n // Cap border radius for the primary media container: \"full\" creates a circle that clips\n // the absolutely-positioned title overlay to the narrow bottom of the circle.\n const primaryMediaRadius = borderRadius === \"full\" ? \"2xl\" : borderRadius;\n\n const { onItemClick } = useWidgetInteraction();\n\n const hasNestedMedia = shareables.length > 0;\n\n const titleAlignmentClasses = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n };\n\n const backgroundColor = background.color || \"background\";\n const backgroundImage =\n (background.resource?.image_url || background.resource?.imageUrl) &&\n background.type === \"image\"\n ? `url(${background.resource.image_url || background.resource.imageUrl})`\n : \"none\";\n\n return (\n <div\n className={`@container flex w-full overflow-hidden p-${padding} rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} ${className}`}\n {...props}\n style={{\n maxHeight: primaryMediaHeight,\n backgroundImage: backgroundImage,\n }}\n >\n {/* Primary Media Container - Full width on mobile, fixed on desktop */}\n <div\n className={`relative @md:flex-none`}\n style={{\n width: primaryMediaHeight,\n }}\n >\n {/* Media + overlay clipped to rounded shape — title is a sibling so it isn't clipped */}\n <div\n className={`overflow-hidden rounded-${primaryMediaRadius} absolute inset-0`}\n >\n <MediaRenderer\n {...(resource ? getMediaPropsFromShareable(resource) : {})}\n autoplay\n loop\n muted\n />\n\n {/* Overlay */}\n {overlayEnabled && (\n <div\n className={`pointer-events-none absolute inset-0 z-10 ${\n overlayType === \"gradient\"\n ? \"bg-gradient-to-t from-black to-transparent\"\n : \"bg-black\"\n }`}\n style={{\n opacity:\n (Number(String(overlayIntensity).replace(\"%\", \"\")) || 50) /\n 100,\n }}\n />\n )}\n </div>\n\n {/* Title and Mobile Nested Media — outside the overflow-hidden clip */}\n {((titleEnabled && titleText) || hasNestedMedia) && (\n <div\n className={`absolute z-20 w-full ${titleAlignmentClasses[titleAlignment?.horizontal ?? \"left\"]} p-${padding} ${\n titleAlignment.vertical === \"top\"\n ? `top-0 pt-${padding}`\n : titleAlignment.vertical === \"center\"\n ? \"top-1/2 -translate-y-1/2\"\n : `bottom-0 pb-${padding}`\n }`}\n >\n {titleEnabled && titleText && (\n <h2\n className={`font-header leading-tight font-bold text-${titleColor} text-${titleFontSize === \"md\" ? \"base\" : titleFontSize}`}\n >\n {titleText}\n </h2>\n )}\n\n {/* Mobile: Products overlay inside primary media */}\n {hasNestedMedia && (\n <div className={`pt-${padding} @md:hidden`}>\n <div\n className={`flex overflow-x-auto gap-${gapValues[gap]} bg-transparent`}\n >\n {shareables.map((shareable) => (\n <div\n key={shareable.id}\n className={`flex shrink-0 flex-col items-center ${onItemClick ? \"cursor-pointer\" : \"\"}`}\n {...(onItemClick\n ? {\n role: \"button\",\n tabIndex: 0,\n onClick: () => onItemClick(shareable),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onItemClick(shareable);\n }\n },\n }\n : {})}\n >\n <div\n className={`aspect-3/4 h-40 overflow-hidden rounded-${borderRadius}`}\n >\n <MediaRenderer\n {...getMediaPropsFromShareable(shareable)}\n />\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Desktop: Products Container - Single row beside primary media */}\n {hasNestedMedia && (\n <div\n className={`relative hidden min-w-0 self-stretch @md:flex @md:flex-1 px-${padding}`}\n >\n <div\n ref={scrollContainerRef}\n className={`flex h-full flex-row overflow-x-auto gap-${gapValues[gap]}`}\n style={{ scrollSnapType: \"x mandatory\" }}\n >\n {shareables.map((shareable) => (\n <div\n key={shareable.id}\n className={`flex shrink-0 flex-col gap-1 ${onItemClick ? \"cursor-pointer\" : \"\"}`}\n style={{\n width: `calc(${primaryMediaHeight} * 0.75)`,\n scrollSnapAlign: \"start\",\n }}\n {...(onItemClick\n ? {\n role: \"button\",\n tabIndex: 0,\n onClick: () => onItemClick(shareable),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onItemClick(shareable);\n }\n },\n }\n : {})}\n >\n <div\n className={`aspect-3/4 h-full rounded-${borderRadius} overflow-hidden`}\n >\n <MediaRenderer {...getMediaPropsFromShareable(shareable)} />\n </div>\n <span className={`flex-none text-sm text-${nestedTextColor}`}>\n <p className=\"truncate\">{shareable.title || \"\"}</p>\n <p className=\"truncate\">\n {((shareable.display_price as string) ?? shareable.price) ||\n \"\"}\n </p>\n </span>\n </div>\n ))}\n </div>\n\n {/* Navigation arrows */}\n <div\n className={`absolute inset-x-0 top-1/2 flex w-full -translate-y-1/2 items-center justify-between px-8`}\n >\n <ScrollArrows\n onPrevious={() => scrollByAmount(\"prev\")}\n onNext={() => scrollByAmount(\"next\")}\n />\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const nestedWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"NestedWidget\",\n displayName: \"Nested Widget\",\n tabsConfig: [\n { id: \"styling\", label: \"Styling\" },\n { id: \"data\", label: \"Data\" },\n ],\n dataSourceTargetProps: [\"shareables\"],\n fields: [\n // Content tab - Resource group\n {\n key: \"resource\",\n label: \"Primary Media\",\n type: \"resource\",\n description: \"Select the primary media displayed in the large panel\",\n allowedTypes: [\"Medium\"],\n tab: \"styling\",\n group: \"Content\",\n },\n // Content tab - Title group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed over the primary media\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Main title displayed over the primary media\",\n defaultValue: \"Featured Collection\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n label: \"Title Font Size\",\n defaultValue: \"xl\",\n key: \"titleFontSize\",\n description: \"Font size for the widget title\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n defaultValue: \"background\",\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n // Styling tab - Design group\n getHeightField({\n key: \"primaryMediaHeight\",\n label: \"Primary Media Height\",\n description: \"Height of the primary media container\",\n defaultValue: \"400px\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"separator\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"titleAlignment\",\n label: \"Content Alignment\",\n type: \"alignment\",\n description: \"Alignment of the content inside the primary media\",\n defaultValue: { horizontal: \"left\", vertical: \"bottom\" },\n options: {\n horizontalEnabled: true,\n verticalEnabled: true,\n },\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"separator2\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n defaultValue: 4,\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding used throughout the widget\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n defaultValue: \"md\",\n label: \"Border Radius\",\n key: \"borderRadius\",\n description: \"Rounded corners for the widget\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: \"Border Width\",\n description: \"Border width for the widget\",\n defaultValue: \"none\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: \"Border Color\",\n description: \"Border color for the widget\",\n defaultValue: \"muted\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"overlayEnabled\",\n label: \"Enable Overlay\",\n type: \"boolean\",\n description:\n \"Add a dark overlay to the primary media for better text readability\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"overlayType\",\n label: \"Overlay Type\",\n type: \"buttonGroup\",\n description: \"Type of overlay effect\",\n defaultValue: \"gradient\",\n options: [\n { label: \"Solid\", value: \"solid\" },\n { label: \"Gradient\", value: \"gradient\" },\n ],\n tab: \"styling\",\n group: \"Design\",\n requiresKeyToBeTrue: \"overlayEnabled\",\n },\n {\n key: \"overlayIntensity\",\n label: \"Overlay Intensity\",\n type: \"slider\",\n description: \"Opacity of the overlay (0-100)\",\n min: 0,\n max: 100,\n step: 5,\n defaultValue: 50,\n unit: \"%\",\n tab: \"styling\",\n group: \"Design\",\n requiresKeyToBeTrue: \"overlayEnabled\",\n },\n // Styling tab - Nested Media Styling group\n getGapField({\n label: \"Gap\",\n defaultValue: \"md\",\n key: \"gap\",\n description: \"Gap between nested media items\",\n tab: \"styling\",\n group: \"Nested Design\",\n }),\n getColorField({\n defaultValue: \"foreground\",\n key: \"nestedTextColor\",\n label: \"Nested Text Color\",\n description: \"Color for nested media labels\",\n tab: \"styling\",\n group: \"Nested Design\",\n }),\n {\n type: \"background\",\n defaultValue: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background color for nested media container\",\n tab: \"styling\",\n group: \"Nested Design\",\n },\n // Data tab\n {\n key: \"dataSource\",\n label: \"Data Source\",\n type: \"dataSource\",\n description: \"Configure dynamic data fetching from an API\",\n tab: \"data\",\n group: \"Data Configuration\",\n },\n ],\n // Per-item configuration schema for custom data sources\n itemConfigSchema: {\n description: \"Configure settings for this item\",\n fields: [\n {\n key: \"title\",\n label: \"Custom Title\",\n type: \"text\",\n description: \"Override the item's title for this widget\",\n },\n ],\n },\n} as const satisfies WidgetPropertySchema;\n"],"mappings":"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ACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;IAAE,YAAY;IAAQ,UAAU;IAAU;GACxD,SAAS;IACP,mBAAmB;IACnB,iBAAiB;IAClB;GACD,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD,gBAAgB;GACd,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,qBAAqB;GACnB,cAAc;GACd,OAAO;GACP,KAAK;GACL,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aACE;GACF,cAAc;GACd,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;GACd,SAAS,CACP;IAAE,OAAO;IAAS,OAAO;IAAS,EAClC;IAAE,OAAO;IAAY,OAAO;IAAY,CACzC;GACD,KAAK;GACL,OAAO;GACP,qBAAqB;GACtB;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,KAAK;GACL,MAAM;GACN,cAAc;GACd,MAAM;GACN,KAAK;GACL,OAAO;GACP,qBAAqB;GACtB;EAED,YAAY;GACV,OAAO;GACP,cAAc;GACd,KAAK;GACL,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,cAAc;GACZ,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,MAAM;GACN,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR;EAED;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,OAAO;GACR;EACF;CAED,kBAAkB;EAChB,aAAa;EACb,QAAQ,CACN;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACd,CACF;EACF;CACF"}
1
+ {"version":3,"file":"NestedWidget-CEeQeCq0.mjs","names":[],"sources":["../../widgets/src/widgets/NestedWidget.tsx"],"sourcesContent":["import { useRef, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport {\n MediaRenderer,\n getMediaPropsFromShareable,\n} from \"../components/MediaRenderer\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n AlignOptions,\n GapOptions,\n BackgroundValue,\n} from \"@fluid-app/portal-core/types\";\nimport {\n getHeightField,\n type WidgetPropertySchema,\n} from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getGapField,\n getPaddingField,\n gapValues,\n} from \"../core/fields\";\nimport { ScrollArrows } from \"../ui/scroll-arrows\";\nimport { type ShareableItem } from \"@fluid-app/portal-core/types\";\nimport { useWidgetInteraction } from \"../contexts/WidgetInteractionContext\";\n\nconst DEFAULT_SHAREABLES: ShareableItem[] = [];\n\ntype NestedWidgetProps = ComponentProps<\"div\"> & {\n // Content\n resource?: ShareableItem;\n titleEnabled?: boolean;\n titleText?: string;\n shareables?: ShareableItem[];\n\n // Layout\n gap?: GapOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n primaryMediaHeight?: string;\n\n // Title styling\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n titleAlignment?: AlignOptions;\n\n // Nested media styling\n nestedTextColor?: ColorOptions;\n background?: BackgroundValue;\n\n // Overlay\n overlayEnabled?: boolean;\n overlayType?: \"solid\" | \"gradient\";\n overlayIntensity?: number;\n};\n\nexport function NestedWidget({\n resource,\n titleEnabled = true,\n titleText = \"Featured Collection\",\n shareables = DEFAULT_SHAREABLES,\n gap = \"md\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n primaryMediaHeight = \"400px\",\n titleFontSize = \"xl\",\n titleColor = \"background\",\n titleAlignment = { horizontal: \"left\", vertical: \"bottom\" },\n nestedTextColor = \"foreground\",\n background = {\n type: \"solid\",\n color: \"background\",\n },\n overlayEnabled = true,\n overlayType = \"gradient\",\n overlayIntensity = 50,\n className,\n ...props\n}: NestedWidgetProps): React.JSX.Element {\n const scrollContainerRef = useRef<HTMLDivElement>(null);\n\n const scrollByAmount = (direction: \"prev\" | \"next\") => {\n const container = scrollContainerRef.current;\n if (!container) return;\n\n const computedGap = parseFloat(getComputedStyle(container).gap) || 0;\n const firstItem = container.firstElementChild as HTMLElement | null;\n const itemWidth = firstItem?.offsetWidth ?? 0;\n const scrollAmount = itemWidth + computedGap;\n\n container.scrollTo({\n left:\n container.scrollLeft +\n (direction === \"next\" ? scrollAmount : -scrollAmount),\n behavior: \"smooth\",\n });\n };\n\n // Cap border radius for the primary media container: \"full\" creates a circle that clips\n // the absolutely-positioned title overlay to the narrow bottom of the circle.\n const primaryMediaRadius = borderRadius === \"full\" ? \"2xl\" : borderRadius;\n\n const { onItemClick } = useWidgetInteraction();\n\n const hasNestedMedia = shareables.length > 0;\n\n const titleAlignmentClasses = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n };\n\n const backgroundColor = background.color || \"background\";\n const backgroundImage =\n (background.resource?.image_url || background.resource?.imageUrl) &&\n background.type === \"image\"\n ? `url(${background.resource.image_url || background.resource.imageUrl})`\n : \"none\";\n\n return (\n <div\n className={`@container flex w-full overflow-hidden p-${padding} rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} ${className}`}\n {...props}\n style={{\n maxHeight: primaryMediaHeight,\n backgroundImage: backgroundImage,\n }}\n >\n {/* Primary Media Container - Full width on mobile, fixed on desktop */}\n <div\n className={`relative @md:flex-none`}\n style={{\n width: primaryMediaHeight,\n }}\n >\n {/* Media + overlay clipped to rounded shape — title is a sibling so it isn't clipped */}\n <div\n className={`overflow-hidden rounded-${primaryMediaRadius} absolute inset-0`}\n >\n <MediaRenderer\n {...(resource ? getMediaPropsFromShareable(resource) : {})}\n autoplay\n loop\n muted\n />\n\n {/* Overlay */}\n {overlayEnabled && (\n <div\n className={`pointer-events-none absolute inset-0 z-10 ${\n overlayType === \"gradient\"\n ? \"bg-gradient-to-t from-black to-transparent\"\n : \"bg-black\"\n }`}\n style={{\n opacity:\n (Number(String(overlayIntensity).replace(\"%\", \"\")) || 50) /\n 100,\n }}\n />\n )}\n </div>\n\n {/* Title and Mobile Nested Media — outside the overflow-hidden clip */}\n {((titleEnabled && titleText) || hasNestedMedia) && (\n <div\n className={`absolute z-20 w-full ${titleAlignmentClasses[titleAlignment?.horizontal ?? \"left\"]} p-${padding} ${\n titleAlignment.vertical === \"top\"\n ? `top-0 pt-${padding}`\n : titleAlignment.vertical === \"center\"\n ? \"top-1/2 -translate-y-1/2\"\n : `bottom-0 pb-${padding}`\n }`}\n >\n {titleEnabled && titleText && (\n <h2\n className={`font-header leading-tight font-bold text-${titleColor} text-${titleFontSize === \"md\" ? \"base\" : titleFontSize}`}\n >\n {titleText}\n </h2>\n )}\n\n {/* Mobile: Products overlay inside primary media */}\n {hasNestedMedia && (\n <div className={`pt-${padding} @md:hidden`}>\n <div\n className={`flex overflow-x-auto gap-${gapValues[gap]} bg-transparent`}\n >\n {shareables.map((shareable) => (\n <div\n key={shareable.id}\n className={`flex shrink-0 flex-col items-center ${onItemClick ? \"cursor-pointer\" : \"\"}`}\n {...(onItemClick\n ? {\n role: \"button\",\n tabIndex: 0,\n onClick: () => onItemClick(shareable),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onItemClick(shareable);\n }\n },\n }\n : {})}\n >\n <div\n className={`aspect-3/4 h-40 overflow-hidden rounded-${borderRadius}`}\n >\n <MediaRenderer\n {...getMediaPropsFromShareable(shareable)}\n />\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Desktop: Products Container - Single row beside primary media */}\n {hasNestedMedia && (\n <div\n className={`relative hidden min-w-0 self-stretch @md:flex @md:flex-1 px-${padding}`}\n >\n <div\n ref={scrollContainerRef}\n className={`flex h-full flex-row overflow-x-auto gap-${gapValues[gap]}`}\n style={{ scrollSnapType: \"x mandatory\" }}\n >\n {shareables.map((shareable) => (\n <div\n key={shareable.id}\n className={`flex shrink-0 flex-col gap-1 ${onItemClick ? \"cursor-pointer\" : \"\"}`}\n style={{\n width: `calc(${primaryMediaHeight} * 0.75)`,\n scrollSnapAlign: \"start\",\n }}\n {...(onItemClick\n ? {\n role: \"button\",\n tabIndex: 0,\n onClick: () => onItemClick(shareable),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onItemClick(shareable);\n }\n },\n }\n : {})}\n >\n <div\n className={`aspect-3/4 h-full rounded-${borderRadius} overflow-hidden`}\n >\n <MediaRenderer {...getMediaPropsFromShareable(shareable)} />\n </div>\n <span className={`flex-none text-sm text-${nestedTextColor}`}>\n <p className=\"truncate\">{shareable.title || \"\"}</p>\n <p className=\"truncate\">\n {((shareable.display_price as string) ?? shareable.price) ||\n \"\"}\n </p>\n </span>\n </div>\n ))}\n </div>\n\n {/* Navigation arrows */}\n <div\n className={`absolute inset-x-0 top-1/2 flex w-full -translate-y-1/2 items-center justify-between px-8`}\n >\n <ScrollArrows\n onPrevious={() => scrollByAmount(\"prev\")}\n onNext={() => scrollByAmount(\"next\")}\n />\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const nestedWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"NestedWidget\",\n displayName: \"Nested Widget\",\n tabsConfig: [\n { id: \"styling\", label: \"Styling\" },\n { id: \"data\", label: \"Data\" },\n ],\n dataSourceTargetProps: [\"shareables\"],\n fields: [\n // Content tab - Resource group\n {\n key: \"resource\",\n label: \"Primary Media\",\n type: \"resource\",\n description: \"Select the primary media displayed in the large panel\",\n allowedTypes: [\"Medium\"],\n tab: \"styling\",\n group: \"Content\",\n },\n // Content tab - Title group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed over the primary media\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Main title displayed over the primary media\",\n defaultValue: \"Featured Collection\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n label: \"Title Font Size\",\n defaultValue: \"xl\",\n key: \"titleFontSize\",\n description: \"Font size for the widget title\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n defaultValue: \"background\",\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n // Styling tab - Design group\n getHeightField({\n key: \"primaryMediaHeight\",\n label: \"Primary Media Height\",\n description: \"Height of the primary media container\",\n defaultValue: \"400px\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"separator\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"titleAlignment\",\n label: \"Content Alignment\",\n type: \"alignment\",\n description: \"Alignment of the content inside the primary media\",\n defaultValue: { horizontal: \"left\", vertical: \"bottom\" },\n options: {\n horizontalEnabled: true,\n verticalEnabled: true,\n },\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"separator2\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n defaultValue: 4,\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding used throughout the widget\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n defaultValue: \"md\",\n label: \"Border Radius\",\n key: \"borderRadius\",\n description: \"Rounded corners for the widget\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: \"Border Width\",\n description: \"Border width for the widget\",\n defaultValue: \"none\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: \"Border Color\",\n description: \"Border color for the widget\",\n defaultValue: \"muted\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"overlayEnabled\",\n label: \"Enable Overlay\",\n type: \"boolean\",\n description:\n \"Add a dark overlay to the primary media for better text readability\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"overlayType\",\n label: \"Overlay Type\",\n type: \"buttonGroup\",\n description: \"Type of overlay effect\",\n defaultValue: \"gradient\",\n options: [\n { label: \"Solid\", value: \"solid\" },\n { label: \"Gradient\", value: \"gradient\" },\n ],\n tab: \"styling\",\n group: \"Design\",\n requiresKeyToBeTrue: \"overlayEnabled\",\n },\n {\n key: \"overlayIntensity\",\n label: \"Overlay Intensity\",\n type: \"slider\",\n description: \"Opacity of the overlay (0-100)\",\n min: 0,\n max: 100,\n step: 5,\n defaultValue: 50,\n unit: \"%\",\n tab: \"styling\",\n group: \"Design\",\n requiresKeyToBeTrue: \"overlayEnabled\",\n },\n // Styling tab - Nested Media Styling group\n getGapField({\n label: \"Gap\",\n defaultValue: \"md\",\n key: \"gap\",\n description: \"Gap between nested media items\",\n tab: \"styling\",\n group: \"Nested Design\",\n }),\n getColorField({\n defaultValue: \"foreground\",\n key: \"nestedTextColor\",\n label: \"Nested Text Color\",\n description: \"Color for nested media labels\",\n tab: \"styling\",\n group: \"Nested Design\",\n }),\n {\n type: \"background\",\n defaultValue: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background color for nested media container\",\n tab: \"styling\",\n group: \"Nested Design\",\n },\n // Data tab\n {\n key: \"dataSource\",\n label: \"Data Source\",\n type: \"dataSource\",\n description: \"Configure dynamic data fetching from an API\",\n tab: \"data\",\n group: \"Data Configuration\",\n },\n ],\n // Per-item configuration schema for custom data sources\n itemConfigSchema: {\n description: \"Configure settings for this item\",\n fields: [\n {\n key: \"title\",\n label: \"Custom Title\",\n type: \"text\",\n description: \"Override the item's title for this widget\",\n },\n ],\n },\n} as const satisfies WidgetPropertySchema;\n"],"mappings":"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ACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;IAAE,YAAY;IAAQ,UAAU;IAAU;GACxD,SAAS;IACP,mBAAmB;IACnB,iBAAiB;IAClB;GACD,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD,gBAAgB;GACd,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,qBAAqB;GACnB,cAAc;GACd,OAAO;GACP,KAAK;GACL,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aACE;GACF,cAAc;GACd,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;GACd,SAAS,CACP;IAAE,OAAO;IAAS,OAAO;IAAS,EAClC;IAAE,OAAO;IAAY,OAAO;IAAY,CACzC;GACD,KAAK;GACL,OAAO;GACP,qBAAqB;GACtB;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,KAAK;GACL,MAAM;GACN,cAAc;GACd,MAAM;GACN,KAAK;GACL,OAAO;GACP,qBAAqB;GACtB;EAED,YAAY;GACV,OAAO;GACP,cAAc;GACd,KAAK;GACL,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF,cAAc;GACZ,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,MAAM;GACN,cAAc;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,KAAK;GACL,OAAO;GACR;EAED;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,OAAO;GACR;EACF;CAED,kBAAkB;EAChB,aAAa;EACb,QAAQ,CACN;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACd,CACF;EACF;CACF"}
@@ -3,6 +3,6 @@ require("./WidgetInteractionContext-Bd1hEEV2.cjs");
3
3
  require("./registries-CpUM406S.cjs");
4
4
  require("./fields-C8gY9GlT.cjs");
5
5
  require("./MediaRenderer-D4HnIAbN.cjs");
6
- require("./scroll-arrows-DVwMDTt3.cjs");
7
- const require_NestedWidget = require("./NestedWidget-ChAWwsRP.cjs");
6
+ require("./scroll-arrows-i0E2N-ct.cjs");
7
+ const require_NestedWidget = require("./NestedWidget-BjT-UrSi.cjs");
8
8
  exports.nestedWidgetPropertySchema = require_NestedWidget.nestedWidgetPropertySchema;
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-9hOWP6kD.cjs");
2
- const require_portal_tenant_content = require("./portal_tenant_content-0zpnjBot.cjs");
2
+ const require_portal_tenant_content = require("./portal_tenant_content-BvYxmADB.cjs");
3
3
  const require_PortalTenantClientProvider = require("./PortalTenantClientProvider-CVv-4rQ9.cjs");
4
4
  const require_src = require("./src-aItPhUAR.cjs");
5
5
  const require_ScreenHeaderContext = require("./ScreenHeaderContext-BXgWydjB.cjs");
@@ -288,6 +288,25 @@ const useUpdatePlaylistMutation = (options) => {
288
288
  }
289
289
  });
290
290
  };
291
+ const useReorderPlaylistItemsMutation = (options) => {
292
+ const api = useShareablesApi();
293
+ const queryClient = (0, _tanstack_react_query.useQueryClient)();
294
+ return (0, _tanstack_react_query.useMutation)({
295
+ mutationFn: ({ playlistId, data }) => {
296
+ if (!api.playlists.reorderPlaylistItems) return Promise.resolve();
297
+ return api.playlists.reorderPlaylistItems(playlistId, data);
298
+ },
299
+ onSuccess: (_, { playlistId }) => {
300
+ if (api.playlists.reorderPlaylistItems) queryClient.invalidateQueries({ queryKey: shareablesKeys.playlists.detail(playlistId) });
301
+ options?.onSuccess?.(playlistId);
302
+ },
303
+ onError: (error, { playlistId }) => {
304
+ if (isCancellationError(error)) return;
305
+ console.error("Error reordering playlist items:", error);
306
+ options?.onError?.(error, playlistId);
307
+ }
308
+ });
309
+ };
291
310
  //#endregion
292
311
  //#region ../../shareables/ui/src/context.tsx
293
312
  const MISSING_PROVIDER = Symbol("missing-shareables-provider");
@@ -578,6 +597,83 @@ function ShareableListLayout({ filters, children, isLoading, error, errorMessage
578
597
  });
579
598
  }
580
599
  //#endregion
600
+ //#region ../../shareables/ui/src/components/shared/ShareableListRow.tsx
601
+ /**
602
+ * Container className for a list-view rendering of shareables. Pairs with
603
+ * `ShareableListRow` and mirrors `SHAREABLE_GRID_CLASS` for symmetry.
604
+ */
605
+ const SHAREABLE_LIST_CLASS = "divide-border divide-y rounded-lg border";
606
+ /**
607
+ * Single row in a shareables list view. The row body (thumbnail + title) is
608
+ * a `<button>` for keyboard/click navigation; `leading` and `trailing` slots
609
+ * are siblings so their interactive elements don't nest inside the body
610
+ * button. Hover styling applies to the entire row container.
611
+ */
612
+ function ShareableListRow({ imageUrl, imageAlt, title, subtitle, onClick, leading, trailing }) {
613
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
614
+ className: "hover:bg-muted flex items-center gap-3 px-4 py-3 transition-colors",
615
+ children: [
616
+ leading,
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
618
+ type: "button",
619
+ onClick,
620
+ className: "flex min-w-0 flex-1 items-center gap-4 text-left",
621
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
622
+ className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
623
+ children: imageUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
624
+ src: imageUrl,
625
+ alt: imageAlt ?? title,
626
+ className: "h-full w-full object-cover"
627
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-muted h-full w-full" })
628
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
629
+ className: "min-w-0 flex-1",
630
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
631
+ className: "text-foreground truncate text-sm font-medium",
632
+ children: title || "Untitled"
633
+ }), subtitle != null && subtitle !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
634
+ className: "text-muted-foreground flex items-center gap-1.5 text-xs",
635
+ children: subtitle
636
+ })]
637
+ })]
638
+ }),
639
+ trailing
640
+ ]
641
+ });
642
+ }
643
+ //#endregion
644
+ //#region ../../shareables/ui/src/components/shared/ViewModeToggle.tsx
645
+ /**
646
+ * Segmented list/grid toggle used in the filter bar of every shareable
647
+ * listing screen. Stateless — callers own the `viewMode` state.
648
+ */
649
+ function ViewModeToggle({ value, onChange }) {
650
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
651
+ className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
652
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleButton, {
653
+ active: value === "list",
654
+ label: "List view",
655
+ onClick: () => onChange("list"),
656
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { className: "h-4 w-4" })
657
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleButton, {
658
+ active: value === "grid",
659
+ label: "Grid view",
660
+ onClick: () => onChange("grid"),
661
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LayoutGrid, { className: "h-4 w-4" })
662
+ })]
663
+ });
664
+ }
665
+ function ToggleButton({ active, label, onClick, children }) {
666
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
667
+ type: "button",
668
+ onClick,
669
+ title: label,
670
+ "aria-label": label,
671
+ "aria-pressed": active,
672
+ className: require_src.cn("flex h-8 w-8 items-center justify-center rounded-md transition-all", active ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
673
+ children
674
+ });
675
+ }
676
+ //#endregion
581
677
  //#region ../../shareables/ui/src/components/screens/ProductsScreen.tsx
582
678
  const PAGE_SIZE$4 = 24;
583
679
  const SORT_OPTIONS$1 = [
@@ -608,6 +704,7 @@ const SORT_OPTIONS$1 = [
608
704
  ];
609
705
  function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNavigate }) {
610
706
  const client = useShareablesClient();
707
+ const { navigate } = useShareablesUI();
611
708
  require_ScreenHeaderContext.useScreenHeaderBreadcrumbs((0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Breadcrumb, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbList, {
612
709
  className: "text-lg",
613
710
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbItem, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbPage, {
@@ -618,6 +715,7 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
618
715
  const [searchTerm, setSearchTerm] = (0, react.useState)("");
619
716
  const [debouncedSearch, setDebouncedSearch] = (0, react.useState)("");
620
717
  const [sortValue, setSortValue] = (0, react.useState)("created_at_desc");
718
+ const [viewMode, setViewMode] = (0, react.useState)("grid");
621
719
  const observerTarget = (0, react.useRef)(null);
622
720
  (0, react.useEffect)(() => {
623
721
  const timer = setTimeout(() => {
@@ -705,9 +803,9 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
705
803
  errorMessage: "Failed to load products. Please try again.",
706
804
  isEmpty: products.length === 0,
707
805
  emptyMessage: debouncedSearch ? "No products match your search." : "No products available.",
708
- filters: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
709
- className: "flex justify-end",
710
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
806
+ filters: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
807
+ className: "flex items-center justify-end gap-2",
808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
711
809
  className: "w-full max-w-sm",
712
810
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
713
811
  searchValue: searchTerm,
@@ -717,14 +815,18 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
717
815
  sortValue,
718
816
  onSortChange: setSortValue
719
817
  })
720
- })
818
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModeToggle, {
819
+ value: viewMode,
820
+ onChange: setViewMode
821
+ })]
721
822
  }),
823
+ loadingFilterShape: "search-view",
722
824
  footer: isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
723
825
  className: "flex justify-center py-4",
724
826
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
725
827
  }),
726
828
  sentinelRef: observerTarget,
727
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
829
+ children: viewMode === "grid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
728
830
  className: SHAREABLE_GRID_CLASS,
729
831
  children: products.map((product) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareProductCard, {
730
832
  product: {
@@ -734,6 +836,14 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
734
836
  },
735
837
  mediaCount: product.media_count
736
838
  }, product.id))
839
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
840
+ className: SHAREABLE_LIST_CLASS,
841
+ children: products.map((product) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListRow, {
842
+ imageUrl: product.image_url,
843
+ title: product.title,
844
+ subtitle: product.media_count != null ? `${product.media_count} assets` : void 0,
845
+ onClick: () => navigate(`product/${product.id}`)
846
+ }, product.id))
737
847
  })
738
848
  });
739
849
  }
@@ -1703,21 +1813,9 @@ function MediaListingScreen({ onNavigate }) {
1703
1813
  }), kindFilter === opt.value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "text-muted-foreground size-4" })]
1704
1814
  }, opt.value))
1705
1815
  })] }),
1706
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1707
- className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
1708
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1709
- type: "button",
1710
- onClick: () => setViewMode("list"),
1711
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1712
- title: "List view",
1713
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { className: "h-4 w-4" })
1714
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1715
- type: "button",
1716
- onClick: () => setViewMode("grid"),
1717
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1718
- title: "Grid view",
1719
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LayoutGrid, { className: "h-4 w-4" })
1720
- })]
1816
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModeToggle, {
1817
+ value: viewMode,
1818
+ onChange: setViewMode
1721
1819
  })
1722
1820
  ]
1723
1821
  })]
@@ -1787,37 +1885,20 @@ function MediaListingScreen({ onNavigate }) {
1787
1885
  }, item.id);
1788
1886
  })
1789
1887
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1790
- className: "divide-border divide-y rounded-lg border",
1888
+ className: SHAREABLE_LIST_CLASS,
1791
1889
  children: filteredItems.map((item) => {
1792
1890
  const canEdit = !readOnly && item.owner_type === "user";
1793
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1794
- className: "hover:bg-muted flex items-center gap-4 px-4 py-3 transition-colors",
1795
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1796
- type: "button",
1797
- onClick: () => navigate(`media/${item.id}`),
1798
- className: "flex min-w-0 flex-1 items-center gap-4 text-left",
1799
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1800
- className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
1801
- children: item.image_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1802
- src: item.image_url,
1803
- alt: item.title ?? "",
1804
- className: "h-full w-full object-cover"
1805
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-muted h-full w-full" })
1806
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1807
- className: "min-w-0 flex-1",
1808
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1809
- className: "text-foreground truncate text-sm font-medium",
1810
- children: item.title ?? "Untitled"
1811
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1812
- className: "text-muted-foreground flex items-center gap-1.5 text-xs",
1813
- children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1814
- variant: "secondary",
1815
- className: "px-1.5 py-0 text-[10px] leading-4",
1816
- children: "My Media"
1817
- })]
1818
- })]
1819
- })]
1820
- }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })]
1891
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListRow, {
1892
+ imageUrl: item.image_url,
1893
+ imageAlt: item.title ?? "",
1894
+ title: item.title ?? "Untitled",
1895
+ subtitle: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1896
+ variant: "secondary",
1897
+ className: "px-1.5 py-0 text-[10px] leading-4",
1898
+ children: "My Media"
1899
+ })] }),
1900
+ onClick: () => navigate(`media/${item.id}`),
1901
+ trailing: canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })
1821
1902
  }, item.id);
1822
1903
  })
1823
1904
  })
@@ -8936,6 +9017,7 @@ function PlaylistsListingScreen(_props) {
8936
9017
  const [debouncedSearch, setDebouncedSearch] = (0, react.useState)("");
8937
9018
  const [sortValue, setSortValue] = (0, react.useState)("title");
8938
9019
  const [ownerFilter, setOwnerFilter] = (0, react.useState)("all");
9020
+ const [viewMode, setViewMode] = (0, react.useState)("grid");
8939
9021
  const [selectedIds, setSelectedIds] = (0, react.useState)(/* @__PURE__ */ new Set());
8940
9022
  const [pendingDeleteId, setPendingDeleteId] = (0, react.useState)(null);
8941
9023
  (0, react.useEffect)(() => {
@@ -9106,33 +9188,39 @@ function PlaylistsListingScreen(_props) {
9106
9188
  value: ownerFilter,
9107
9189
  onValueChange: setOwnerFilter,
9108
9190
  myLabel: "My Playlists"
9109
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9110
- className: "ml-auto w-full max-w-sm",
9111
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
9112
- searchValue: searchTerm,
9113
- onSearchChange: setSearchTerm,
9114
- placeholder: "Search playlists...",
9115
- sortOptions: [
9116
- {
9117
- label: "Name (A-Z)",
9118
- value: "title"
9119
- },
9120
- {
9121
- label: "Name (Z-A)",
9122
- value: "-title"
9123
- },
9124
- {
9125
- label: "Date Created (Newest)",
9126
- value: "-created_at"
9127
- },
9128
- {
9129
- label: "Date Created (Oldest)",
9130
- value: "created_at"
9131
- }
9132
- ],
9133
- sortValue,
9134
- onSortChange: setSortValue
9135
- })
9191
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9192
+ className: "ml-auto flex items-center gap-2",
9193
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9194
+ className: "w-full max-w-sm",
9195
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
9196
+ searchValue: searchTerm,
9197
+ onSearchChange: setSearchTerm,
9198
+ placeholder: "Search playlists...",
9199
+ sortOptions: [
9200
+ {
9201
+ label: "Name (A-Z)",
9202
+ value: "title"
9203
+ },
9204
+ {
9205
+ label: "Name (Z-A)",
9206
+ value: "-title"
9207
+ },
9208
+ {
9209
+ label: "Date Created (Newest)",
9210
+ value: "-created_at"
9211
+ },
9212
+ {
9213
+ label: "Date Created (Oldest)",
9214
+ value: "created_at"
9215
+ }
9216
+ ],
9217
+ sortValue,
9218
+ onSortChange: setSortValue
9219
+ })
9220
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModeToggle, {
9221
+ value: viewMode,
9222
+ onChange: setViewMode
9223
+ })]
9136
9224
  })]
9137
9225
  });
9138
9226
  const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -9154,8 +9242,8 @@ function PlaylistsListingScreen(_props) {
9154
9242
  }),
9155
9243
  footer,
9156
9244
  sentinelRef: observerTarget,
9157
- loadingFilterShape: "search-action",
9158
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9245
+ loadingFilterShape: "search-view",
9246
+ children: viewMode === "grid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9159
9247
  className: GRID_CLASS,
9160
9248
  children: filteredPlaylists.map((playlist) => {
9161
9249
  const firstItem = playlist.items?.[0];
@@ -9177,6 +9265,61 @@ function PlaylistsListingScreen(_props) {
9177
9265
  onDelete: onDeletePlaylist ? () => setPendingDeleteId(playlist.id) : void 0
9178
9266
  }, playlist.id);
9179
9267
  })
9268
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9269
+ className: SHAREABLE_LIST_CLASS,
9270
+ children: filteredPlaylists.map((playlist) => {
9271
+ const firstItem = playlist.items?.[0];
9272
+ const imageUrl = playlist.image_url ?? firstItem?.image_url ?? firstItem?.relateable?.image_url ?? firstItem?.relateable?.compressed_image_url;
9273
+ const itemCount = playlist.items_count ?? playlist.items?.length ?? 0;
9274
+ const canEdit = !readOnly && playlist.user_id === user?.id;
9275
+ const isSelected = selectedIds.has(playlist.id);
9276
+ const title = playlist.title || "Untitled Playlist";
9277
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListRow, {
9278
+ imageUrl,
9279
+ title,
9280
+ subtitle: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Badge, {
9281
+ variant: "secondary",
9282
+ className: "px-1.5 py-0 text-[10px] leading-4",
9283
+ children: [
9284
+ itemCount,
9285
+ " ",
9286
+ itemCount === 1 ? "item" : "items"
9287
+ ]
9288
+ }),
9289
+ onClick: () => navigate(`playlists/${playlist.id}`),
9290
+ leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Checkbox, {
9291
+ checked: isSelected,
9292
+ onCheckedChange: (checked) => handleToggleSelection(playlist.id, checked === true),
9293
+ "aria-label": `Select ${title}`
9294
+ }),
9295
+ trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [onToggleFavorite && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9296
+ type: "button",
9297
+ onClick: () => handleFavorite(playlist.id),
9298
+ "aria-label": playlist.is_favorited ? "Unfavorite" : "Favorite",
9299
+ className: "hover:bg-muted-foreground/10 flex h-8 w-8 items-center justify-center rounded-md transition-colors",
9300
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Heart, { className: require_src.cn("h-4 w-4 transition-colors", playlist.is_favorited ? "fill-destructive text-destructive" : "text-muted-foreground") })
9301
+ }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuTrigger, {
9302
+ asChild: true,
9303
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Button, {
9304
+ variant: "ghost",
9305
+ size: "sm",
9306
+ className: "h-8 w-8 p-0",
9307
+ "aria-label": "Playlist actions",
9308
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.MoreVertical, { className: "h-4 w-4" })
9309
+ })
9310
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenuContent, {
9311
+ align: "end",
9312
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenuItem, {
9313
+ onClick: () => handleEdit(playlist.id),
9314
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Pencil, { className: "mr-2 h-4 w-4" }), "Edit"]
9315
+ }), onDeletePlaylist && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuSeparator, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenuItem, {
9316
+ variant: "destructive",
9317
+ onClick: () => setPendingDeleteId(playlist.id),
9318
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, { className: "mr-2 h-4 w-4" }), "Delete"]
9319
+ })] })]
9320
+ })] })] })
9321
+ }, playlist.id);
9322
+ })
9180
9323
  })
9181
9324
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialog, {
9182
9325
  open: pendingDeleteId !== null,
@@ -10065,6 +10208,7 @@ function SortableTable({ items, setItems, onDeleteItem, isDeletePending, enableR
10065
10208
  }
10066
10209
  //#endregion
10067
10210
  //#region ../../shareables/ui/src/components/playlists/form/PlaylistItemsSection.tsx
10211
+ const REORDER_DEBOUNCE_MS = 500;
10068
10212
  const PLAYLIST_CONTENT_TYPES = new Set([
10069
10213
  "Medium",
10070
10214
  "Page",
@@ -10134,9 +10278,53 @@ function PlaylistItemsSection({ playlistId }) {
10134
10278
  });
10135
10279
  }
10136
10280
  });
10281
+ const reorderItemsMutation = useReorderPlaylistItemsMutation();
10282
+ const reorderTimerRef = (0, react.useRef)(null);
10283
+ const reorderRollbackRef = (0, react.useRef)(null);
10284
+ (0, react.useEffect)(() => () => {
10285
+ if (reorderTimerRef.current) clearTimeout(reorderTimerRef.current);
10286
+ }, []);
10287
+ const canPersistReorder = !!api.playlists.reorderPlaylistItems;
10137
10288
  const mergeAndOrder = (0, react.useCallback)((reorderedContent) => {
10138
- updateItems(computeOrderedItems([...reorderedContent, ...tailItems]));
10139
- }, [tailItems, updateItems]);
10289
+ const ordered = computeOrderedItems([...reorderedContent, ...tailItems]);
10290
+ if (playlistId && canPersistReorder && reorderTimerRef.current === null && reorderRollbackRef.current === null) reorderRollbackRef.current = tableItems;
10291
+ updateItems(ordered);
10292
+ if (!playlistId || !canPersistReorder) return;
10293
+ if (reorderTimerRef.current) clearTimeout(reorderTimerRef.current);
10294
+ reorderTimerRef.current = setTimeout(() => {
10295
+ reorderTimerRef.current = null;
10296
+ const rollback = reorderRollbackRef.current;
10297
+ reorderItemsMutation.mutate({
10298
+ playlistId,
10299
+ data: { items: ordered.filter((item) => Number.isInteger(item.id) && typeof item.order === "number").map((item) => ({
10300
+ id: item.id,
10301
+ order: item.order
10302
+ })) }
10303
+ }, {
10304
+ onSuccess: () => {
10305
+ if (reorderTimerRef.current === null) reorderRollbackRef.current = null;
10306
+ else reorderRollbackRef.current = ordered;
10307
+ },
10308
+ onError: (error) => {
10309
+ if (rollback) updateItems(rollback);
10310
+ reorderRollbackRef.current = null;
10311
+ showToast({
10312
+ title: "Failed to save new item order",
10313
+ type: "error",
10314
+ error
10315
+ });
10316
+ }
10317
+ });
10318
+ }, REORDER_DEBOUNCE_MS);
10319
+ }, [
10320
+ tailItems,
10321
+ tableItems,
10322
+ updateItems,
10323
+ playlistId,
10324
+ canPersistReorder,
10325
+ reorderItemsMutation.mutate,
10326
+ showToast
10327
+ ]);
10140
10328
  const handleDeleteItem = (itemId) => {
10141
10329
  removeItem(itemId);
10142
10330
  if (playlistId) removeItemMutation.mutate({
@@ -11490,6 +11678,9 @@ function createPlaylistsAdapter(client) {
11490
11678
  items: [],
11491
11679
  items_count: 0
11492
11680
  };
11681
+ },
11682
+ reorderPlaylistItems: async (playlistId, data) => {
11683
+ await require_portal_tenant_content.playlists_items_reorder(client, playlistId, { items: data.items });
11493
11684
  }
11494
11685
  };
11495
11686
  }
@@ -11905,4 +12096,4 @@ Object.defineProperty(exports, "usePortalContentContext", {
11905
12096
  }
11906
12097
  });
11907
12098
 
11908
- //# sourceMappingURL=PortalContentApiProvider-RXBp8FNj.cjs.map
12099
+ //# sourceMappingURL=PortalContentApiProvider-DXnplIOD.cjs.map