@fluid-app/portal-sdk 0.1.323 → 0.1.325
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/dist/{AddressAutocompleteInput-DkFs_leA.cjs → AddressAutocompleteInput-Dld_dEfL.cjs} +2 -2
- package/dist/{AddressAutocompleteInput-DkFs_leA.cjs.map → AddressAutocompleteInput-Dld_dEfL.cjs.map} +1 -1
- package/dist/{AddressAutocompleteInput-BpbBARKk.mjs → AddressAutocompleteInput-aFbv45NN.mjs} +2 -2
- package/dist/{AddressAutocompleteInput-BpbBARKk.mjs.map → AddressAutocompleteInput-aFbv45NN.mjs.map} +1 -1
- package/dist/{AlertWidget-BTo7kgdG.mjs → AlertWidget-Dl4ybl2d.mjs} +2 -2
- package/dist/{AlertWidget-BTo7kgdG.mjs.map → AlertWidget-Dl4ybl2d.mjs.map} +1 -1
- package/dist/{AlertWidget-hK0MjMAm.cjs → AlertWidget-bAjN8KYn.cjs} +2 -2
- package/dist/{AlertWidget-hK0MjMAm.cjs.map → AlertWidget-bAjN8KYn.cjs.map} +1 -1
- package/dist/{BulletListWidget-B-n012vU.mjs → BulletListWidget-DqJDKiHh.mjs} +2 -2
- package/dist/{BulletListWidget-B-n012vU.mjs.map → BulletListWidget-DqJDKiHh.mjs.map} +1 -1
- package/dist/{BulletListWidget-msyZSQST.cjs → BulletListWidget-DuKkxn5H.cjs} +2 -2
- package/dist/{BulletListWidget-msyZSQST.cjs.map → BulletListWidget-DuKkxn5H.cjs.map} +1 -1
- package/dist/{CalendarWidget-BntrqUnu.mjs → CalendarWidget-B2siaYgQ.mjs} +2 -2
- package/dist/{CalendarWidget-BntrqUnu.mjs.map → CalendarWidget-B2siaYgQ.mjs.map} +1 -1
- package/dist/{CalendarWidget-BMAo4qi6.cjs → CalendarWidget-mUe0zSHR.cjs} +2 -2
- package/dist/{CalendarWidget-BMAo4qi6.cjs.map → CalendarWidget-mUe0zSHR.cjs.map} +1 -1
- package/dist/{CardWidget-S0QoRtuV.mjs → CardWidget-D-Nmqh-H.mjs} +2 -2
- package/dist/{CardWidget-S0QoRtuV.mjs.map → CardWidget-D-Nmqh-H.mjs.map} +1 -1
- package/dist/{CardWidget-DER6ZQ5t.cjs → CardWidget-DxkBzyhN.cjs} +2 -2
- package/dist/{CardWidget-H9myJUzx.cjs → CardWidget-uyxSuloC.cjs} +2 -2
- package/dist/{CardWidget-H9myJUzx.cjs.map → CardWidget-uyxSuloC.cjs.map} +1 -1
- package/dist/{CarouselWidget-2fu0NYxd.mjs → CarouselWidget-CCusUzW6.mjs} +2 -2
- package/dist/{CarouselWidget-2fu0NYxd.mjs.map → CarouselWidget-CCusUzW6.mjs.map} +1 -1
- package/dist/{CarouselWidget-Acr_f77b.cjs → CarouselWidget-CuuDgPCD.cjs} +2 -2
- package/dist/{CarouselWidget-Acr_f77b.cjs.map → CarouselWidget-CuuDgPCD.cjs.map} +1 -1
- package/dist/{CatchUpWidget-CAUqALXy.cjs → CatchUpWidget-CIWYi2de.cjs} +2 -2
- package/dist/{CatchUpWidget-CAUqALXy.cjs.map → CatchUpWidget-CIWYi2de.cjs.map} +1 -1
- package/dist/{CatchUpWidget-DxeBgueZ.mjs → CatchUpWidget-CPcS9iE6.mjs} +2 -2
- package/dist/{CatchUpWidget-DxeBgueZ.mjs.map → CatchUpWidget-CPcS9iE6.mjs.map} +1 -1
- package/dist/{ChartWidget-DycALBF6.cjs → ChartWidget-BKRbgs5D.cjs} +2 -2
- package/dist/{ChartWidget-DfQQU2w2.mjs → ChartWidget-Cn5-G7gy.mjs} +2 -2
- package/dist/{ChartWidget-DfQQU2w2.mjs.map → ChartWidget-Cn5-G7gy.mjs.map} +1 -1
- package/dist/{ChartWidget-BWTEeSPh.cjs → ChartWidget-UR7CSbmw.cjs} +2 -2
- package/dist/{ChartWidget-BWTEeSPh.cjs.map → ChartWidget-UR7CSbmw.cjs.map} +1 -1
- package/dist/{ContactsScreen-DiGrOlwx.cjs → ContactsScreen-BYrRJ55W.cjs} +2 -2
- package/dist/{ContactsScreen-FELUSAZ_.mjs → ContactsScreen-D3_LQj6x.mjs} +2 -2
- package/dist/{ContactsScreen-FELUSAZ_.mjs.map → ContactsScreen-D3_LQj6x.mjs.map} +1 -1
- package/dist/{ContactsScreen-Cf0ShCew.cjs → ContactsScreen-Dm8b4SyY.cjs} +2 -2
- package/dist/{ContactsScreen-Cf0ShCew.cjs.map → ContactsScreen-Dm8b4SyY.cjs.map} +1 -1
- package/dist/{ContainerWidget-CPacdtRy.mjs → ContainerWidget-BV9kEtM-.mjs} +3 -3
- package/dist/{ContainerWidget-CPacdtRy.mjs.map → ContainerWidget-BV9kEtM-.mjs.map} +1 -1
- package/dist/{ContainerWidget-FKpteSdR.cjs → ContainerWidget-D2r6Y_7q.cjs} +3 -3
- package/dist/{ContainerWidget-FKpteSdR.cjs.map → ContainerWidget-D2r6Y_7q.cjs.map} +1 -1
- package/dist/{ContainerWidget-DB_c7f-D.cjs → ContainerWidget-DgAqYsVI.cjs} +3 -3
- package/dist/{EmbedWidget-C3ZJCcHD.cjs → EmbedWidget-Cml-sPlc.cjs} +2 -2
- package/dist/{EmbedWidget-C3ZJCcHD.cjs.map → EmbedWidget-Cml-sPlc.cjs.map} +1 -1
- package/dist/{EmbedWidget-Be16VTGq.mjs → EmbedWidget-D2BvU6NX.mjs} +2 -2
- package/dist/{EmbedWidget-Be16VTGq.mjs.map → EmbedWidget-D2BvU6NX.mjs.map} +1 -1
- package/dist/{FluidProvider-BIeO1i4r.cjs → FluidProvider-CjYA1xCO.cjs} +52 -52
- package/dist/{FluidProvider-BIeO1i4r.cjs.map → FluidProvider-CjYA1xCO.cjs.map} +1 -1
- package/dist/{FluidProvider-LqejfvZ-.mjs → FluidProvider-yiuYMcmc.mjs} +52 -52
- package/dist/{FluidProvider-LqejfvZ-.mjs.map → FluidProvider-yiuYMcmc.mjs.map} +1 -1
- package/dist/{ImageWidget-BUCn7lmY.cjs → ImageWidget-BSt2Dou6.cjs} +2 -2
- package/dist/{ImageWidget-BUCn7lmY.cjs.map → ImageWidget-BSt2Dou6.cjs.map} +1 -1
- package/dist/{ImageWidget-BOFNCjT1.mjs → ImageWidget-XOHYmG99.mjs} +2 -2
- package/dist/{ImageWidget-BOFNCjT1.mjs.map → ImageWidget-XOHYmG99.mjs.map} +1 -1
- package/dist/{LayoutWidget-MkXO0u_P.mjs → LayoutWidget-BOqRqcGA.mjs} +2 -2
- package/dist/{LayoutWidget-MkXO0u_P.mjs.map → LayoutWidget-BOqRqcGA.mjs.map} +1 -1
- package/dist/{LayoutWidget-BPzEtpHX.cjs → LayoutWidget-CWI-s1HP.cjs} +2 -2
- package/dist/{LayoutWidget-BPzEtpHX.cjs.map → LayoutWidget-CWI-s1HP.cjs.map} +1 -1
- package/dist/{LayoutWidget-DLylR1cc.cjs → LayoutWidget-e2OcJE1c.cjs} +2 -2
- package/dist/{LinkWidget-B_I6ziKN.cjs → LinkWidget-B5Q0XIk0.cjs} +2 -2
- package/dist/{LinkWidget-CWZb9_j6.mjs → LinkWidget-DW90JZN_.mjs} +2 -2
- package/dist/{LinkWidget-CWZb9_j6.mjs.map → LinkWidget-DW90JZN_.mjs.map} +1 -1
- package/dist/{LinkWidget-C9yxFoZ4.cjs → LinkWidget-DaJulYpk.cjs} +2 -2
- package/dist/{LinkWidget-C9yxFoZ4.cjs.map → LinkWidget-DaJulYpk.cjs.map} +1 -1
- package/dist/{ListWidget-KMeln6EX.cjs → ListWidget-6URaaauG.cjs} +2 -2
- package/dist/{ListWidget-BSfGkOQW.cjs → ListWidget-BvaT2ZGa.cjs} +2 -2
- package/dist/{ListWidget-BSfGkOQW.cjs.map → ListWidget-BvaT2ZGa.cjs.map} +1 -1
- package/dist/{ListWidget-DL5nFaDa.mjs → ListWidget-Cb6ZVDHZ.mjs} +2 -2
- package/dist/{ListWidget-DL5nFaDa.mjs.map → ListWidget-Cb6ZVDHZ.mjs.map} +1 -1
- package/dist/MessagingScreen-86kMIKB0.cjs +52 -0
- package/dist/{MessagingScreen-BRcAo9ux.mjs → MessagingScreen-BxawMPRR.mjs} +2 -2
- package/dist/{MessagingScreen-BRcAo9ux.mjs.map → MessagingScreen-BxawMPRR.mjs.map} +1 -1
- package/dist/MessagingScreen-Dxhyiwfo.mjs +50 -0
- package/dist/{MessagingScreen-BBkqLkcx.cjs → MessagingScreen-aaJrCtsv.cjs} +2 -2
- package/dist/{MessagingScreen-BBkqLkcx.cjs.map → MessagingScreen-aaJrCtsv.cjs.map} +1 -1
- package/dist/{MySiteWidget-f0fTOyGV.mjs → MySiteWidget-BDysvovd.mjs} +2 -2
- package/dist/{MySiteWidget-f0fTOyGV.mjs.map → MySiteWidget-BDysvovd.mjs.map} +1 -1
- package/dist/{MySiteWidget-NxJ0L8yI.cjs → MySiteWidget-CXbBZfm4.cjs} +2 -2
- package/dist/{MySiteWidget-NxJ0L8yI.cjs.map → MySiteWidget-CXbBZfm4.cjs.map} +1 -1
- package/dist/{NestedWidget-CAiA4odQ.cjs → NestedWidget-4_u-kg10.cjs} +2 -2
- package/dist/{NestedWidget-DIBPcnGJ.cjs → NestedWidget-C0A3Yh-L.cjs} +2 -2
- package/dist/{NestedWidget-DIBPcnGJ.cjs.map → NestedWidget-C0A3Yh-L.cjs.map} +1 -1
- package/dist/{NestedWidget-twmPxyim.mjs → NestedWidget-C8Kf_ohj.mjs} +2 -2
- package/dist/{NestedWidget-twmPxyim.mjs.map → NestedWidget-C8Kf_ohj.mjs.map} +1 -1
- package/dist/{OrdersScreen-Do7YtBiu.mjs → OrdersScreen-CGN6PQ3t.mjs} +2 -2
- package/dist/{OrdersScreen-Do7YtBiu.mjs.map → OrdersScreen-CGN6PQ3t.mjs.map} +1 -1
- package/dist/{OrdersScreen-CZIlrF_M.cjs → OrdersScreen-DRSEBmUE.cjs} +2 -2
- package/dist/{OrdersScreen-CZIlrF_M.cjs.map → OrdersScreen-DRSEBmUE.cjs.map} +1 -1
- package/dist/OrdersScreen-DcJ4uMWl.cjs +50 -0
- package/dist/OrdersScreen-DurayzCe.mjs +48 -0
- package/dist/{PointsWidget-BkwbNnJo.cjs → PointsWidget-BTteH_l8.cjs} +2 -2
- package/dist/{PointsWidget-BkwbNnJo.cjs.map → PointsWidget-BTteH_l8.cjs.map} +1 -1
- package/dist/{PointsWidget-BibKktPg.mjs → PointsWidget-BylUmQaD.mjs} +2 -2
- package/dist/{PointsWidget-BibKktPg.mjs.map → PointsWidget-BylUmQaD.mjs.map} +1 -1
- package/dist/ProfileScreen-B0ayDRoJ.mjs +51 -0
- package/dist/{ProfileScreen-C3zOvEhX.cjs → ProfileScreen-BIxMho6c.cjs} +3 -3
- package/dist/{ProfileScreen-C3zOvEhX.cjs.map → ProfileScreen-BIxMho6c.cjs.map} +1 -1
- package/dist/{ProfileScreen-Chk8YkMW.mjs → ProfileScreen-BjeloZs1.mjs} +3 -3
- package/dist/{ProfileScreen-Chk8YkMW.mjs.map → ProfileScreen-BjeloZs1.mjs.map} +1 -1
- package/dist/ProfileScreen-CVMVU5S4.cjs +53 -0
- package/dist/{QuickLinksWidget-BiDVfB80.cjs → QuickLinksWidget-Bd-6ll7w.cjs} +2 -2
- package/dist/{QuickLinksWidget-BiDVfB80.cjs.map → QuickLinksWidget-Bd-6ll7w.cjs.map} +1 -1
- package/dist/{QuickLinksWidget-CrQ_jAuJ.mjs → QuickLinksWidget-CxMjqbp0.mjs} +2 -2
- package/dist/{QuickLinksWidget-CrQ_jAuJ.mjs.map → QuickLinksWidget-CxMjqbp0.mjs.map} +1 -1
- package/dist/{QuickShareWidget-Eb-zkAD6.cjs → QuickShareWidget-Bb1pmkgW.cjs} +2 -2
- package/dist/{QuickShareWidget-Eb-zkAD6.cjs.map → QuickShareWidget-Bb1pmkgW.cjs.map} +1 -1
- package/dist/{QuickShareWidget-CgYm0dtU.mjs → QuickShareWidget-CTCRjdrB.mjs} +2 -2
- package/dist/{QuickShareWidget-CgYm0dtU.mjs.map → QuickShareWidget-CTCRjdrB.mjs.map} +1 -1
- package/dist/{RecentActivityWidget-D15aOkXL.mjs → RecentActivityWidget-CoAL8wCS.mjs} +2 -2
- package/dist/{RecentActivityWidget-D15aOkXL.mjs.map → RecentActivityWidget-CoAL8wCS.mjs.map} +1 -1
- package/dist/{RecentActivityWidget-Cy9957Sq.cjs → RecentActivityWidget-DEQHXSNu.cjs} +2 -2
- package/dist/{RecentActivityWidget-Cy9957Sq.cjs.map → RecentActivityWidget-DEQHXSNu.cjs.map} +1 -1
- package/dist/{SeparatorWidget-LQ97EZd5.cjs → SeparatorWidget-DFL3n-Yu.cjs} +2 -2
- package/dist/{SeparatorWidget-LQ97EZd5.cjs.map → SeparatorWidget-DFL3n-Yu.cjs.map} +1 -1
- package/dist/{SeparatorWidget-DugZ8ukt.mjs → SeparatorWidget-DVJZu5gP.mjs} +2 -2
- package/dist/{SeparatorWidget-DugZ8ukt.mjs.map → SeparatorWidget-DVJZu5gP.mjs.map} +1 -1
- package/dist/{ShopScreen-CjrEStJc.cjs → ShopScreen-GZP87KNb.cjs} +3 -3
- package/dist/{ShopScreen-CjrEStJc.cjs.map → ShopScreen-GZP87KNb.cjs.map} +1 -1
- package/dist/ShopScreen-UGwfdQWs.cjs +50 -0
- package/dist/{ShopScreen-uOMi-jFD.mjs → ShopScreen-cFF4LS3Z.mjs} +3 -3
- package/dist/{ShopScreen-uOMi-jFD.mjs.map → ShopScreen-cFF4LS3Z.mjs.map} +1 -1
- package/dist/ShopScreen-eYhRe-Mf.mjs +48 -0
- package/dist/{ShopWidget-B56mIngl.cjs → ShopWidget-B0y-LGzS.cjs} +2 -2
- package/dist/{ShopWidget-BRx2rblg.cjs → ShopWidget-CGs4oaaC.cjs} +2 -2
- package/dist/{ShopWidget-BRx2rblg.cjs.map → ShopWidget-CGs4oaaC.cjs.map} +1 -1
- package/dist/{ShopWidget-BKTYcwx_.mjs → ShopWidget-CfasYQGp.mjs} +2 -2
- package/dist/{ShopWidget-BKTYcwx_.mjs.map → ShopWidget-CfasYQGp.mjs.map} +1 -1
- package/dist/{SubscriptionsScreen-BIVZPjlJ.mjs → SubscriptionsScreen-0KLzhq6r.mjs} +3 -3
- package/dist/{SubscriptionsScreen-BIVZPjlJ.mjs.map → SubscriptionsScreen-0KLzhq6r.mjs.map} +1 -1
- package/dist/SubscriptionsScreen-5cM3czQu.mjs +50 -0
- package/dist/SubscriptionsScreen-CgiytyiE.cjs +52 -0
- package/dist/{SubscriptionsScreen-CLJO_AE7.cjs → SubscriptionsScreen-p70EpLPu.cjs} +3 -3
- package/dist/{SubscriptionsScreen-CLJO_AE7.cjs.map → SubscriptionsScreen-p70EpLPu.cjs.map} +1 -1
- package/dist/{TableWidget-Cef29Su9.mjs → TableWidget-CeaAeGSw.mjs} +2 -2
- package/dist/{TableWidget-Cef29Su9.mjs.map → TableWidget-CeaAeGSw.mjs.map} +1 -1
- package/dist/{TableWidget-DJWvrS1q.cjs → TableWidget-CpJ1diVs.cjs} +2 -2
- package/dist/{TableWidget-DfKZhS1Y.cjs → TableWidget-SgL9bBT9.cjs} +2 -2
- package/dist/{TableWidget-DfKZhS1Y.cjs.map → TableWidget-SgL9bBT9.cjs.map} +1 -1
- package/dist/{TextWidget-Di-tx9xg.mjs → TextWidget-APrOBo5U.mjs} +2 -2
- package/dist/{TextWidget-Di-tx9xg.mjs.map → TextWidget-APrOBo5U.mjs.map} +1 -1
- package/dist/{TextWidget--r6oQ94b.cjs → TextWidget-S8qNxr06.cjs} +2 -2
- package/dist/{TextWidget--r6oQ94b.cjs.map → TextWidget-S8qNxr06.cjs.map} +1 -1
- package/dist/{ToDoWidget-CImAWF3O.cjs → ToDoWidget-6Yj9hQQ5.cjs} +4 -4
- package/dist/ToDoWidget-6Yj9hQQ5.cjs.map +1 -0
- package/dist/{ToDoWidget-C9qX7raE.cjs → ToDoWidget-Bc3uv4ft.cjs} +3 -3
- package/dist/{ToDoWidget-sLWwJJ_g.mjs → ToDoWidget-CHsQI_Qk.mjs} +4 -4
- package/dist/ToDoWidget-CHsQI_Qk.mjs.map +1 -0
- package/dist/{VideoWidget-Ddt85ZZg.cjs → VideoWidget-0G91idne.cjs} +2 -2
- package/dist/{VideoWidget-Ddt85ZZg.cjs.map → VideoWidget-0G91idne.cjs.map} +1 -1
- package/dist/{VideoWidget-CjOhc8KW.mjs → VideoWidget-Cnx19JYQ.mjs} +2 -2
- package/dist/{VideoWidget-CjOhc8KW.mjs.map → VideoWidget-Cnx19JYQ.mjs.map} +1 -1
- package/dist/index.cjs +47 -47
- package/dist/index.d.cts +0 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +47 -47
- package/dist/{registries-ClaAtpiG.cjs → registries-BxNpO5DV.cjs} +1 -10
- package/dist/registries-BxNpO5DV.cjs.map +1 -0
- package/dist/{registries-BtCYIu-b.mjs → registries-C-t6-veK.mjs} +1 -10
- package/dist/registries-C-t6-veK.mjs.map +1 -0
- package/dist/{task-composer-form-ChwUNk1I.mjs → task-composer-form-CHyQZUCo.mjs} +3 -2
- package/dist/{task-composer-form-ChwUNk1I.mjs.map → task-composer-form-CHyQZUCo.mjs.map} +1 -1
- package/dist/{task-composer-form-DnRjQmkv.cjs → task-composer-form-D9KhWUI1.cjs} +3 -2
- package/dist/{task-composer-form-DnRjQmkv.cjs.map → task-composer-form-D9KhWUI1.cjs.map} +1 -1
- package/package.json +10 -10
- package/dist/MessagingScreen-CQNNI4RN.cjs +0 -52
- package/dist/MessagingScreen-y3AG96Gi.mjs +0 -50
- package/dist/OrdersScreen-BgcQi5Re.cjs +0 -50
- package/dist/OrdersScreen-DISwVljS.mjs +0 -48
- package/dist/ProfileScreen-CxOJ1D3H.cjs +0 -53
- package/dist/ProfileScreen-DZFMc4E9.mjs +0 -51
- package/dist/ShopScreen-C4vXayCp.mjs +0 -48
- package/dist/ShopScreen-DM9jxl3w.cjs +0 -50
- package/dist/SubscriptionsScreen-BLligJrd.mjs +0 -50
- package/dist/SubscriptionsScreen-fUWYlZkB.cjs +0 -52
- package/dist/ToDoWidget-CImAWF3O.cjs.map +0 -1
- package/dist/ToDoWidget-sLWwJJ_g.mjs.map +0 -1
- package/dist/registries-BtCYIu-b.mjs.map +0 -1
- package/dist/registries-ClaAtpiG.cjs.map +0 -1
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import "./PortalTenantClientProvider-CqW6ae2M.mjs";
|
|
2
|
-
import "./FluidProvider-LqejfvZ-.mjs";
|
|
3
|
-
import "./ScreenRenderer-BszshjZg.mjs";
|
|
4
|
-
import "./store-api-context-DViwxyG4.mjs";
|
|
5
|
-
import "./mysite-api-context-kUTM3GNG.mjs";
|
|
6
|
-
import "./countries-api-context-DScC_39w.mjs";
|
|
7
|
-
import "./task-composer-form-ChwUNk1I.mjs";
|
|
8
|
-
import "./registry-context-BKvTiuXB.mjs";
|
|
9
|
-
import "./WidgetInteractionContext-BWH7njU7.mjs";
|
|
10
|
-
import "./EmbedWidget-Be16VTGq.mjs";
|
|
11
|
-
import "./static-dict-adapter-Z_42w_55.mjs";
|
|
12
|
-
import "./error-state-Djgt-RGW.mjs";
|
|
13
|
-
import "./translation-api-context-factory-Cq-pFTiQ.mjs";
|
|
14
|
-
import "./LayoutWidget-MkXO0u_P.mjs";
|
|
15
|
-
import "./registries-BtCYIu-b.mjs";
|
|
16
|
-
import "./fields-B2DTFWQl.mjs";
|
|
17
|
-
import "./TextWidget-Di-tx9xg.mjs";
|
|
18
|
-
import "./AlertWidget-BTo7kgdG.mjs";
|
|
19
|
-
import "./BulletListWidget-B-n012vU.mjs";
|
|
20
|
-
import "./preview-context-CDnhakni.mjs";
|
|
21
|
-
import "./CalendarWidget-BntrqUnu.mjs";
|
|
22
|
-
import "./CardWidget-S0QoRtuV.mjs";
|
|
23
|
-
import "./purify.es-Cjc3iy5J.mjs";
|
|
24
|
-
import "./MediaRenderer-CLmBOrvK.mjs";
|
|
25
|
-
import "./CarouselWidget-2fu0NYxd.mjs";
|
|
26
|
-
import "./CatchUpWidget-DxeBgueZ.mjs";
|
|
27
|
-
import "./src-DC5QoBhR.mjs";
|
|
28
|
-
import { n as subscriptionsScreenPropertySchema, t as SubscriptionsScreen } from "./SubscriptionsScreen-BIVZPjlJ.mjs";
|
|
29
|
-
import "./ChartWidget-DfQQU2w2.mjs";
|
|
30
|
-
import "./ContainerWidget-CPacdtRy.mjs";
|
|
31
|
-
import "./ImageWidget-BOFNCjT1.mjs";
|
|
32
|
-
import "./LinkWidget-CWZb9_j6.mjs";
|
|
33
|
-
import "./ListWidget-DL5nFaDa.mjs";
|
|
34
|
-
import "./MySiteWidget-f0fTOyGV.mjs";
|
|
35
|
-
import "./NestedWidget-twmPxyim.mjs";
|
|
36
|
-
import "./PointsWidget-BibKktPg.mjs";
|
|
37
|
-
import "./QuickLinksWidget-CrQ_jAuJ.mjs";
|
|
38
|
-
import "./QuickShareWidget-CgYm0dtU.mjs";
|
|
39
|
-
import "./RecentActivityWidget-D15aOkXL.mjs";
|
|
40
|
-
import "./SeparatorWidget-DugZ8ukt.mjs";
|
|
41
|
-
import "./SpacerWidget-CCpCGUjM.mjs";
|
|
42
|
-
import "./TableWidget-Cef29Su9.mjs";
|
|
43
|
-
import "./ToDoWidget-sLWwJJ_g.mjs";
|
|
44
|
-
import "./VideoWidget-CjOhc8KW.mjs";
|
|
45
|
-
import "./SearchSort-BeYMXXIi.mjs";
|
|
46
|
-
import "./ShopWidget-BKTYcwx_.mjs";
|
|
47
|
-
import "./ScreenHeaderContext-Dn12BZyj.mjs";
|
|
48
|
-
import "./AddressAutocompleteInput-BpbBARKk.mjs";
|
|
49
|
-
import "./Combobox-B-rNFLTr.mjs";
|
|
50
|
-
export { SubscriptionsScreen, subscriptionsScreenPropertySchema };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
require("./chunk-9hOWP6kD.cjs");
|
|
2
|
-
require("./FluidProvider-BIeO1i4r.cjs");
|
|
3
|
-
require("./ScreenRenderer-D_eEc6S4.cjs");
|
|
4
|
-
require("./PortalTenantClientProvider-D-QE-YzB.cjs");
|
|
5
|
-
require("./store-api-context-D1gZn22Z.cjs");
|
|
6
|
-
require("./mysite-api-context-CilZcDS4.cjs");
|
|
7
|
-
require("./countries-api-context-G-NW4BoH.cjs");
|
|
8
|
-
require("./task-composer-form-DnRjQmkv.cjs");
|
|
9
|
-
require("./registry-context-DJ5xiVnt.cjs");
|
|
10
|
-
require("./WidgetInteractionContext-BhkusA95.cjs");
|
|
11
|
-
require("./EmbedWidget-C3ZJCcHD.cjs");
|
|
12
|
-
require("./static-dict-adapter-BH5PbEBP.cjs");
|
|
13
|
-
require("./error-state-BI3jxWlb.cjs");
|
|
14
|
-
require("./translation-api-context-factory-6jtyJm6d.cjs");
|
|
15
|
-
require("./LayoutWidget-BPzEtpHX.cjs");
|
|
16
|
-
require("./registries-ClaAtpiG.cjs");
|
|
17
|
-
require("./fields-D4qgIZ7Y.cjs");
|
|
18
|
-
require("./TextWidget--r6oQ94b.cjs");
|
|
19
|
-
require("./AlertWidget-hK0MjMAm.cjs");
|
|
20
|
-
require("./BulletListWidget-msyZSQST.cjs");
|
|
21
|
-
require("./preview-context-B4-iZOzd.cjs");
|
|
22
|
-
require("./CalendarWidget-BMAo4qi6.cjs");
|
|
23
|
-
require("./CardWidget-H9myJUzx.cjs");
|
|
24
|
-
require("./purify.es-BQcWWtgQ.cjs");
|
|
25
|
-
require("./MediaRenderer-DYcPG_Yl.cjs");
|
|
26
|
-
require("./CarouselWidget-Acr_f77b.cjs");
|
|
27
|
-
require("./CatchUpWidget-CAUqALXy.cjs");
|
|
28
|
-
require("./src-DoVMGOoV.cjs");
|
|
29
|
-
require("./ChartWidget-BWTEeSPh.cjs");
|
|
30
|
-
require("./ContainerWidget-FKpteSdR.cjs");
|
|
31
|
-
require("./ImageWidget-BUCn7lmY.cjs");
|
|
32
|
-
require("./LinkWidget-C9yxFoZ4.cjs");
|
|
33
|
-
require("./ListWidget-BSfGkOQW.cjs");
|
|
34
|
-
require("./MySiteWidget-NxJ0L8yI.cjs");
|
|
35
|
-
require("./NestedWidget-DIBPcnGJ.cjs");
|
|
36
|
-
require("./PointsWidget-BkwbNnJo.cjs");
|
|
37
|
-
require("./QuickLinksWidget-BiDVfB80.cjs");
|
|
38
|
-
require("./QuickShareWidget-Eb-zkAD6.cjs");
|
|
39
|
-
require("./RecentActivityWidget-Cy9957Sq.cjs");
|
|
40
|
-
require("./SeparatorWidget-LQ97EZd5.cjs");
|
|
41
|
-
require("./SpacerWidget-CYHjTF38.cjs");
|
|
42
|
-
require("./TableWidget-DfKZhS1Y.cjs");
|
|
43
|
-
require("./ToDoWidget-CImAWF3O.cjs");
|
|
44
|
-
require("./VideoWidget-Ddt85ZZg.cjs");
|
|
45
|
-
require("./SearchSort-Bi9QWJQQ.cjs");
|
|
46
|
-
require("./ShopWidget-BRx2rblg.cjs");
|
|
47
|
-
require("./ScreenHeaderContext-DWDN0-mb.cjs");
|
|
48
|
-
require("./AddressAutocompleteInput-DkFs_leA.cjs");
|
|
49
|
-
require("./Combobox-D9lqcqeg.cjs");
|
|
50
|
-
const require_SubscriptionsScreen = require("./SubscriptionsScreen-CLJO_AE7.cjs");
|
|
51
|
-
exports.SubscriptionsScreen = require_SubscriptionsScreen.SubscriptionsScreen;
|
|
52
|
-
exports.subscriptionsScreenPropertySchema = require_SubscriptionsScreen.subscriptionsScreenPropertySchema;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToDoWidget-CImAWF3O.cjs","names":["useWidgetsApi","useWidgetPreviewContext","useDataSourceRegistryConfig","useWidgetsApi","useWidgetPreviewContext","useDataSourceRegistryConfig","useWidgetPreviewContext","useDataSourceRegistryConfig","Dialog","DialogContent","DialogHeader","DialogTitle","PortalContainerProvider","TaskComposerForm","useContactsTranslation","useInfiniteContacts","Popover","PopoverTrigger","cn","ChevronsUpDown","PopoverContent","Search","Input","Check","useWidgetPreviewContext","borderWidthClasses","borderColorClasses","ListChecks","ErrorState","Plus","parseTaskBody","getFontSizeField","getColorField","getPaddingField","getBorderRadiusField","getBorderWidthField","getBorderColorField"],"sources":["../../widgets/src/hooks/use-todos.preview.ts","../../widgets/src/hooks/use-todos.ts","../../widgets/src/hooks/use-update-todo.ts","../../widgets/src/widgets/CreateTodoDialog.tsx","../../widgets/src/widgets/ToDoWidget.tsx"],"sourcesContent":["import type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nconst now = new Date();\n\nfunction daysFromNow(days: number): string {\n const d = new Date(now);\n d.setDate(d.getDate() + days);\n return d.toISOString();\n}\n\nexport const PREVIEW_DATA: Todo[] = [\n {\n id: 1,\n body: \"Send follow-up email to new leads\",\n dueAt: daysFromNow(1),\n completedAt: null,\n createdAt: daysFromNow(-2),\n contactId: 101,\n contactName: \"Sarah Johnson\",\n },\n {\n id: 2,\n body: \"Prepare slides for team training\",\n dueAt: daysFromNow(3),\n completedAt: null,\n createdAt: daysFromNow(-1),\n contactId: null,\n contactName: null,\n },\n {\n id: 3,\n body: \"Review monthly sales report\",\n dueAt: daysFromNow(-1),\n completedAt: null,\n createdAt: daysFromNow(-5),\n contactId: 102,\n contactName: \"Mike Chen\",\n },\n];\n","import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { PREVIEW_DATA } from \"./use-todos.preview\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nexport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\n/**\n * Shared cache key for the todos list. Both useTodos (read) and\n * useUpdateTodo (write) use this — drift would silently break optimistic\n * updates.\n */\nexport function todosQueryKey(args: {\n baseUrl: string | undefined;\n isPreview: boolean;\n}) {\n return [\n \"portal-widget-use\",\n \"todos\",\n args.isPreview ? \"preview\" : args.baseUrl,\n ] as const;\n}\n\nexport function useTodos(): UseQueryResult<Todo[], Error> {\n const widgetsApi = useWidgetsApi();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n return useQuery({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),\n enabled: !isPreview,\n ...(isPreview && { placeholderData: PREVIEW_DATA }),\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\nimport { todosQueryKey } from \"./use-todos\";\n\ntype UpdateVariables = { id: number; completed: boolean };\n\nexport function useUpdateTodo() {\n const widgetsApi = useWidgetsApi();\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n const queryKey = todosQueryKey({ baseUrl, isPreview });\n\n return useMutation({\n mutationFn: ({ id, completed }: UpdateVariables) => {\n // Defense in depth: ToDoWidget already disables the checkbox in\n // preview mode, but if any other caller fires this mutation while\n // previewing we'd PATCH demo data on the real backend. Fail closed.\n if (isPreview) {\n return Promise.reject(\n new Error(\"Todo updates are disabled in preview mode\"),\n );\n }\n return widgetsApi.updateTodo(id, completed);\n },\n onMutate: async ({ id, completed }) => {\n await queryClient.cancelQueries({ queryKey });\n const previous = queryClient.getQueryData<Todo[]>(queryKey);\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === id\n ? {\n ...todo,\n completedAt: completed ? new Date().toISOString() : null,\n }\n : todo,\n ),\n );\n return { previous };\n },\n onSuccess: (updated, { completed }) => {\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === updated.id ? updated : todo,\n ),\n );\n // Match the documented test plan: completing toasts, un-completing\n // is silent. The widget today only ever fires completed=true, but\n // gating here keeps the contract honest for any future caller that\n // toggles either direction.\n if (completed) {\n fluidToast({ title: \"Todo completed\", type: \"success\" });\n }\n },\n onError: (_error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData(queryKey, context.previous);\n }\n fluidToast({ title: \"Failed to update todo\", type: \"error\" });\n },\n // Belt-and-suspenders refetch to reconcile any race we couldn't see\n // (e.g., a different tab mutated the same todo). Cheap — one GET per\n // toggle.\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Check, ChevronsUpDown, Search } from \"lucide-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n cn,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n PortalContainerProvider,\n} from \"@fluid-app/ui-primitives\";\nimport { useInfiniteContacts } from \"@fluid-app/contacts-core/hooks/use-infinite-contacts\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport type { Contact } from \"@fluid-app/contacts-core/types\";\nimport { TaskComposerForm } from \"@fluid-app/contacts-ui/portal/components/tasks/task-composer-form\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { todosQueryKey } from \"../hooks/use-todos\";\n\nconst SEARCH_DEBOUNCE_MS = 200;\n\nexport interface CreateTodoDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function CreateTodoDialog({\n open,\n onOpenChange,\n}: CreateTodoDialogProps): React.JSX.Element {\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n const [selectedContact, setSelectedContact] = useState<Contact | null>(null);\n // Anchor the popover's portal inside the dialog. Dialog applies\n // pointer-events: none to the body while open, which kills clicks and\n // scroll on a body-portaled popover in WebKit. See edit-bill-date-dialog\n // for the canonical pattern.\n const [popoverContainer, setPopoverContainer] =\n useState<HTMLDivElement | null>(null);\n\n // Reset state when the dialog closes so the next open starts fresh.\n useEffect(() => {\n if (!open) setSelectedContact(null);\n }, [open]);\n\n const handleDone = () => {\n queryClient.invalidateQueries({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n });\n onOpenChange(false);\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add a to-do</DialogTitle>\n </DialogHeader>\n\n <PortalContainerProvider container={popoverContainer}>\n <div className=\"mt-2 flex flex-col gap-3\">\n <ContactPicker\n value={selectedContact}\n onSelect={setSelectedContact}\n />\n\n {selectedContact && (\n <TaskComposerForm\n key={selectedContact.id}\n contactId={String(selectedContact.id)}\n onDone={handleDone}\n />\n )}\n </div>\n </PortalContainerProvider>\n {/* Portal target for the popover. Must live INSIDE DialogContent so\n it ends up in the dialog's body-level portal — otherwise it\n renders inline next to the widget, and any transformed ancestor\n (builder canvas zoom, etc.) would break `position: fixed`.\n Intentionally NOT aria-hidden — the popover content portaled\n into this container has its own listbox/option semantics that\n screen readers need to reach. `pointer-events-none` keeps the\n empty overlay from capturing clicks; the popover content sets\n `pointer-events-auto` so it remains interactive. */}\n <div\n ref={setPopoverContainer}\n className=\"pointer-events-none fixed inset-0 z-[9998]\"\n />\n </DialogContent>\n </Dialog>\n );\n}\n\ninterface ContactPickerProps {\n value: Contact | null;\n onSelect: (contact: Contact) => void;\n}\n\nfunction ContactPicker({\n value,\n onSelect,\n}: ContactPickerProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [open, setOpen] = useState(false);\n const [searchInput, setSearchInput] = useState(\"\");\n const [debouncedSearch, setDebouncedSearch] = useState(\"\");\n const sentinelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handle = window.setTimeout(() => {\n setDebouncedSearch(searchInput.trim());\n }, SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const queryParams = useMemo(\n () => ({\n search_query: debouncedSearch || undefined,\n sort_by: \"full_name\",\n sort_direction: \"asc\",\n per_page: 25,\n }),\n [debouncedSearch],\n );\n\n const {\n data,\n isLoading,\n isError,\n hasNextPage,\n isFetchingNextPage,\n fetchNextPage,\n } = useInfiniteContacts(queryParams);\n\n const contacts: Contact[] = useMemo(\n () => data?.pages.flatMap((page) => page.contacts ?? []) ?? [],\n [data],\n );\n\n // Auto-load the next page when the sentinel scrolls into view, so reps\n // with many contacts can reach all of them without typing a precise\n // search query.\n useEffect(() => {\n const sentinel = sentinelRef.current;\n if (!sentinel || !hasNextPage) return;\n\n const observer = new IntersectionObserver((entries) => {\n const entry = entries[0];\n if (entry?.isIntersecting && !isFetchingNextPage) {\n fetchNextPage();\n }\n });\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n return (\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-muted-foreground text-xs font-medium\">\n {t(\"contact_label\")}\n </label>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"border-input bg-background hover:bg-muted/40 flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors\"\n >\n <span\n className={cn(\n \"truncate\",\n !value && \"text-muted-foreground font-normal\",\n )}\n >\n {value ? value.full_name : t(\"empty_select_contact\")}\n </span>\n <ChevronsUpDown\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"bg-popover text-popover-foreground pointer-events-auto z-[9999] w-(--radix-popover-trigger-width) overflow-hidden rounded-md border p-0 shadow-md\"\n >\n <div className=\"border-border flex items-center gap-2 border-b px-3 py-2\">\n <Search\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n <Input\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={t(\"search_placeholder\")}\n aria-label={t(\"search_placeholder\")}\n className=\"h-7 border-0 px-0 shadow-none focus-visible:ring-0\"\n />\n </div>\n <div className=\"max-h-64 overflow-y-auto py-1\" role=\"listbox\">\n {isLoading ? (\n <div className=\"text-muted-foreground px-3 py-6 text-center text-xs\">\n {t(\"loading\")}\n </div>\n ) : isError ? (\n <div className=\"text-destructive px-3 py-6 text-center text-xs\">\n {t(\"error_loading_list\")}\n </div>\n ) : contacts.length === 0 ? (\n <div className=\"text-muted-foreground px-3 py-6 text-center text-xs\">\n {debouncedSearch\n ? t(\"no_contacts_search\", { term: debouncedSearch })\n : t(\"no_contacts_yet\")}\n </div>\n ) : (\n <>\n {contacts.map((contact) => {\n const isSelected = value?.id === contact.id;\n return (\n <button\n key={contact.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n onClick={() => {\n onSelect(contact);\n setOpen(false);\n }}\n className=\"hover:bg-muted/50 flex w-full items-center justify-between gap-2 px-3 py-1.5 text-left text-sm transition-colors\"\n >\n <span className=\"truncate\">{contact.full_name}</span>\n {isSelected && (\n <Check\n className=\"text-primary size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n })}\n {hasNextPage && (\n <div ref={sentinelRef} aria-hidden=\"true\" className=\"h-4\" />\n )}\n {isFetchingNextPage && (\n <div className=\"text-muted-foreground px-3 py-2 text-center text-xs\">\n {t(\"loading_more\")}\n </div>\n )}\n </>\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n","import { useState, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport type {\n BackgroundValue,\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type { WidgetPropertySchema } from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getPaddingField,\n} from \"../core/fields\";\nimport { ListChecks, Plus } from \"lucide-react\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { parseTaskBody } from \"@fluid-app/contacts-core/parse-task-body\";\nimport { useTodos } from \"../hooks/use-todos\";\nimport { useUpdateTodo } from \"../hooks/use-update-todo\";\nimport { ErrorState } from \"../components/error-state\";\nimport { CreateTodoDialog } from \"./CreateTodoDialog\";\n\ntype ToDoWidgetProps = ComponentProps<\"div\"> & {\n // Title\n titleEnabled?: boolean;\n titleText?: string;\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n\n // Styling\n background?: BackgroundValue;\n textColor?: ColorOptions;\n accentColor?: ColorOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n\n // Content\n maxItems?: number;\n};\n\nexport function ToDoWidget({\n // Title defaults\n titleEnabled = true,\n titleText = \"To-Do\",\n titleFontSize = \"lg\",\n titleColor = \"foreground\",\n\n // Styling defaults\n background = {\n type: \"solid\",\n color: \"background\",\n },\n textColor = \"foreground\",\n accentColor = \"primary\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n\n // Content defaults\n maxItems = 5,\n\n className,\n ...props\n}: ToDoWidgetProps): React.JSX.Element {\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 const { data: todos = [], isLoading, isError } = useTodos();\n const updateTodo = useUpdateTodo();\n const { isPreview } = useWidgetPreviewContext();\n const [pendingIds, setPendingIds] = useState<Set<number>>(new Set());\n const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);\n\n const toggleTodo = (id: number, completed: boolean) => {\n if (isPreview || pendingIds.has(id)) return;\n setPendingIds((prev) => new Set(prev).add(id));\n updateTodo.mutate(\n { id, completed },\n {\n onSettled: () =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n }),\n },\n );\n };\n\n const activeTodos = todos.filter((todo) => !todo.completedAt);\n const todosToShow = activeTodos.slice(0, maxItems);\n const remainingCount = activeTodos.length - todosToShow.length;\n const isEmpty = activeTodos.length === 0;\n\n // Group todos by contactId so same-named contacts stay separate.\n const groupedTodos = todosToShow.reduce<\n {\n contactId: number | null;\n contactName: string | null;\n todos: typeof todosToShow;\n }[]\n >((groups, todo) => {\n const existing = groups.find((g) => g.contactId === todo.contactId);\n if (existing) {\n existing.todos.push(todo);\n } else {\n groups.push({\n contactId: todo.contactId,\n contactName: todo.contactName,\n todos: [todo],\n });\n }\n return groups;\n }, []);\n\n return (\n <div\n className={`overflow-hidden rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? \"\"}`}\n style={{ backgroundImage }}\n {...props}\n >\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n {titleEnabled && titleText && (\n <h2\n className={`text-${titleFontSize} font-header font-bold text-${titleColor}`}\n >\n {titleText}\n </h2>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {!isEmpty && !isLoading && (\n <span className={`text-2xl font-bold text-${accentColor}`}>\n {activeTodos.length}\n </span>\n )}\n <div\n className={`flex h-10 w-10 shrink-0 items-center justify-center`}\n >\n <ListChecks className={`h-5 w-5 text-${accentColor}-foreground`} />\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {isLoading ? (\n <div className=\"flex min-h-[120px] items-center justify-center\">\n <div className=\"h-6 w-6 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n </div>\n ) : isError ? (\n /* Error state */\n <ErrorState />\n ) : isEmpty ? (\n /* Empty state */\n <div className=\"flex flex-col items-center justify-center gap-3 py-8\">\n <p className={`text-center text-${textColor}/60`}>\n You've got nothing else To-Do!\n </p>\n {!isPreview && (\n <button\n type=\"button\"\n onClick={() => setIsCreateDialogOpen(true)}\n className={`inline-flex items-center gap-1.5 rounded-full bg-${accentColor} text-${accentColor}-foreground hover:bg-${accentColor}/90 px-4 py-1.5 text-xs font-semibold transition-colors`}\n >\n <Plus className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Add to-do\n </button>\n )}\n </div>\n ) : (\n /* Todo List */\n <>\n <div className=\"flex flex-col gap-3\">\n {groupedTodos.map((group) => (\n <div\n key={group.contactId ?? \"__unassigned__\"}\n className=\"flex flex-col\"\n >\n {group.contactName && (\n <div\n className={`mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`}\n >\n {group.contactName}\n </div>\n )}\n {group.todos.map((todo, index) => (\n <div\n key={todo.id}\n className={`flex items-center gap-3 py-2 ${\n index !== group.todos.length - 1\n ? `border-b border-${textColor}/10`\n : \"\"\n }`}\n >\n <input\n type=\"checkbox\"\n className={`h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`}\n checked={!!todo.completedAt}\n disabled={isPreview || pendingIds.has(todo.id)}\n onChange={(event) =>\n toggleTodo(todo.id, event.target.checked)\n }\n />\n <span className=\"line-clamp-1 flex-1 text-sm\">\n {parseTaskBody(todo.body).title}\n </span>\n </div>\n ))}\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"mt-2 flex items-center justify-between\">\n {remainingCount > 0 && (\n <span className={`text-sm text-${textColor}/50 underline`}>\n {remainingCount} more task{remainingCount > 1 ? \"s\" : \"\"}\n </span>\n )}\n <div className=\"ml-auto\">\n <button\n type=\"button\"\n onClick={() => setIsCreateDialogOpen(true)}\n disabled={isPreview}\n aria-label=\"Add to-do\"\n className={`flex h-8 w-8 items-center justify-center rounded-full transition-colors not-disabled:cursor-pointer hover:bg-${textColor}/10 disabled:opacity-60`}\n >\n <Plus className={`h-5 w-5 text-${textColor}/50`} />\n </button>\n </div>\n </div>\n </>\n )}\n\n {!isPreview && (\n <CreateTodoDialog\n open={isCreateDialogOpen}\n onOpenChange={setIsCreateDialogOpen}\n />\n )}\n </div>\n );\n}\n\nexport const toDoWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"ToDoWidget\",\n displayName: \"To-Do Widget\",\n tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n fields: [\n // Styling Tab - Title Group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed above the todo list\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Title text displayed above the todo list\",\n defaultValue: \"To-Do\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n key: \"titleFontSize\",\n label: \"Title Font Size\",\n description: \"Font size for the widget title\",\n defaultValue: \"xl\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n\n // Styling Tab - Design Group\n {\n type: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background for the widget container\",\n defaultValue: \"background\",\n tab: \"styling\",\n group: \"Design\",\n },\n getColorField({\n key: \"textColor\",\n label: \"Text Color\",\n description: \"Default text color for todo items\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getColorField({\n key: \"accentColor\",\n label: \"Accent Color\",\n description: \"Color used for count badge and icon\",\n defaultValue: \"primary\",\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: \"maxItems\",\n label: \"Max Items\",\n type: \"number\",\n description: \"Maximum number of todo items to display\",\n min: 1,\n max: 20,\n step: 1,\n defaultValue: 5,\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding around the widget container\",\n defaultValue: 4,\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: \"Border Radius\",\n description: \"Border radius for the widget container\",\n defaultValue: \"md\",\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} as const satisfies WidgetPropertySchema;\n"],"mappings":"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AAA,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACFA,mBAAAA,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,KAAK;GACL,MAAM;GACN,cAAc;GACd,KAAK;GACL,OAAO;GACR;EACDC,mBAAAA,gBAAgB;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACFC,mBAAAA,qBAAqB;GACnB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACFC,mBAAAA,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACFC,mBAAAA,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACH;CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToDoWidget-sLWwJJ_g.mjs","names":[],"sources":["../../widgets/src/hooks/use-todos.preview.ts","../../widgets/src/hooks/use-todos.ts","../../widgets/src/hooks/use-update-todo.ts","../../widgets/src/widgets/CreateTodoDialog.tsx","../../widgets/src/widgets/ToDoWidget.tsx"],"sourcesContent":["import type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nconst now = new Date();\n\nfunction daysFromNow(days: number): string {\n const d = new Date(now);\n d.setDate(d.getDate() + days);\n return d.toISOString();\n}\n\nexport const PREVIEW_DATA: Todo[] = [\n {\n id: 1,\n body: \"Send follow-up email to new leads\",\n dueAt: daysFromNow(1),\n completedAt: null,\n createdAt: daysFromNow(-2),\n contactId: 101,\n contactName: \"Sarah Johnson\",\n },\n {\n id: 2,\n body: \"Prepare slides for team training\",\n dueAt: daysFromNow(3),\n completedAt: null,\n createdAt: daysFromNow(-1),\n contactId: null,\n contactName: null,\n },\n {\n id: 3,\n body: \"Review monthly sales report\",\n dueAt: daysFromNow(-1),\n completedAt: null,\n createdAt: daysFromNow(-5),\n contactId: 102,\n contactName: \"Mike Chen\",\n },\n];\n","import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { PREVIEW_DATA } from \"./use-todos.preview\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nexport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\n/**\n * Shared cache key for the todos list. Both useTodos (read) and\n * useUpdateTodo (write) use this — drift would silently break optimistic\n * updates.\n */\nexport function todosQueryKey(args: {\n baseUrl: string | undefined;\n isPreview: boolean;\n}) {\n return [\n \"portal-widget-use\",\n \"todos\",\n args.isPreview ? \"preview\" : args.baseUrl,\n ] as const;\n}\n\nexport function useTodos(): UseQueryResult<Todo[], Error> {\n const widgetsApi = useWidgetsApi();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n return useQuery({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),\n enabled: !isPreview,\n ...(isPreview && { placeholderData: PREVIEW_DATA }),\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\nimport { todosQueryKey } from \"./use-todos\";\n\ntype UpdateVariables = { id: number; completed: boolean };\n\nexport function useUpdateTodo() {\n const widgetsApi = useWidgetsApi();\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n const queryKey = todosQueryKey({ baseUrl, isPreview });\n\n return useMutation({\n mutationFn: ({ id, completed }: UpdateVariables) => {\n // Defense in depth: ToDoWidget already disables the checkbox in\n // preview mode, but if any other caller fires this mutation while\n // previewing we'd PATCH demo data on the real backend. Fail closed.\n if (isPreview) {\n return Promise.reject(\n new Error(\"Todo updates are disabled in preview mode\"),\n );\n }\n return widgetsApi.updateTodo(id, completed);\n },\n onMutate: async ({ id, completed }) => {\n await queryClient.cancelQueries({ queryKey });\n const previous = queryClient.getQueryData<Todo[]>(queryKey);\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === id\n ? {\n ...todo,\n completedAt: completed ? new Date().toISOString() : null,\n }\n : todo,\n ),\n );\n return { previous };\n },\n onSuccess: (updated, { completed }) => {\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === updated.id ? updated : todo,\n ),\n );\n // Match the documented test plan: completing toasts, un-completing\n // is silent. The widget today only ever fires completed=true, but\n // gating here keeps the contract honest for any future caller that\n // toggles either direction.\n if (completed) {\n fluidToast({ title: \"Todo completed\", type: \"success\" });\n }\n },\n onError: (_error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData(queryKey, context.previous);\n }\n fluidToast({ title: \"Failed to update todo\", type: \"error\" });\n },\n // Belt-and-suspenders refetch to reconcile any race we couldn't see\n // (e.g., a different tab mutated the same todo). Cheap — one GET per\n // toggle.\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Check, ChevronsUpDown, Search } from \"lucide-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n cn,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n PortalContainerProvider,\n} from \"@fluid-app/ui-primitives\";\nimport { useInfiniteContacts } from \"@fluid-app/contacts-core/hooks/use-infinite-contacts\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport type { Contact } from \"@fluid-app/contacts-core/types\";\nimport { TaskComposerForm } from \"@fluid-app/contacts-ui/portal/components/tasks/task-composer-form\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { todosQueryKey } from \"../hooks/use-todos\";\n\nconst SEARCH_DEBOUNCE_MS = 200;\n\nexport interface CreateTodoDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function CreateTodoDialog({\n open,\n onOpenChange,\n}: CreateTodoDialogProps): React.JSX.Element {\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n const [selectedContact, setSelectedContact] = useState<Contact | null>(null);\n // Anchor the popover's portal inside the dialog. Dialog applies\n // pointer-events: none to the body while open, which kills clicks and\n // scroll on a body-portaled popover in WebKit. See edit-bill-date-dialog\n // for the canonical pattern.\n const [popoverContainer, setPopoverContainer] =\n useState<HTMLDivElement | null>(null);\n\n // Reset state when the dialog closes so the next open starts fresh.\n useEffect(() => {\n if (!open) setSelectedContact(null);\n }, [open]);\n\n const handleDone = () => {\n queryClient.invalidateQueries({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n });\n onOpenChange(false);\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add a to-do</DialogTitle>\n </DialogHeader>\n\n <PortalContainerProvider container={popoverContainer}>\n <div className=\"mt-2 flex flex-col gap-3\">\n <ContactPicker\n value={selectedContact}\n onSelect={setSelectedContact}\n />\n\n {selectedContact && (\n <TaskComposerForm\n key={selectedContact.id}\n contactId={String(selectedContact.id)}\n onDone={handleDone}\n />\n )}\n </div>\n </PortalContainerProvider>\n {/* Portal target for the popover. Must live INSIDE DialogContent so\n it ends up in the dialog's body-level portal — otherwise it\n renders inline next to the widget, and any transformed ancestor\n (builder canvas zoom, etc.) would break `position: fixed`.\n Intentionally NOT aria-hidden — the popover content portaled\n into this container has its own listbox/option semantics that\n screen readers need to reach. `pointer-events-none` keeps the\n empty overlay from capturing clicks; the popover content sets\n `pointer-events-auto` so it remains interactive. */}\n <div\n ref={setPopoverContainer}\n className=\"pointer-events-none fixed inset-0 z-[9998]\"\n />\n </DialogContent>\n </Dialog>\n );\n}\n\ninterface ContactPickerProps {\n value: Contact | null;\n onSelect: (contact: Contact) => void;\n}\n\nfunction ContactPicker({\n value,\n onSelect,\n}: ContactPickerProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [open, setOpen] = useState(false);\n const [searchInput, setSearchInput] = useState(\"\");\n const [debouncedSearch, setDebouncedSearch] = useState(\"\");\n const sentinelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handle = window.setTimeout(() => {\n setDebouncedSearch(searchInput.trim());\n }, SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const queryParams = useMemo(\n () => ({\n search_query: debouncedSearch || undefined,\n sort_by: \"full_name\",\n sort_direction: \"asc\",\n per_page: 25,\n }),\n [debouncedSearch],\n );\n\n const {\n data,\n isLoading,\n isError,\n hasNextPage,\n isFetchingNextPage,\n fetchNextPage,\n } = useInfiniteContacts(queryParams);\n\n const contacts: Contact[] = useMemo(\n () => data?.pages.flatMap((page) => page.contacts ?? []) ?? [],\n [data],\n );\n\n // Auto-load the next page when the sentinel scrolls into view, so reps\n // with many contacts can reach all of them without typing a precise\n // search query.\n useEffect(() => {\n const sentinel = sentinelRef.current;\n if (!sentinel || !hasNextPage) return;\n\n const observer = new IntersectionObserver((entries) => {\n const entry = entries[0];\n if (entry?.isIntersecting && !isFetchingNextPage) {\n fetchNextPage();\n }\n });\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n return (\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-muted-foreground text-xs font-medium\">\n {t(\"contact_label\")}\n </label>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"border-input bg-background hover:bg-muted/40 flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors\"\n >\n <span\n className={cn(\n \"truncate\",\n !value && \"text-muted-foreground font-normal\",\n )}\n >\n {value ? value.full_name : t(\"empty_select_contact\")}\n </span>\n <ChevronsUpDown\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"bg-popover text-popover-foreground pointer-events-auto z-[9999] w-(--radix-popover-trigger-width) overflow-hidden rounded-md border p-0 shadow-md\"\n >\n <div className=\"border-border flex items-center gap-2 border-b px-3 py-2\">\n <Search\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n <Input\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={t(\"search_placeholder\")}\n aria-label={t(\"search_placeholder\")}\n className=\"h-7 border-0 px-0 shadow-none focus-visible:ring-0\"\n />\n </div>\n <div className=\"max-h-64 overflow-y-auto py-1\" role=\"listbox\">\n {isLoading ? (\n <div className=\"text-muted-foreground px-3 py-6 text-center text-xs\">\n {t(\"loading\")}\n </div>\n ) : isError ? (\n <div className=\"text-destructive px-3 py-6 text-center text-xs\">\n {t(\"error_loading_list\")}\n </div>\n ) : contacts.length === 0 ? (\n <div className=\"text-muted-foreground px-3 py-6 text-center text-xs\">\n {debouncedSearch\n ? t(\"no_contacts_search\", { term: debouncedSearch })\n : t(\"no_contacts_yet\")}\n </div>\n ) : (\n <>\n {contacts.map((contact) => {\n const isSelected = value?.id === contact.id;\n return (\n <button\n key={contact.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n onClick={() => {\n onSelect(contact);\n setOpen(false);\n }}\n className=\"hover:bg-muted/50 flex w-full items-center justify-between gap-2 px-3 py-1.5 text-left text-sm transition-colors\"\n >\n <span className=\"truncate\">{contact.full_name}</span>\n {isSelected && (\n <Check\n className=\"text-primary size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n })}\n {hasNextPage && (\n <div ref={sentinelRef} aria-hidden=\"true\" className=\"h-4\" />\n )}\n {isFetchingNextPage && (\n <div className=\"text-muted-foreground px-3 py-2 text-center text-xs\">\n {t(\"loading_more\")}\n </div>\n )}\n </>\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n","import { useState, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport type {\n BackgroundValue,\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type { WidgetPropertySchema } from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getPaddingField,\n} from \"../core/fields\";\nimport { ListChecks, Plus } from \"lucide-react\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { parseTaskBody } from \"@fluid-app/contacts-core/parse-task-body\";\nimport { useTodos } from \"../hooks/use-todos\";\nimport { useUpdateTodo } from \"../hooks/use-update-todo\";\nimport { ErrorState } from \"../components/error-state\";\nimport { CreateTodoDialog } from \"./CreateTodoDialog\";\n\ntype ToDoWidgetProps = ComponentProps<\"div\"> & {\n // Title\n titleEnabled?: boolean;\n titleText?: string;\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n\n // Styling\n background?: BackgroundValue;\n textColor?: ColorOptions;\n accentColor?: ColorOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n\n // Content\n maxItems?: number;\n};\n\nexport function ToDoWidget({\n // Title defaults\n titleEnabled = true,\n titleText = \"To-Do\",\n titleFontSize = \"lg\",\n titleColor = \"foreground\",\n\n // Styling defaults\n background = {\n type: \"solid\",\n color: \"background\",\n },\n textColor = \"foreground\",\n accentColor = \"primary\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n\n // Content defaults\n maxItems = 5,\n\n className,\n ...props\n}: ToDoWidgetProps): React.JSX.Element {\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 const { data: todos = [], isLoading, isError } = useTodos();\n const updateTodo = useUpdateTodo();\n const { isPreview } = useWidgetPreviewContext();\n const [pendingIds, setPendingIds] = useState<Set<number>>(new Set());\n const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);\n\n const toggleTodo = (id: number, completed: boolean) => {\n if (isPreview || pendingIds.has(id)) return;\n setPendingIds((prev) => new Set(prev).add(id));\n updateTodo.mutate(\n { id, completed },\n {\n onSettled: () =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n }),\n },\n );\n };\n\n const activeTodos = todos.filter((todo) => !todo.completedAt);\n const todosToShow = activeTodos.slice(0, maxItems);\n const remainingCount = activeTodos.length - todosToShow.length;\n const isEmpty = activeTodos.length === 0;\n\n // Group todos by contactId so same-named contacts stay separate.\n const groupedTodos = todosToShow.reduce<\n {\n contactId: number | null;\n contactName: string | null;\n todos: typeof todosToShow;\n }[]\n >((groups, todo) => {\n const existing = groups.find((g) => g.contactId === todo.contactId);\n if (existing) {\n existing.todos.push(todo);\n } else {\n groups.push({\n contactId: todo.contactId,\n contactName: todo.contactName,\n todos: [todo],\n });\n }\n return groups;\n }, []);\n\n return (\n <div\n className={`overflow-hidden rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? \"\"}`}\n style={{ backgroundImage }}\n {...props}\n >\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n {titleEnabled && titleText && (\n <h2\n className={`text-${titleFontSize} font-header font-bold text-${titleColor}`}\n >\n {titleText}\n </h2>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {!isEmpty && !isLoading && (\n <span className={`text-2xl font-bold text-${accentColor}`}>\n {activeTodos.length}\n </span>\n )}\n <div\n className={`flex h-10 w-10 shrink-0 items-center justify-center`}\n >\n <ListChecks className={`h-5 w-5 text-${accentColor}-foreground`} />\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {isLoading ? (\n <div className=\"flex min-h-[120px] items-center justify-center\">\n <div className=\"h-6 w-6 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n </div>\n ) : isError ? (\n /* Error state */\n <ErrorState />\n ) : isEmpty ? (\n /* Empty state */\n <div className=\"flex flex-col items-center justify-center gap-3 py-8\">\n <p className={`text-center text-${textColor}/60`}>\n You've got nothing else To-Do!\n </p>\n {!isPreview && (\n <button\n type=\"button\"\n onClick={() => setIsCreateDialogOpen(true)}\n className={`inline-flex items-center gap-1.5 rounded-full bg-${accentColor} text-${accentColor}-foreground hover:bg-${accentColor}/90 px-4 py-1.5 text-xs font-semibold transition-colors`}\n >\n <Plus className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Add to-do\n </button>\n )}\n </div>\n ) : (\n /* Todo List */\n <>\n <div className=\"flex flex-col gap-3\">\n {groupedTodos.map((group) => (\n <div\n key={group.contactId ?? \"__unassigned__\"}\n className=\"flex flex-col\"\n >\n {group.contactName && (\n <div\n className={`mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`}\n >\n {group.contactName}\n </div>\n )}\n {group.todos.map((todo, index) => (\n <div\n key={todo.id}\n className={`flex items-center gap-3 py-2 ${\n index !== group.todos.length - 1\n ? `border-b border-${textColor}/10`\n : \"\"\n }`}\n >\n <input\n type=\"checkbox\"\n className={`h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`}\n checked={!!todo.completedAt}\n disabled={isPreview || pendingIds.has(todo.id)}\n onChange={(event) =>\n toggleTodo(todo.id, event.target.checked)\n }\n />\n <span className=\"line-clamp-1 flex-1 text-sm\">\n {parseTaskBody(todo.body).title}\n </span>\n </div>\n ))}\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"mt-2 flex items-center justify-between\">\n {remainingCount > 0 && (\n <span className={`text-sm text-${textColor}/50 underline`}>\n {remainingCount} more task{remainingCount > 1 ? \"s\" : \"\"}\n </span>\n )}\n <div className=\"ml-auto\">\n <button\n type=\"button\"\n onClick={() => setIsCreateDialogOpen(true)}\n disabled={isPreview}\n aria-label=\"Add to-do\"\n className={`flex h-8 w-8 items-center justify-center rounded-full transition-colors not-disabled:cursor-pointer hover:bg-${textColor}/10 disabled:opacity-60`}\n >\n <Plus className={`h-5 w-5 text-${textColor}/50`} />\n </button>\n </div>\n </div>\n </>\n )}\n\n {!isPreview && (\n <CreateTodoDialog\n open={isCreateDialogOpen}\n onOpenChange={setIsCreateDialogOpen}\n />\n )}\n </div>\n );\n}\n\nexport const toDoWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"ToDoWidget\",\n displayName: \"To-Do Widget\",\n tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n fields: [\n // Styling Tab - Title Group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed above the todo list\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Title text displayed above the todo list\",\n defaultValue: \"To-Do\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n key: \"titleFontSize\",\n label: \"Title Font Size\",\n description: \"Font size for the widget title\",\n defaultValue: \"xl\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n\n // Styling Tab - Design Group\n {\n type: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background for the widget container\",\n defaultValue: \"background\",\n tab: \"styling\",\n group: \"Design\",\n },\n getColorField({\n key: \"textColor\",\n label: \"Text Color\",\n description: \"Default text color for todo items\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getColorField({\n key: \"accentColor\",\n label: \"Accent Color\",\n description: \"Color used for count badge and icon\",\n defaultValue: \"primary\",\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: \"maxItems\",\n label: \"Max Items\",\n type: \"number\",\n description: \"Maximum number of todo items to display\",\n min: 1,\n max: 20,\n step: 1,\n defaultValue: 5,\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding around the widget container\",\n defaultValue: 4,\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: \"Border Radius\",\n description: \"Border radius for the widget container\",\n defaultValue: \"md\",\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} as const satisfies WidgetPropertySchema;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registries-BtCYIu-b.mjs","names":[],"sources":["../../core/src/registries/property-schema-types.ts","../../core/src/registries/field-helpers.ts","../../core/src/registries/droplet-widget-chrome.ts"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport type {\n WidgetType,\n WidgetSchema,\n AlignOptions,\n ColorOptions,\n FontSizeOptions,\n SectionLayoutType,\n StrictOmit,\n BorderRadiusOptions,\n} from \"../types\";\n\n/**\n * Tab configuration for organizing properties\n */\nexport interface TabConfig {\n /** Unique identifier for the tab */\n id: string;\n /** Display label for the tab */\n label: string;\n}\n\n// ============================================================================\n// Property Field Types - Derive from constant for single source of truth\n// ============================================================================\n\n/**\n * Property field type constant - single source of truth for field types.\n * Use PROPERTY_FIELD_TYPES.text instead of \"text\" for type-safe comparisons.\n */\nexport const PROPERTY_FIELD_TYPES = {\n text: \"text\",\n textarea: \"textarea\",\n number: \"number\",\n boolean: \"boolean\",\n select: \"select\",\n color: \"color\",\n range: \"range\",\n dataSource: \"dataSource\",\n resource: \"resource\",\n image: \"image\",\n alignment: \"alignment\",\n slider: \"slider\",\n colorPicker: \"colorPicker\",\n sectionHeader: \"sectionHeader\",\n separator: \"separator\",\n buttonGroup: \"buttonGroup\",\n colorSelect: \"colorSelect\",\n sectionLayoutSelect: \"sectionLayoutSelect\",\n background: \"background\",\n contentPosition: \"contentPosition\",\n textSizeSelect: \"textSizeSelect\",\n cssUnit: \"cssUnit\",\n fontPicker: \"fontPicker\",\n stringArray: \"stringArray\",\n borderRadius: \"borderRadius\",\n screenPicker: \"screenPicker\",\n} as const;\n\n/**\n * Union type of all property field types, derived from PROPERTY_FIELD_TYPES constant.\n * @see deriving-typeof-for-object-keys pattern\n */\nexport type PropertyFieldType =\n (typeof PROPERTY_FIELD_TYPES)[keyof typeof PROPERTY_FIELD_TYPES];\n\n/**\n * Runtime validation for property field types.\n * @param value - The value to check\n * @returns true if value is a valid PropertyFieldType\n */\nexport function isPropertyFieldType(value: string): value is PropertyFieldType {\n return Object.values(PROPERTY_FIELD_TYPES).includes(\n value as PropertyFieldType,\n );\n}\n\n/**\n * Group label for fields that override theme-derived styling.\n * Rendered last within a tab and collapsed by default so the primary\n * configuration surface stays clean.\n */\nexport const CUSTOM_STYLING_GROUP = \"Custom styling\";\n\n/**\n * Base schema for a property field\n */\nexport interface PropertyFieldSchema {\n /** Property key in the widget props */\n key: string;\n /** Display label for the field */\n label: string;\n /** Field type determines the input control */\n type: PropertyFieldType;\n /** Optional description/help text */\n description?: string;\n /** Optional default value */\n defaultValue?: unknown;\n /** Optional tab ID (must match a TabConfig id if widget has tabsConfig) */\n tab?: string;\n /** Optional group for organizing fields within a tab */\n group?: string;\n /**\n * When true, this field is treated as an override of a value that can\n * otherwise be inherited from the active theme (e.g. border radius,\n * padding, border width). Advanced fields are automatically bucketed\n * into the `CUSTOM_STYLING_GROUP` at the bottom of their tab and\n * rendered collapsed by default so the default surface area stays\n * minimal.\n */\n advanced?: boolean;\n /**\n * @deprecated Use requiresKeyValue instead\n */\n requiresKeyToBeTrue?: string;\n /** Optional requires a specific key to have a specific value. Supports single condition or array (AND logic). */\n requiresKeyValue?:\n | { key: string; value: unknown }\n | Array<{ key: string; value: unknown }>;\n}\n\n/**\n * Text field schema\n */\nexport interface TextFieldSchema extends PropertyFieldSchema {\n type: \"text\";\n placeholder?: string;\n maxLength?: number;\n}\n\n/**\n * Textarea field schema\n */\nexport interface TextareaFieldSchema extends PropertyFieldSchema {\n type: \"textarea\";\n placeholder?: string;\n rows?: number;\n maxLength?: number;\n}\n\n/**\n * Number field schema\n */\nexport interface NumberFieldSchema extends PropertyFieldSchema {\n type: \"number\";\n min?: number;\n max?: number;\n step?: number;\n}\n\n/**\n * Boolean field schema\n */\nexport interface BooleanFieldSchema extends PropertyFieldSchema {\n type: \"boolean\";\n}\n\n/**\n * Select field schema with type-safe option values.\n * Uses StrictOmit to ensure \"defaultValue\" key exists on PropertyFieldSchema.\n */\nexport interface SelectFieldSchema<\n T extends string | number = string | number,\n> extends StrictOmit<PropertyFieldSchema, \"defaultValue\"> {\n type: \"select\";\n options: Array<{ label: string; value: T }>;\n defaultValue?: T;\n}\n\n/**\n * Color field schema\n */\nexport interface ColorFieldSchema extends PropertyFieldSchema {\n type: \"color\";\n}\n\n/**\n * Range slider field schema\n */\nexport interface RangeFieldSchema extends PropertyFieldSchema {\n type: \"range\";\n min: number;\n max: number;\n step?: number;\n}\n\n/**\n * Data source field schema for configuring widget data sources\n */\nexport interface DataSourceFieldSchema extends PropertyFieldSchema {\n type: \"dataSource\";\n}\n\n/**\n * Resource field schema for selecting a single resource from the selection modal\n */\nexport interface ResourceFieldSchema extends PropertyFieldSchema {\n type: \"resource\";\n /** Optional filter to specific shareable types */\n allowedTypes?: string[];\n}\n\n/**\n * Image field schema for selecting a single image from the image picker\n */\nexport interface ImageFieldSchema extends PropertyFieldSchema {\n type: \"image\";\n}\n\n/**\n * Alignment field schema\n */\nexport interface AlignmentFieldSchema extends PropertyFieldSchema {\n type: \"alignment\";\n options: {\n verticalEnabled: boolean;\n horizontalEnabled: boolean;\n };\n defaultValue?: AlignOptions;\n}\n\n/**\n * Slider field schema with optional unit suffix (e.g., \"rem\", \"px\")\n */\nexport interface SliderFieldSchema extends PropertyFieldSchema {\n type: \"slider\";\n min: number;\n max: number;\n step?: number;\n unit?: string;\n}\n\n/**\n * Color picker field schema with optional swatches\n */\nexport interface ColorPickerFieldSchema extends PropertyFieldSchema {\n type: \"colorPicker\";\n swatches?: string[];\n}\n\n/**\n * Section header field schema for visual grouping\n */\nexport interface SectionHeaderFieldSchema extends PropertyFieldSchema {\n type: \"sectionHeader\";\n subtitle?: string;\n}\n\n/**\n * Separator field schema for visual separation\n */\nexport interface SeparatorFieldSchema extends PropertyFieldSchema {\n type: \"separator\";\n}\n\n/**\n * Button group field schema.\n * Uses StrictOmit to ensure \"defaultValue\" key exists on PropertyFieldSchema.\n */\nexport interface ButtonGroupFieldSchema<\n T extends string | number = string | number,\n> extends StrictOmit<PropertyFieldSchema, \"defaultValue\"> {\n type: \"buttonGroup\";\n options: Array<{\n label?: string;\n ariaLabel?: string;\n icon?: LucideIcon;\n value: T;\n }>;\n defaultValue?: T;\n}\n\n/**\n * Color select field schema\n */\nexport interface ColorSelectFieldSchema extends PropertyFieldSchema {\n type: \"colorSelect\";\n defaultValue?: ColorOptions;\n excludeColors?: ColorOptions[];\n}\n\n/**\n * Section layout select field schema for visual masonry layout selector\n */\nexport interface SectionLayoutSelectFieldSchema extends PropertyFieldSchema {\n type: \"sectionLayoutSelect\";\n defaultValue?: SectionLayoutType;\n}\n\n/**\n * Background field combines resource selection and color properties.\n * Uses StrictOmit to exclude conflicting \"type\" discriminant from parents.\n */\nexport interface BackgroundFieldSchema\n extends\n StrictOmit<ResourceFieldSchema, \"type\">,\n StrictOmit<ColorFieldSchema, \"type\"> {\n type: \"background\";\n}\n\n/**\n * Content position field schema for 3x3 grid position picker\n */\nexport interface ContentPositionFieldSchema extends PropertyFieldSchema {\n type: \"contentPosition\";\n defaultValue?: string;\n}\n\n/**\n * Text size select field schema for visual font size selector\n */\nexport interface TextSizeSelectFieldSchema extends PropertyFieldSchema {\n type: \"textSizeSelect\";\n defaultValue?: FontSizeOptions;\n}\n\n/**\n * CSS unit type for height/width fields\n */\nexport type CssUnit = \"px\" | \"rem\" | \"vh\" | \"%\";\n\n/**\n * CSS unit field schema for numeric values with selectable units (px, rem, vh, %)\n */\nexport interface CssUnitFieldSchema extends PropertyFieldSchema {\n type: \"cssUnit\";\n minByUnit?: Partial<Record<CssUnit, number>>;\n maxByUnit?: Partial<Record<CssUnit, number>>;\n stepByUnit?: Partial<Record<CssUnit, number>>;\n allowedUnits?: CssUnit[];\n defaultUnit?: CssUnit;\n}\n\n/**\n * Font picker field schema for Google Fonts selection\n */\nexport interface FontPickerFieldSchema extends PropertyFieldSchema {\n type: \"fontPicker\";\n placeholder?: string;\n}\n\n/**\n * String array field schema for managing lists of text items\n */\nexport interface StringArrayFieldSchema extends PropertyFieldSchema {\n type: \"stringArray\";\n placeholder?: string;\n defaultValue?: string[];\n}\n\n/**\n * Border radius composite field schema for controlling 4 corners with a single field.\n * Maps to 4 individual widget prop keys (topLeft, topRight, bottomLeft, bottomRight).\n */\nexport interface BorderRadiusFieldSchema extends PropertyFieldSchema {\n type: \"borderRadius\";\n keys: {\n topLeft: string;\n topRight: string;\n bottomLeft: string;\n bottomRight: string;\n };\n defaultValue?: BorderRadiusOptions;\n}\n\n/**\n * Screen picker field schema for selecting a portal screen (navigation, system, or available)\n */\nexport interface ScreenPickerFieldSchema extends PropertyFieldSchema {\n type: \"screenPicker\";\n /** Whether to include system navigation items in the picker */\n includeSystemItems?: boolean;\n}\n\n/**\n * Union of all field schema types\n */\nexport type PropertyField =\n | TextFieldSchema\n | TextareaFieldSchema\n | NumberFieldSchema\n | BooleanFieldSchema\n | SelectFieldSchema<string | number>\n | ColorFieldSchema\n | RangeFieldSchema\n | DataSourceFieldSchema\n | ResourceFieldSchema\n | ImageFieldSchema\n | AlignmentFieldSchema\n | SliderFieldSchema\n | ColorPickerFieldSchema\n | SectionHeaderFieldSchema\n | SeparatorFieldSchema\n | ButtonGroupFieldSchema<string | number>\n | ColorSelectFieldSchema\n | SectionLayoutSelectFieldSchema\n | BackgroundFieldSchema\n | ContentPositionFieldSchema\n | TextSizeSelectFieldSchema\n | CssUnitFieldSchema\n | FontPickerFieldSchema\n | StringArrayFieldSchema\n | BorderRadiusFieldSchema\n | ScreenPickerFieldSchema;\n\n/**\n * Schema for per-item configuration in custom data sources.\n * Widgets can define this to allow users to configure widget-specific\n * settings for each selected item (e.g., title, description, button).\n */\nexport interface ItemConfigSchema {\n /** Fields available for per-item configuration */\n fields: PropertyField[];\n /** Optional description shown at top of item config panel */\n description?: string;\n}\n\n/**\n * Schema for a widget's editable properties\n */\nexport interface WidgetPropertySchema {\n /** Widget type this schema applies to */\n widgetType: WidgetType;\n /** Display name for the widget */\n displayName: string;\n /** Optional tab configuration - if present, tabs are enabled */\n tabsConfig?: TabConfig[];\n /** Editable property fields */\n fields: PropertyField[];\n /** Optional custom validator function */\n validate?: (props: Record<string, unknown>) => string | null;\n /** Props that can be populated from data sources */\n dataSourceTargetProps?: string[];\n /** Optional schema for per-item configurations in custom data sources */\n itemConfigSchema?: ItemConfigSchema;\n}\n\n/**\n * Registry mapping widget types to their property schemas\n */\nexport type PropertySchemaRegistry = Record<WidgetType, WidgetPropertySchema>;\n\n/**\n * Group property fields by their group property.\n *\n * Fields flagged with `advanced: true` are collected into the\n * `CUSTOM_STYLING_GROUP` bucket regardless of their declared `group`,\n * and that bucket is always placed last so it renders at the bottom of\n * the tab. Non-advanced fields keep their author-declared group and\n * their relative insertion order, including fields that explicitly use\n * `CUSTOM_STYLING_GROUP`.\n */\nexport function groupPropertyFields(\n fields: readonly PropertyField[],\n): Record<string, PropertyField[]> {\n const grouped: Record<string, PropertyField[]> = {};\n const advancedFields: PropertyField[] = [];\n\n fields.forEach((field) => {\n if (field.advanced) {\n advancedFields.push(field);\n return;\n }\n const group = field.group || \"General\";\n if (!grouped[group]) {\n grouped[group] = [];\n }\n grouped[group].push(field);\n });\n\n if (advancedFields.length > 0) {\n const customStylingFields = grouped[CUSTOM_STYLING_GROUP] ?? [];\n delete grouped[CUSTOM_STYLING_GROUP];\n grouped[CUSTOM_STYLING_GROUP] = [...customStylingFields, ...advancedFields];\n }\n\n return grouped;\n}\n\n/**\n * Extract current values from widget props based on property fields\n */\nexport function extractPropertyValues(\n widget: Readonly<WidgetSchema>,\n fields: readonly PropertyField[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n\n fields.forEach((field) => {\n // For borderRadius composite fields, skip the top-level key —\n // it is a schema grouping identifier, not a real widget prop.\n if (field.type === \"borderRadius\") {\n for (const subKey of Object.values(field.keys)) {\n const subValue = widget.props[subKey];\n values[subKey] = subValue !== undefined ? subValue : field.defaultValue;\n }\n return;\n }\n\n // dataSource config is stored at widget.dataSource (top-level),\n // not in widget.props. Surface it under the field key so schema\n // visibility checks (requiresKeyValue) can gate on its presence.\n if (field.type === \"dataSource\") {\n values[field.key] = widget.dataSource;\n return;\n }\n\n const value = widget.props[field.key];\n values[field.key] = value !== undefined ? value : field.defaultValue;\n });\n\n return values;\n}\n\n/**\n * Apply property values to widget props\n */\nexport function applyPropertyValues(\n widget: Readonly<WidgetSchema>,\n values: Readonly<Record<string, unknown>>,\n): WidgetSchema {\n return {\n ...widget,\n props: {\n ...widget.props,\n ...values,\n },\n };\n}\n","import type {\n BorderRadiusFieldSchema,\n ButtonGroupFieldSchema,\n ColorSelectFieldSchema,\n CssUnitFieldSchema,\n TextSizeSelectFieldSchema,\n} from \"./property-schema-types\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontWeightOptions,\n PaddingOptions,\n ButtonSizeOptions,\n GapOptions,\n} from \"../types\";\nimport { Ban } from \"lucide-react\";\n\nexport const getColorField = (\n props: Readonly<Omit<ColorSelectFieldSchema, \"type\">>,\n): ColorSelectFieldSchema => {\n return {\n ...props,\n type: \"colorSelect\",\n };\n};\n\nexport const getBorderRadiusField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<BorderRadiusOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<BorderRadiusOptions> => {\n return {\n // Border radius inherits from the active theme by default. Mark as\n // advanced so widget-level overrides collapse into the \"Custom\n // styling\" disclosure.\n //\n // Note: `groupPropertyFields` ignores `field.group` whenever\n // `field.advanced` is true. If a widget explicitly wants this\n // control to live in its own group, pass `advanced: false`\n // alongside `group: \"...\"` to opt out.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No radius\", value: \"none\" },\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"md\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n { label: \"FULL\", value: \"full\" },\n ],\n };\n};\n\nexport const getPaddingField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<PaddingOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<PaddingOptions> => {\n return {\n // Padding follows the theme's global spacing scale by default.\n // Mark as advanced so widget-level overrides collapse into the\n // \"Custom styling\" disclosure. To keep this field in a different\n // group, pass `advanced: false` alongside `group: \"...\"`.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No padding\", value: 0 },\n { label: \"SM\", value: 2 },\n { label: \"MD\", value: 4 },\n { label: \"LG\", value: 6 },\n { label: \"XL\", value: 8 },\n { label: \"FULL\", value: 10 },\n ],\n };\n};\n\nexport const getButtonSizeField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<ButtonSizeOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<ButtonSizeOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"default\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n ],\n };\n};\n\nexport const getFontWeightField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<FontWeightOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<FontWeightOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { label: \"Normal\", value: \"normal\" },\n { label: \"Medium\", value: \"medium\" },\n { label: \"Semibold\", value: \"semibold\" },\n { label: \"Bold\", value: \"bold\" },\n ],\n };\n};\n\nexport const getFontSizeField = (\n props: Readonly<Omit<TextSizeSelectFieldSchema, \"type\">>,\n): TextSizeSelectFieldSchema => {\n return {\n ...props,\n type: \"textSizeSelect\",\n };\n};\n\nexport const getGapField = (\n props: Readonly<Omit<ButtonGroupFieldSchema<GapOptions>, \"options\" | \"type\">>,\n): ButtonGroupFieldSchema<GapOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No gap\", value: \"none\" },\n { label: \"XS\", value: \"xs\" },\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"md\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n ],\n };\n};\n\nexport const getHeightField = (\n props: Readonly<\n Omit<\n CssUnitFieldSchema,\n \"type\" | \"minByUnit\" | \"maxByUnit\" | \"stepByUnit\" | \"allowedUnits\"\n >\n >,\n): CssUnitFieldSchema => {\n return {\n ...props,\n type: \"cssUnit\",\n allowedUnits: [\"px\", \"vh\", \"rem\"],\n minByUnit: { px: 10, vh: 1, rem: 1 },\n maxByUnit: { px: 1200, vh: 100, rem: 75 },\n stepByUnit: { px: 10, vh: 1, rem: 1 },\n };\n};\n\nexport const getBorderRadiusCompositeField = (\n props: Readonly<\n Omit<BorderRadiusFieldSchema, \"type\" | \"keys\"> & {\n keys?: BorderRadiusFieldSchema[\"keys\"];\n }\n >,\n): BorderRadiusFieldSchema => {\n return {\n // Per-corner radius controls are theme-derivable in the same way as\n // the simpler `getBorderRadiusField`. Pass `advanced: false`\n // alongside `group: \"...\"` to opt out of Custom Styling.\n advanced: true,\n ...props,\n type: \"borderRadius\",\n keys: props.keys ?? {\n topLeft: \"borderRadiusTL\",\n topRight: \"borderRadiusTR\",\n bottomLeft: \"borderRadiusBL\",\n bottomRight: \"borderRadiusBR\",\n },\n };\n};\n\n/**\n * Gap value mapping - use `as const satisfies` for compile-time validation\n * with literal type preservation.\n */\nexport const gapValues: {\n readonly none: 0;\n readonly xs: 1;\n readonly sm: 2;\n readonly md: 4;\n readonly lg: 6;\n readonly xl: 8;\n} = {\n none: 0,\n xs: 1,\n sm: 2,\n md: 4,\n lg: 6,\n xl: 8,\n} as const satisfies Record<GapOptions, number>;\n\nexport const getBorderWidthField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<BorderWidthOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<BorderWidthOptions> => ({\n // Border width is a styling override of a theme-derivable decision.\n // Pass `advanced: false` alongside `group: \"...\"` to opt out of\n // Custom Styling.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No border\", value: \"none\" },\n { label: \"THIN\", value: \"thin\" },\n { label: \"MD\", value: \"medium\" },\n { label: \"THICK\", value: \"thick\" },\n ],\n});\n\nexport const getBorderColorField: (\n props: Readonly<Omit<ColorSelectFieldSchema, \"type\">>,\n) => ColorSelectFieldSchema = getColorField;\n\n/**\n * Border width class mapping - full literal Tailwind classes for scanner.\n */\nexport const borderWidthClasses: {\n readonly none: \"border-0\";\n readonly thin: \"border\";\n readonly medium: \"border-2\";\n readonly thick: \"border-4\";\n} = {\n none: \"border-0\",\n thin: \"border\",\n medium: \"border-2\",\n thick: \"border-4\",\n} as const satisfies Record<BorderWidthOptions, string>;\n\n/**\n * Border color class mapping - full literal Tailwind classes for scanner.\n */\nexport const borderColorClasses: {\n readonly background: \"border-background\";\n readonly foreground: \"border-foreground\";\n readonly primary: \"border-primary\";\n readonly secondary: \"border-secondary\";\n readonly accent: \"border-accent\";\n readonly border: \"border-border\";\n readonly muted: \"border-muted\";\n readonly destructive: \"border-destructive\";\n readonly transparent: \"border-transparent\";\n} = {\n background: \"border-background\",\n foreground: \"border-foreground\",\n primary: \"border-primary\",\n secondary: \"border-secondary\",\n accent: \"border-accent\",\n border: \"border-border\",\n muted: \"border-muted\",\n destructive: \"border-destructive\",\n transparent: \"border-transparent\",\n} as const satisfies Record<ColorOptions, string>;\n","import {\n getBorderColorField,\n getBorderRadiusField,\n getBorderWidthField,\n getPaddingField,\n} from \"./field-helpers\";\nimport type { PropertyField } from \"./property-schema-types\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n PaddingOptions,\n} from \"../types\";\n\nexport const DROPLET_WIDGET_SIZES = {\n auto: { label: \"Auto\", value: \"auto\", height: 400 },\n compact: { label: \"Compact\", value: \"compact\", height: 300 },\n standard: { label: \"Standard\", value: \"standard\", height: 500 },\n tall: { label: \"Tall\", value: \"tall\", height: 700 },\n \"extra-tall\": { label: \"Extra Tall\", value: \"extra-tall\", height: 900 },\n custom: { label: \"Custom\", value: \"custom\", height: 500 },\n} as const;\n\nexport type DropletWidgetSizeKey = keyof typeof DROPLET_WIDGET_SIZES;\n\nexport const DROPLET_CHROME_PROP_KEYS = [\n \"padding\",\n \"borderRadius\",\n \"borderWidth\",\n \"borderColor\",\n \"widgetSize\",\n \"customHeight\",\n \"placement\",\n] as const;\n\nexport type DropletChromePropKey = (typeof DROPLET_CHROME_PROP_KEYS)[number];\n\nconst CHROME_KEY_SET: ReadonlySet<string> = new Set(DROPLET_CHROME_PROP_KEYS);\n\nexport const DROPLET_CHROME_DEFAULTS: Readonly<\n Record<DropletChromePropKey, unknown>\n> = Object.freeze({\n padding: 0,\n borderRadius: \"md\",\n borderWidth: \"none\",\n borderColor: \"muted\",\n widgetSize: \"auto\",\n customHeight: \"500px\",\n placement: \"inline\",\n});\n\n/**\n * Resolve the configured chrome into an actual pixel height for the\n * iframe. Called by the renderer. If the size is \"auto\" the renderer\n * uses this as the initial height but lets `fluid:resize` override it.\n */\nexport function resolveDropletHeight(\n widgetSize: DropletWidgetSizeKey | string,\n customHeight: string | undefined,\n): number {\n if (widgetSize === \"custom\" && customHeight) {\n const parsed = parseInt(customHeight, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 500;\n }\n const preset = DROPLET_WIDGET_SIZES[widgetSize as DropletWidgetSizeKey];\n return preset ? preset.height : 400;\n}\n\n/**\n * Translatable user-facing strings emitted by `dropletChromeFields`.\n * The host injects these via `dropletChromeFields({ labels })` so the\n * package itself doesn't depend on translation infrastructure. English\n * defaults match the legacy hardcoded labels.\n */\nexport interface DropletChromeLabels {\n readonly widgetSize?: string;\n readonly widgetSizeDescription?: string;\n readonly customHeight?: string;\n readonly customHeightDescription?: string;\n readonly padding?: string;\n readonly paddingDescription?: string;\n readonly borderRadius?: string;\n readonly borderRadiusDescription?: string;\n readonly borderWidth?: string;\n readonly borderWidthDescription?: string;\n readonly borderColor?: string;\n readonly borderColorDescription?: string;\n readonly sizes?: {\n readonly auto?: string;\n readonly compact?: string;\n readonly standard?: string;\n readonly tall?: string;\n readonly extraTall?: string;\n readonly custom?: string;\n };\n}\n\nconst DEFAULT_CHROME_LABELS: Required<Omit<DropletChromeLabels, \"sizes\">> & {\n sizes: Required<NonNullable<DropletChromeLabels[\"sizes\"]>>;\n} = {\n widgetSize: \"Widget Size\",\n widgetSizeDescription:\n 'How tall the widget appears. \"Auto\" lets the widget resize itself.',\n customHeight: \"Custom Height\",\n customHeightDescription: \"Height in px (e.g. 600).\",\n padding: \"Padding\",\n paddingDescription: \"Padding around the widget.\",\n borderRadius: \"Border Radius\",\n borderRadiusDescription: \"Rounding applied to the widget container.\",\n borderWidth: \"Border Width\",\n borderWidthDescription: \"Border thickness around the widget.\",\n borderColor: \"Border Color\",\n borderColorDescription: \"Border color when border width is set.\",\n sizes: {\n auto: \"Auto\",\n compact: \"Compact\",\n standard: \"Standard\",\n tall: \"Tall\",\n extraTall: \"Extra Tall\",\n custom: \"Custom\",\n },\n};\n\nexport function dropletChromeFields(options?: {\n readonly labels?: DropletChromeLabels;\n}): PropertyField[] {\n const labels: typeof DEFAULT_CHROME_LABELS = {\n ...DEFAULT_CHROME_LABELS,\n ...options?.labels,\n sizes: { ...DEFAULT_CHROME_LABELS.sizes, ...options?.labels?.sizes },\n };\n\n const sizeOptions = [\n { label: labels.sizes.auto, value: \"auto\" },\n { label: labels.sizes.compact, value: \"compact\" },\n { label: labels.sizes.standard, value: \"standard\" },\n { label: labels.sizes.tall, value: \"tall\" },\n { label: labels.sizes.extraTall, value: \"extra-tall\" },\n { label: labels.sizes.custom, value: \"custom\" },\n ];\n\n return [\n {\n key: \"widgetSize\",\n label: labels.widgetSize,\n type: \"select\",\n description: labels.widgetSizeDescription,\n defaultValue: \"auto\",\n group: \"Design\",\n options: sizeOptions,\n },\n {\n key: \"customHeight\",\n label: labels.customHeight,\n type: \"text\",\n description: labels.customHeightDescription,\n defaultValue: \"500\",\n group: \"Design\",\n requiresKeyValue: { key: \"widgetSize\", value: \"custom\" },\n },\n getPaddingField({\n key: \"padding\",\n label: labels.padding,\n description: labels.paddingDescription,\n defaultValue: DROPLET_CHROME_DEFAULTS.padding as 0,\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: labels.borderRadius,\n description: labels.borderRadiusDescription,\n defaultValue: \"md\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: labels.borderWidth,\n description: labels.borderWidthDescription,\n defaultValue: \"none\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: labels.borderColor,\n description: labels.borderColorDescription,\n defaultValue: \"muted\",\n group: \"Design\",\n }),\n ];\n}\n\nexport function isDropletChromeKey(key: string): key is DropletChromePropKey {\n return CHROME_KEY_SET.has(key);\n}\n\nexport function splitDropletProps(props: Readonly<Record<string, unknown>>): {\n chrome: Record<DropletChromePropKey, unknown>;\n forwarded: Record<string, unknown>;\n} {\n const chrome = { ...DROPLET_CHROME_DEFAULTS } as Record<\n DropletChromePropKey,\n unknown\n >;\n const forwarded: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n if (isDropletChromeKey(key)) {\n chrome[key] = value;\n } else {\n forwarded[key] = value;\n }\n }\n return { chrome, forwarded };\n}\n\nconst VALID_PADDINGS = new Set<PaddingOptions>([0, 2, 4, 6, 8, 10]);\nconst VALID_BORDER_RADII = new Set<BorderRadiusOptions>([\n \"none\",\n \"sm\",\n \"md\",\n \"lg\",\n \"xl\",\n \"full\",\n]);\nconst VALID_BORDER_WIDTHS = new Set<BorderWidthOptions>([\n \"none\",\n \"thin\",\n \"medium\",\n \"thick\",\n]);\nconst VALID_BORDER_COLORS = new Set<ColorOptions>([\n \"primary\",\n \"secondary\",\n \"muted\",\n \"accent\",\n \"destructive\",\n]);\n\n/**\n * Validated chrome shape — every field is guaranteed to be a valid\n * option, never `unknown`. Out-of-vocabulary or missing values fall\n * back to the matching default in DROPLET_CHROME_DEFAULTS.\n */\nexport interface ParsedDropletChrome {\n readonly padding: PaddingOptions;\n readonly borderRadius: BorderRadiusOptions;\n readonly borderWidth: BorderWidthOptions;\n readonly borderColor: ColorOptions;\n readonly widgetSize: string;\n readonly customHeight: string | undefined;\n}\n\n/**\n * Parse the unvalidated chrome bag from `splitDropletProps` into a\n * typed shape with runtime-validated values. Replaces ad-hoc `as`\n * casts at the renderer call site so a stale persisted props object\n * (e.g. an enum value renamed across versions) cannot silently produce\n * an undefined Tailwind-class lookup.\n */\nexport function parseDropletChrome(\n chrome: Readonly<Record<DropletChromePropKey, unknown>>,\n): ParsedDropletChrome {\n return {\n padding: VALID_PADDINGS.has(chrome.padding as PaddingOptions)\n ? (chrome.padding as PaddingOptions)\n : 0,\n borderRadius: VALID_BORDER_RADII.has(\n chrome.borderRadius as BorderRadiusOptions,\n )\n ? (chrome.borderRadius as BorderRadiusOptions)\n : \"md\",\n borderWidth: VALID_BORDER_WIDTHS.has(\n chrome.borderWidth as BorderWidthOptions,\n )\n ? (chrome.borderWidth as BorderWidthOptions)\n : \"none\",\n borderColor: VALID_BORDER_COLORS.has(chrome.borderColor as ColorOptions)\n ? (chrome.borderColor as ColorOptions)\n : \"muted\",\n widgetSize:\n typeof chrome.widgetSize === \"string\" ? chrome.widgetSize : \"auto\",\n customHeight:\n typeof chrome.customHeight === \"string\" ? chrome.customHeight : undefined,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registries-ClaAtpiG.cjs","names":["Ban"],"sources":["../../core/src/registries/property-schema-types.ts","../../core/src/registries/field-helpers.ts","../../core/src/registries/droplet-widget-chrome.ts"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport type {\n WidgetType,\n WidgetSchema,\n AlignOptions,\n ColorOptions,\n FontSizeOptions,\n SectionLayoutType,\n StrictOmit,\n BorderRadiusOptions,\n} from \"../types\";\n\n/**\n * Tab configuration for organizing properties\n */\nexport interface TabConfig {\n /** Unique identifier for the tab */\n id: string;\n /** Display label for the tab */\n label: string;\n}\n\n// ============================================================================\n// Property Field Types - Derive from constant for single source of truth\n// ============================================================================\n\n/**\n * Property field type constant - single source of truth for field types.\n * Use PROPERTY_FIELD_TYPES.text instead of \"text\" for type-safe comparisons.\n */\nexport const PROPERTY_FIELD_TYPES = {\n text: \"text\",\n textarea: \"textarea\",\n number: \"number\",\n boolean: \"boolean\",\n select: \"select\",\n color: \"color\",\n range: \"range\",\n dataSource: \"dataSource\",\n resource: \"resource\",\n image: \"image\",\n alignment: \"alignment\",\n slider: \"slider\",\n colorPicker: \"colorPicker\",\n sectionHeader: \"sectionHeader\",\n separator: \"separator\",\n buttonGroup: \"buttonGroup\",\n colorSelect: \"colorSelect\",\n sectionLayoutSelect: \"sectionLayoutSelect\",\n background: \"background\",\n contentPosition: \"contentPosition\",\n textSizeSelect: \"textSizeSelect\",\n cssUnit: \"cssUnit\",\n fontPicker: \"fontPicker\",\n stringArray: \"stringArray\",\n borderRadius: \"borderRadius\",\n screenPicker: \"screenPicker\",\n} as const;\n\n/**\n * Union type of all property field types, derived from PROPERTY_FIELD_TYPES constant.\n * @see deriving-typeof-for-object-keys pattern\n */\nexport type PropertyFieldType =\n (typeof PROPERTY_FIELD_TYPES)[keyof typeof PROPERTY_FIELD_TYPES];\n\n/**\n * Runtime validation for property field types.\n * @param value - The value to check\n * @returns true if value is a valid PropertyFieldType\n */\nexport function isPropertyFieldType(value: string): value is PropertyFieldType {\n return Object.values(PROPERTY_FIELD_TYPES).includes(\n value as PropertyFieldType,\n );\n}\n\n/**\n * Group label for fields that override theme-derived styling.\n * Rendered last within a tab and collapsed by default so the primary\n * configuration surface stays clean.\n */\nexport const CUSTOM_STYLING_GROUP = \"Custom styling\";\n\n/**\n * Base schema for a property field\n */\nexport interface PropertyFieldSchema {\n /** Property key in the widget props */\n key: string;\n /** Display label for the field */\n label: string;\n /** Field type determines the input control */\n type: PropertyFieldType;\n /** Optional description/help text */\n description?: string;\n /** Optional default value */\n defaultValue?: unknown;\n /** Optional tab ID (must match a TabConfig id if widget has tabsConfig) */\n tab?: string;\n /** Optional group for organizing fields within a tab */\n group?: string;\n /**\n * When true, this field is treated as an override of a value that can\n * otherwise be inherited from the active theme (e.g. border radius,\n * padding, border width). Advanced fields are automatically bucketed\n * into the `CUSTOM_STYLING_GROUP` at the bottom of their tab and\n * rendered collapsed by default so the default surface area stays\n * minimal.\n */\n advanced?: boolean;\n /**\n * @deprecated Use requiresKeyValue instead\n */\n requiresKeyToBeTrue?: string;\n /** Optional requires a specific key to have a specific value. Supports single condition or array (AND logic). */\n requiresKeyValue?:\n | { key: string; value: unknown }\n | Array<{ key: string; value: unknown }>;\n}\n\n/**\n * Text field schema\n */\nexport interface TextFieldSchema extends PropertyFieldSchema {\n type: \"text\";\n placeholder?: string;\n maxLength?: number;\n}\n\n/**\n * Textarea field schema\n */\nexport interface TextareaFieldSchema extends PropertyFieldSchema {\n type: \"textarea\";\n placeholder?: string;\n rows?: number;\n maxLength?: number;\n}\n\n/**\n * Number field schema\n */\nexport interface NumberFieldSchema extends PropertyFieldSchema {\n type: \"number\";\n min?: number;\n max?: number;\n step?: number;\n}\n\n/**\n * Boolean field schema\n */\nexport interface BooleanFieldSchema extends PropertyFieldSchema {\n type: \"boolean\";\n}\n\n/**\n * Select field schema with type-safe option values.\n * Uses StrictOmit to ensure \"defaultValue\" key exists on PropertyFieldSchema.\n */\nexport interface SelectFieldSchema<\n T extends string | number = string | number,\n> extends StrictOmit<PropertyFieldSchema, \"defaultValue\"> {\n type: \"select\";\n options: Array<{ label: string; value: T }>;\n defaultValue?: T;\n}\n\n/**\n * Color field schema\n */\nexport interface ColorFieldSchema extends PropertyFieldSchema {\n type: \"color\";\n}\n\n/**\n * Range slider field schema\n */\nexport interface RangeFieldSchema extends PropertyFieldSchema {\n type: \"range\";\n min: number;\n max: number;\n step?: number;\n}\n\n/**\n * Data source field schema for configuring widget data sources\n */\nexport interface DataSourceFieldSchema extends PropertyFieldSchema {\n type: \"dataSource\";\n}\n\n/**\n * Resource field schema for selecting a single resource from the selection modal\n */\nexport interface ResourceFieldSchema extends PropertyFieldSchema {\n type: \"resource\";\n /** Optional filter to specific shareable types */\n allowedTypes?: string[];\n}\n\n/**\n * Image field schema for selecting a single image from the image picker\n */\nexport interface ImageFieldSchema extends PropertyFieldSchema {\n type: \"image\";\n}\n\n/**\n * Alignment field schema\n */\nexport interface AlignmentFieldSchema extends PropertyFieldSchema {\n type: \"alignment\";\n options: {\n verticalEnabled: boolean;\n horizontalEnabled: boolean;\n };\n defaultValue?: AlignOptions;\n}\n\n/**\n * Slider field schema with optional unit suffix (e.g., \"rem\", \"px\")\n */\nexport interface SliderFieldSchema extends PropertyFieldSchema {\n type: \"slider\";\n min: number;\n max: number;\n step?: number;\n unit?: string;\n}\n\n/**\n * Color picker field schema with optional swatches\n */\nexport interface ColorPickerFieldSchema extends PropertyFieldSchema {\n type: \"colorPicker\";\n swatches?: string[];\n}\n\n/**\n * Section header field schema for visual grouping\n */\nexport interface SectionHeaderFieldSchema extends PropertyFieldSchema {\n type: \"sectionHeader\";\n subtitle?: string;\n}\n\n/**\n * Separator field schema for visual separation\n */\nexport interface SeparatorFieldSchema extends PropertyFieldSchema {\n type: \"separator\";\n}\n\n/**\n * Button group field schema.\n * Uses StrictOmit to ensure \"defaultValue\" key exists on PropertyFieldSchema.\n */\nexport interface ButtonGroupFieldSchema<\n T extends string | number = string | number,\n> extends StrictOmit<PropertyFieldSchema, \"defaultValue\"> {\n type: \"buttonGroup\";\n options: Array<{\n label?: string;\n ariaLabel?: string;\n icon?: LucideIcon;\n value: T;\n }>;\n defaultValue?: T;\n}\n\n/**\n * Color select field schema\n */\nexport interface ColorSelectFieldSchema extends PropertyFieldSchema {\n type: \"colorSelect\";\n defaultValue?: ColorOptions;\n excludeColors?: ColorOptions[];\n}\n\n/**\n * Section layout select field schema for visual masonry layout selector\n */\nexport interface SectionLayoutSelectFieldSchema extends PropertyFieldSchema {\n type: \"sectionLayoutSelect\";\n defaultValue?: SectionLayoutType;\n}\n\n/**\n * Background field combines resource selection and color properties.\n * Uses StrictOmit to exclude conflicting \"type\" discriminant from parents.\n */\nexport interface BackgroundFieldSchema\n extends\n StrictOmit<ResourceFieldSchema, \"type\">,\n StrictOmit<ColorFieldSchema, \"type\"> {\n type: \"background\";\n}\n\n/**\n * Content position field schema for 3x3 grid position picker\n */\nexport interface ContentPositionFieldSchema extends PropertyFieldSchema {\n type: \"contentPosition\";\n defaultValue?: string;\n}\n\n/**\n * Text size select field schema for visual font size selector\n */\nexport interface TextSizeSelectFieldSchema extends PropertyFieldSchema {\n type: \"textSizeSelect\";\n defaultValue?: FontSizeOptions;\n}\n\n/**\n * CSS unit type for height/width fields\n */\nexport type CssUnit = \"px\" | \"rem\" | \"vh\" | \"%\";\n\n/**\n * CSS unit field schema for numeric values with selectable units (px, rem, vh, %)\n */\nexport interface CssUnitFieldSchema extends PropertyFieldSchema {\n type: \"cssUnit\";\n minByUnit?: Partial<Record<CssUnit, number>>;\n maxByUnit?: Partial<Record<CssUnit, number>>;\n stepByUnit?: Partial<Record<CssUnit, number>>;\n allowedUnits?: CssUnit[];\n defaultUnit?: CssUnit;\n}\n\n/**\n * Font picker field schema for Google Fonts selection\n */\nexport interface FontPickerFieldSchema extends PropertyFieldSchema {\n type: \"fontPicker\";\n placeholder?: string;\n}\n\n/**\n * String array field schema for managing lists of text items\n */\nexport interface StringArrayFieldSchema extends PropertyFieldSchema {\n type: \"stringArray\";\n placeholder?: string;\n defaultValue?: string[];\n}\n\n/**\n * Border radius composite field schema for controlling 4 corners with a single field.\n * Maps to 4 individual widget prop keys (topLeft, topRight, bottomLeft, bottomRight).\n */\nexport interface BorderRadiusFieldSchema extends PropertyFieldSchema {\n type: \"borderRadius\";\n keys: {\n topLeft: string;\n topRight: string;\n bottomLeft: string;\n bottomRight: string;\n };\n defaultValue?: BorderRadiusOptions;\n}\n\n/**\n * Screen picker field schema for selecting a portal screen (navigation, system, or available)\n */\nexport interface ScreenPickerFieldSchema extends PropertyFieldSchema {\n type: \"screenPicker\";\n /** Whether to include system navigation items in the picker */\n includeSystemItems?: boolean;\n}\n\n/**\n * Union of all field schema types\n */\nexport type PropertyField =\n | TextFieldSchema\n | TextareaFieldSchema\n | NumberFieldSchema\n | BooleanFieldSchema\n | SelectFieldSchema<string | number>\n | ColorFieldSchema\n | RangeFieldSchema\n | DataSourceFieldSchema\n | ResourceFieldSchema\n | ImageFieldSchema\n | AlignmentFieldSchema\n | SliderFieldSchema\n | ColorPickerFieldSchema\n | SectionHeaderFieldSchema\n | SeparatorFieldSchema\n | ButtonGroupFieldSchema<string | number>\n | ColorSelectFieldSchema\n | SectionLayoutSelectFieldSchema\n | BackgroundFieldSchema\n | ContentPositionFieldSchema\n | TextSizeSelectFieldSchema\n | CssUnitFieldSchema\n | FontPickerFieldSchema\n | StringArrayFieldSchema\n | BorderRadiusFieldSchema\n | ScreenPickerFieldSchema;\n\n/**\n * Schema for per-item configuration in custom data sources.\n * Widgets can define this to allow users to configure widget-specific\n * settings for each selected item (e.g., title, description, button).\n */\nexport interface ItemConfigSchema {\n /** Fields available for per-item configuration */\n fields: PropertyField[];\n /** Optional description shown at top of item config panel */\n description?: string;\n}\n\n/**\n * Schema for a widget's editable properties\n */\nexport interface WidgetPropertySchema {\n /** Widget type this schema applies to */\n widgetType: WidgetType;\n /** Display name for the widget */\n displayName: string;\n /** Optional tab configuration - if present, tabs are enabled */\n tabsConfig?: TabConfig[];\n /** Editable property fields */\n fields: PropertyField[];\n /** Optional custom validator function */\n validate?: (props: Record<string, unknown>) => string | null;\n /** Props that can be populated from data sources */\n dataSourceTargetProps?: string[];\n /** Optional schema for per-item configurations in custom data sources */\n itemConfigSchema?: ItemConfigSchema;\n}\n\n/**\n * Registry mapping widget types to their property schemas\n */\nexport type PropertySchemaRegistry = Record<WidgetType, WidgetPropertySchema>;\n\n/**\n * Group property fields by their group property.\n *\n * Fields flagged with `advanced: true` are collected into the\n * `CUSTOM_STYLING_GROUP` bucket regardless of their declared `group`,\n * and that bucket is always placed last so it renders at the bottom of\n * the tab. Non-advanced fields keep their author-declared group and\n * their relative insertion order, including fields that explicitly use\n * `CUSTOM_STYLING_GROUP`.\n */\nexport function groupPropertyFields(\n fields: readonly PropertyField[],\n): Record<string, PropertyField[]> {\n const grouped: Record<string, PropertyField[]> = {};\n const advancedFields: PropertyField[] = [];\n\n fields.forEach((field) => {\n if (field.advanced) {\n advancedFields.push(field);\n return;\n }\n const group = field.group || \"General\";\n if (!grouped[group]) {\n grouped[group] = [];\n }\n grouped[group].push(field);\n });\n\n if (advancedFields.length > 0) {\n const customStylingFields = grouped[CUSTOM_STYLING_GROUP] ?? [];\n delete grouped[CUSTOM_STYLING_GROUP];\n grouped[CUSTOM_STYLING_GROUP] = [...customStylingFields, ...advancedFields];\n }\n\n return grouped;\n}\n\n/**\n * Extract current values from widget props based on property fields\n */\nexport function extractPropertyValues(\n widget: Readonly<WidgetSchema>,\n fields: readonly PropertyField[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n\n fields.forEach((field) => {\n // For borderRadius composite fields, skip the top-level key —\n // it is a schema grouping identifier, not a real widget prop.\n if (field.type === \"borderRadius\") {\n for (const subKey of Object.values(field.keys)) {\n const subValue = widget.props[subKey];\n values[subKey] = subValue !== undefined ? subValue : field.defaultValue;\n }\n return;\n }\n\n // dataSource config is stored at widget.dataSource (top-level),\n // not in widget.props. Surface it under the field key so schema\n // visibility checks (requiresKeyValue) can gate on its presence.\n if (field.type === \"dataSource\") {\n values[field.key] = widget.dataSource;\n return;\n }\n\n const value = widget.props[field.key];\n values[field.key] = value !== undefined ? value : field.defaultValue;\n });\n\n return values;\n}\n\n/**\n * Apply property values to widget props\n */\nexport function applyPropertyValues(\n widget: Readonly<WidgetSchema>,\n values: Readonly<Record<string, unknown>>,\n): WidgetSchema {\n return {\n ...widget,\n props: {\n ...widget.props,\n ...values,\n },\n };\n}\n","import type {\n BorderRadiusFieldSchema,\n ButtonGroupFieldSchema,\n ColorSelectFieldSchema,\n CssUnitFieldSchema,\n TextSizeSelectFieldSchema,\n} from \"./property-schema-types\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontWeightOptions,\n PaddingOptions,\n ButtonSizeOptions,\n GapOptions,\n} from \"../types\";\nimport { Ban } from \"lucide-react\";\n\nexport const getColorField = (\n props: Readonly<Omit<ColorSelectFieldSchema, \"type\">>,\n): ColorSelectFieldSchema => {\n return {\n ...props,\n type: \"colorSelect\",\n };\n};\n\nexport const getBorderRadiusField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<BorderRadiusOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<BorderRadiusOptions> => {\n return {\n // Border radius inherits from the active theme by default. Mark as\n // advanced so widget-level overrides collapse into the \"Custom\n // styling\" disclosure.\n //\n // Note: `groupPropertyFields` ignores `field.group` whenever\n // `field.advanced` is true. If a widget explicitly wants this\n // control to live in its own group, pass `advanced: false`\n // alongside `group: \"...\"` to opt out.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No radius\", value: \"none\" },\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"md\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n { label: \"FULL\", value: \"full\" },\n ],\n };\n};\n\nexport const getPaddingField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<PaddingOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<PaddingOptions> => {\n return {\n // Padding follows the theme's global spacing scale by default.\n // Mark as advanced so widget-level overrides collapse into the\n // \"Custom styling\" disclosure. To keep this field in a different\n // group, pass `advanced: false` alongside `group: \"...\"`.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No padding\", value: 0 },\n { label: \"SM\", value: 2 },\n { label: \"MD\", value: 4 },\n { label: \"LG\", value: 6 },\n { label: \"XL\", value: 8 },\n { label: \"FULL\", value: 10 },\n ],\n };\n};\n\nexport const getButtonSizeField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<ButtonSizeOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<ButtonSizeOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"default\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n ],\n };\n};\n\nexport const getFontWeightField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<FontWeightOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<FontWeightOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { label: \"Normal\", value: \"normal\" },\n { label: \"Medium\", value: \"medium\" },\n { label: \"Semibold\", value: \"semibold\" },\n { label: \"Bold\", value: \"bold\" },\n ],\n };\n};\n\nexport const getFontSizeField = (\n props: Readonly<Omit<TextSizeSelectFieldSchema, \"type\">>,\n): TextSizeSelectFieldSchema => {\n return {\n ...props,\n type: \"textSizeSelect\",\n };\n};\n\nexport const getGapField = (\n props: Readonly<Omit<ButtonGroupFieldSchema<GapOptions>, \"options\" | \"type\">>,\n): ButtonGroupFieldSchema<GapOptions> => {\n return {\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No gap\", value: \"none\" },\n { label: \"XS\", value: \"xs\" },\n { label: \"SM\", value: \"sm\" },\n { label: \"MD\", value: \"md\" },\n { label: \"LG\", value: \"lg\" },\n { label: \"XL\", value: \"xl\" },\n ],\n };\n};\n\nexport const getHeightField = (\n props: Readonly<\n Omit<\n CssUnitFieldSchema,\n \"type\" | \"minByUnit\" | \"maxByUnit\" | \"stepByUnit\" | \"allowedUnits\"\n >\n >,\n): CssUnitFieldSchema => {\n return {\n ...props,\n type: \"cssUnit\",\n allowedUnits: [\"px\", \"vh\", \"rem\"],\n minByUnit: { px: 10, vh: 1, rem: 1 },\n maxByUnit: { px: 1200, vh: 100, rem: 75 },\n stepByUnit: { px: 10, vh: 1, rem: 1 },\n };\n};\n\nexport const getBorderRadiusCompositeField = (\n props: Readonly<\n Omit<BorderRadiusFieldSchema, \"type\" | \"keys\"> & {\n keys?: BorderRadiusFieldSchema[\"keys\"];\n }\n >,\n): BorderRadiusFieldSchema => {\n return {\n // Per-corner radius controls are theme-derivable in the same way as\n // the simpler `getBorderRadiusField`. Pass `advanced: false`\n // alongside `group: \"...\"` to opt out of Custom Styling.\n advanced: true,\n ...props,\n type: \"borderRadius\",\n keys: props.keys ?? {\n topLeft: \"borderRadiusTL\",\n topRight: \"borderRadiusTR\",\n bottomLeft: \"borderRadiusBL\",\n bottomRight: \"borderRadiusBR\",\n },\n };\n};\n\n/**\n * Gap value mapping - use `as const satisfies` for compile-time validation\n * with literal type preservation.\n */\nexport const gapValues: {\n readonly none: 0;\n readonly xs: 1;\n readonly sm: 2;\n readonly md: 4;\n readonly lg: 6;\n readonly xl: 8;\n} = {\n none: 0,\n xs: 1,\n sm: 2,\n md: 4,\n lg: 6,\n xl: 8,\n} as const satisfies Record<GapOptions, number>;\n\nexport const getBorderWidthField = (\n props: Readonly<\n Omit<ButtonGroupFieldSchema<BorderWidthOptions>, \"options\" | \"type\">\n >,\n): ButtonGroupFieldSchema<BorderWidthOptions> => ({\n // Border width is a styling override of a theme-derivable decision.\n // Pass `advanced: false` alongside `group: \"...\"` to opt out of\n // Custom Styling.\n advanced: true,\n ...props,\n type: \"buttonGroup\",\n options: [\n { icon: Ban, ariaLabel: \"No border\", value: \"none\" },\n { label: \"THIN\", value: \"thin\" },\n { label: \"MD\", value: \"medium\" },\n { label: \"THICK\", value: \"thick\" },\n ],\n});\n\nexport const getBorderColorField: (\n props: Readonly<Omit<ColorSelectFieldSchema, \"type\">>,\n) => ColorSelectFieldSchema = getColorField;\n\n/**\n * Border width class mapping - full literal Tailwind classes for scanner.\n */\nexport const borderWidthClasses: {\n readonly none: \"border-0\";\n readonly thin: \"border\";\n readonly medium: \"border-2\";\n readonly thick: \"border-4\";\n} = {\n none: \"border-0\",\n thin: \"border\",\n medium: \"border-2\",\n thick: \"border-4\",\n} as const satisfies Record<BorderWidthOptions, string>;\n\n/**\n * Border color class mapping - full literal Tailwind classes for scanner.\n */\nexport const borderColorClasses: {\n readonly background: \"border-background\";\n readonly foreground: \"border-foreground\";\n readonly primary: \"border-primary\";\n readonly secondary: \"border-secondary\";\n readonly accent: \"border-accent\";\n readonly border: \"border-border\";\n readonly muted: \"border-muted\";\n readonly destructive: \"border-destructive\";\n readonly transparent: \"border-transparent\";\n} = {\n background: \"border-background\",\n foreground: \"border-foreground\",\n primary: \"border-primary\",\n secondary: \"border-secondary\",\n accent: \"border-accent\",\n border: \"border-border\",\n muted: \"border-muted\",\n destructive: \"border-destructive\",\n transparent: \"border-transparent\",\n} as const satisfies Record<ColorOptions, string>;\n","import {\n getBorderColorField,\n getBorderRadiusField,\n getBorderWidthField,\n getPaddingField,\n} from \"./field-helpers\";\nimport type { PropertyField } from \"./property-schema-types\";\nimport type {\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n PaddingOptions,\n} from \"../types\";\n\nexport const DROPLET_WIDGET_SIZES = {\n auto: { label: \"Auto\", value: \"auto\", height: 400 },\n compact: { label: \"Compact\", value: \"compact\", height: 300 },\n standard: { label: \"Standard\", value: \"standard\", height: 500 },\n tall: { label: \"Tall\", value: \"tall\", height: 700 },\n \"extra-tall\": { label: \"Extra Tall\", value: \"extra-tall\", height: 900 },\n custom: { label: \"Custom\", value: \"custom\", height: 500 },\n} as const;\n\nexport type DropletWidgetSizeKey = keyof typeof DROPLET_WIDGET_SIZES;\n\nexport const DROPLET_CHROME_PROP_KEYS = [\n \"padding\",\n \"borderRadius\",\n \"borderWidth\",\n \"borderColor\",\n \"widgetSize\",\n \"customHeight\",\n \"placement\",\n] as const;\n\nexport type DropletChromePropKey = (typeof DROPLET_CHROME_PROP_KEYS)[number];\n\nconst CHROME_KEY_SET: ReadonlySet<string> = new Set(DROPLET_CHROME_PROP_KEYS);\n\nexport const DROPLET_CHROME_DEFAULTS: Readonly<\n Record<DropletChromePropKey, unknown>\n> = Object.freeze({\n padding: 0,\n borderRadius: \"md\",\n borderWidth: \"none\",\n borderColor: \"muted\",\n widgetSize: \"auto\",\n customHeight: \"500px\",\n placement: \"inline\",\n});\n\n/**\n * Resolve the configured chrome into an actual pixel height for the\n * iframe. Called by the renderer. If the size is \"auto\" the renderer\n * uses this as the initial height but lets `fluid:resize` override it.\n */\nexport function resolveDropletHeight(\n widgetSize: DropletWidgetSizeKey | string,\n customHeight: string | undefined,\n): number {\n if (widgetSize === \"custom\" && customHeight) {\n const parsed = parseInt(customHeight, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 500;\n }\n const preset = DROPLET_WIDGET_SIZES[widgetSize as DropletWidgetSizeKey];\n return preset ? preset.height : 400;\n}\n\n/**\n * Translatable user-facing strings emitted by `dropletChromeFields`.\n * The host injects these via `dropletChromeFields({ labels })` so the\n * package itself doesn't depend on translation infrastructure. English\n * defaults match the legacy hardcoded labels.\n */\nexport interface DropletChromeLabels {\n readonly widgetSize?: string;\n readonly widgetSizeDescription?: string;\n readonly customHeight?: string;\n readonly customHeightDescription?: string;\n readonly padding?: string;\n readonly paddingDescription?: string;\n readonly borderRadius?: string;\n readonly borderRadiusDescription?: string;\n readonly borderWidth?: string;\n readonly borderWidthDescription?: string;\n readonly borderColor?: string;\n readonly borderColorDescription?: string;\n readonly sizes?: {\n readonly auto?: string;\n readonly compact?: string;\n readonly standard?: string;\n readonly tall?: string;\n readonly extraTall?: string;\n readonly custom?: string;\n };\n}\n\nconst DEFAULT_CHROME_LABELS: Required<Omit<DropletChromeLabels, \"sizes\">> & {\n sizes: Required<NonNullable<DropletChromeLabels[\"sizes\"]>>;\n} = {\n widgetSize: \"Widget Size\",\n widgetSizeDescription:\n 'How tall the widget appears. \"Auto\" lets the widget resize itself.',\n customHeight: \"Custom Height\",\n customHeightDescription: \"Height in px (e.g. 600).\",\n padding: \"Padding\",\n paddingDescription: \"Padding around the widget.\",\n borderRadius: \"Border Radius\",\n borderRadiusDescription: \"Rounding applied to the widget container.\",\n borderWidth: \"Border Width\",\n borderWidthDescription: \"Border thickness around the widget.\",\n borderColor: \"Border Color\",\n borderColorDescription: \"Border color when border width is set.\",\n sizes: {\n auto: \"Auto\",\n compact: \"Compact\",\n standard: \"Standard\",\n tall: \"Tall\",\n extraTall: \"Extra Tall\",\n custom: \"Custom\",\n },\n};\n\nexport function dropletChromeFields(options?: {\n readonly labels?: DropletChromeLabels;\n}): PropertyField[] {\n const labels: typeof DEFAULT_CHROME_LABELS = {\n ...DEFAULT_CHROME_LABELS,\n ...options?.labels,\n sizes: { ...DEFAULT_CHROME_LABELS.sizes, ...options?.labels?.sizes },\n };\n\n const sizeOptions = [\n { label: labels.sizes.auto, value: \"auto\" },\n { label: labels.sizes.compact, value: \"compact\" },\n { label: labels.sizes.standard, value: \"standard\" },\n { label: labels.sizes.tall, value: \"tall\" },\n { label: labels.sizes.extraTall, value: \"extra-tall\" },\n { label: labels.sizes.custom, value: \"custom\" },\n ];\n\n return [\n {\n key: \"widgetSize\",\n label: labels.widgetSize,\n type: \"select\",\n description: labels.widgetSizeDescription,\n defaultValue: \"auto\",\n group: \"Design\",\n options: sizeOptions,\n },\n {\n key: \"customHeight\",\n label: labels.customHeight,\n type: \"text\",\n description: labels.customHeightDescription,\n defaultValue: \"500\",\n group: \"Design\",\n requiresKeyValue: { key: \"widgetSize\", value: \"custom\" },\n },\n getPaddingField({\n key: \"padding\",\n label: labels.padding,\n description: labels.paddingDescription,\n defaultValue: DROPLET_CHROME_DEFAULTS.padding as 0,\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: labels.borderRadius,\n description: labels.borderRadiusDescription,\n defaultValue: \"md\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: labels.borderWidth,\n description: labels.borderWidthDescription,\n defaultValue: \"none\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: labels.borderColor,\n description: labels.borderColorDescription,\n defaultValue: \"muted\",\n group: \"Design\",\n }),\n ];\n}\n\nexport function isDropletChromeKey(key: string): key is DropletChromePropKey {\n return CHROME_KEY_SET.has(key);\n}\n\nexport function splitDropletProps(props: Readonly<Record<string, unknown>>): {\n chrome: Record<DropletChromePropKey, unknown>;\n forwarded: Record<string, unknown>;\n} {\n const chrome = { ...DROPLET_CHROME_DEFAULTS } as Record<\n DropletChromePropKey,\n unknown\n >;\n const forwarded: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n if (isDropletChromeKey(key)) {\n chrome[key] = value;\n } else {\n forwarded[key] = value;\n }\n }\n return { chrome, forwarded };\n}\n\nconst VALID_PADDINGS = new Set<PaddingOptions>([0, 2, 4, 6, 8, 10]);\nconst VALID_BORDER_RADII = new Set<BorderRadiusOptions>([\n \"none\",\n \"sm\",\n \"md\",\n \"lg\",\n \"xl\",\n \"full\",\n]);\nconst VALID_BORDER_WIDTHS = new Set<BorderWidthOptions>([\n \"none\",\n \"thin\",\n \"medium\",\n \"thick\",\n]);\nconst VALID_BORDER_COLORS = new Set<ColorOptions>([\n \"primary\",\n \"secondary\",\n \"muted\",\n \"accent\",\n \"destructive\",\n]);\n\n/**\n * Validated chrome shape — every field is guaranteed to be a valid\n * option, never `unknown`. Out-of-vocabulary or missing values fall\n * back to the matching default in DROPLET_CHROME_DEFAULTS.\n */\nexport interface ParsedDropletChrome {\n readonly padding: PaddingOptions;\n readonly borderRadius: BorderRadiusOptions;\n readonly borderWidth: BorderWidthOptions;\n readonly borderColor: ColorOptions;\n readonly widgetSize: string;\n readonly customHeight: string | undefined;\n}\n\n/**\n * Parse the unvalidated chrome bag from `splitDropletProps` into a\n * typed shape with runtime-validated values. Replaces ad-hoc `as`\n * casts at the renderer call site so a stale persisted props object\n * (e.g. an enum value renamed across versions) cannot silently produce\n * an undefined Tailwind-class lookup.\n */\nexport function parseDropletChrome(\n chrome: Readonly<Record<DropletChromePropKey, unknown>>,\n): ParsedDropletChrome {\n return {\n padding: VALID_PADDINGS.has(chrome.padding as PaddingOptions)\n ? (chrome.padding as PaddingOptions)\n : 0,\n borderRadius: VALID_BORDER_RADII.has(\n chrome.borderRadius as BorderRadiusOptions,\n )\n ? (chrome.borderRadius as BorderRadiusOptions)\n : \"md\",\n borderWidth: VALID_BORDER_WIDTHS.has(\n chrome.borderWidth as BorderWidthOptions,\n )\n ? (chrome.borderWidth as BorderWidthOptions)\n : \"none\",\n borderColor: VALID_BORDER_COLORS.has(chrome.borderColor as ColorOptions)\n ? (chrome.borderColor as ColorOptions)\n : \"muted\",\n widgetSize:\n typeof chrome.widgetSize === \"string\" ? chrome.widgetSize : \"auto\",\n customHeight:\n typeof chrome.customHeight === \"string\" ? chrome.customHeight : undefined,\n };\n}\n"],"mappings":"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"}
|