@aglyn/plugins-commerce 1.0.0-beta.290 → 1.0.0-beta.292

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/README.md +4 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/cart-fulfillment.d.ts +3 -0
  4. package/src/lib/components/cart-fulfillment.js.map +1 -1
  5. package/src/lib/components/cart.js +14 -4
  6. package/src/lib/components/cart.js.map +1 -1
  7. package/src/lib/components/commerce-console-page.js +2 -1
  8. package/src/lib/components/commerce-console-page.js.map +1 -1
  9. package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
  10. package/src/lib/components/console/host-orders-card.component.js +3 -2
  11. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  12. package/src/lib/components/console/invoice-send.component.d.ts +44 -0
  13. package/src/lib/components/console/invoice-send.component.js +237 -0
  14. package/src/lib/components/console/invoice-send.component.js.map +1 -0
  15. package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
  16. package/src/lib/components/console/order-detail-dialog.component.js +9 -2
  17. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  18. package/src/lib/components/console/order-invoice.component.d.ts +9 -0
  19. package/src/lib/components/console/order-invoice.component.js +27 -2
  20. package/src/lib/components/console/order-invoice.component.js.map +1 -1
  21. package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
  22. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  23. package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
  24. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
  25. package/src/lib/components/console/pos-page.component.js +1 -1
  26. package/src/lib/components/console/pos-page.component.js.map +1 -1
  27. package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
  28. package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
  29. package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
  30. package/src/lib/components/console/product-editor-dialog.component.js +31 -5
  31. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  32. package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
  33. package/src/lib/components/console/product-register-fields.component.js +8 -197
  34. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  35. package/src/lib/components/console/products-hub-zone.component.js +6 -0
  36. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  37. package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
  38. package/src/lib/components/console/quote-editor-dialog.component.js +102 -4
  39. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
  40. package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
  41. package/src/lib/components/console/quote-pay-link.component.js +29 -3
  42. package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
  43. package/src/lib/components/console/quote-proofs.component.d.ts +11 -0
  44. package/src/lib/components/console/quote-proofs.component.js +68 -0
  45. package/src/lib/components/console/quote-proofs.component.js.map +1 -1
  46. package/src/lib/components/console/quote-send.component.d.ts +42 -0
  47. package/src/lib/components/console/quote-send.component.js +304 -0
  48. package/src/lib/components/console/quote-send.component.js.map +1 -0
  49. package/src/lib/components/console/quote-start.component.d.ts +45 -0
  50. package/src/lib/components/console/quote-start.component.js +182 -0
  51. package/src/lib/components/console/quote-start.component.js.map +1 -0
  52. package/src/lib/components/product-add-ons.d.ts +28 -0
  53. package/src/lib/components/product-add-ons.js +136 -0
  54. package/src/lib/components/product-add-ons.js.map +1 -0
  55. package/src/lib/components/product-detail.d.ts +16 -0
  56. package/src/lib/components/product-detail.js +112 -19
  57. package/src/lib/components/product-detail.js.map +1 -1
  58. package/src/lib/components/product-grid.d.ts +8 -2
  59. package/src/lib/components/product-grid.js +6 -1
  60. package/src/lib/components/product-grid.js.map +1 -1
  61. package/src/lib/components/product-options-field-body.d.ts +44 -0
  62. package/src/lib/components/product-options-field-body.js +260 -0
  63. package/src/lib/components/product-options-field-body.js.map +1 -0
  64. package/src/lib/components/product-options-field.d.ts +32 -0
  65. package/src/lib/components/product-options-field.js +128 -0
  66. package/src/lib/components/product-options-field.js.map +1 -0
  67. package/src/lib/components/quote-page-body.js +39 -20
  68. package/src/lib/components/quote-page-body.js.map +1 -1
  69. package/src/lib/model/commerce-cart.d.ts +15 -3
  70. package/src/lib/model/commerce-cart.js +10 -3
  71. package/src/lib/model/commerce-cart.js.map +1 -1
  72. package/src/lib/model/commerce-invoices.d.ts +139 -0
  73. package/src/lib/model/commerce-invoices.js +343 -0
  74. package/src/lib/model/commerce-invoices.js.map +1 -0
  75. package/src/lib/model/commerce-orders.d.ts +35 -5
  76. package/src/lib/model/commerce-orders.js +3 -0
  77. package/src/lib/model/commerce-orders.js.map +1 -1
  78. package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
  79. package/src/lib/model/commerce-pos-kiosk.js +3 -2
  80. package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
  81. package/src/lib/model/commerce-product-field.d.ts +23 -0
  82. package/src/lib/model/commerce-product-field.js +26 -0
  83. package/src/lib/model/commerce-product-field.js.map +1 -0
  84. package/src/lib/model/commerce-quote-email.d.ts +81 -0
  85. package/src/lib/model/commerce-quote-email.js +170 -0
  86. package/src/lib/model/commerce-quote-email.js.map +1 -0
  87. package/src/lib/model/commerce-quote-pay.d.ts +49 -6
  88. package/src/lib/model/commerce-quote-pay.js +69 -36
  89. package/src/lib/model/commerce-quote-pay.js.map +1 -1
  90. package/src/lib/model/commerce-quote-request.d.ts +93 -0
  91. package/src/lib/model/commerce-quote-request.js +207 -0
  92. package/src/lib/model/commerce-quote-request.js.map +1 -0
  93. package/src/lib/model/commerce-quotes.d.ts +81 -0
  94. package/src/lib/model/commerce-quotes.js +1 -1
  95. package/src/lib/model/commerce-quotes.js.map +1 -1
  96. package/src/lib/model/commerce.d.ts +18 -0
  97. package/src/lib/model/commerce.js +26 -0
  98. package/src/lib/model/commerce.js.map +1 -1
  99. package/src/lib/model/index.d.ts +3 -0
  100. package/src/lib/model/index.js +3 -0
  101. package/src/lib/model/index.js.map +1 -1
  102. package/src/lib/model/product-modifiers.d.ts +74 -4
  103. package/src/lib/model/product-modifiers.js +131 -10
  104. package/src/lib/model/product-modifiers.js.map +1 -1
  105. package/src/lib/plugin.js +19 -0
  106. package/src/lib/plugin.js.map +1 -1
  107. package/src/lib/server/billing-webhook.js +164 -98
  108. package/src/lib/server/billing-webhook.js.map +1 -1
  109. package/src/lib/server/cart-checkout.js +14 -2
  110. package/src/lib/server/cart-checkout.js.map +1 -1
  111. package/src/lib/server/cart.d.ts +5 -0
  112. package/src/lib/server/cart.js +41 -4
  113. package/src/lib/server/cart.js.map +1 -1
  114. package/src/lib/server/catalog.d.ts +6 -0
  115. package/src/lib/server/catalog.js +2 -0
  116. package/src/lib/server/catalog.js.map +1 -1
  117. package/src/lib/server/checkout-extras.js +3 -1
  118. package/src/lib/server/checkout-extras.js.map +1 -1
  119. package/src/lib/server/checkout.js +28 -3
  120. package/src/lib/server/checkout.js.map +1 -1
  121. package/src/lib/server/commerce-pdf.d.ts +5 -1
  122. package/src/lib/server/commerce-pdf.js +37 -10
  123. package/src/lib/server/commerce-pdf.js.map +1 -1
  124. package/src/lib/server/draft-order.js +23 -3
  125. package/src/lib/server/draft-order.js.map +1 -1
  126. package/src/lib/server/invoice-email.d.ts +76 -0
  127. package/src/lib/server/invoice-email.js +183 -0
  128. package/src/lib/server/invoice-email.js.map +1 -0
  129. package/src/lib/server/local-fulfillment.js +5 -1
  130. package/src/lib/server/local-fulfillment.js.map +1 -1
  131. package/src/lib/server/order-invoice-send.d.ts +43 -0
  132. package/src/lib/server/order-invoice-send.js +218 -0
  133. package/src/lib/server/order-invoice-send.js.map +1 -0
  134. package/src/lib/server/order-status.d.ts +11 -0
  135. package/src/lib/server/order-status.js +44 -8
  136. package/src/lib/server/order-status.js.map +1 -1
  137. package/src/lib/server/person-eraser.d.ts +3 -2
  138. package/src/lib/server/person-eraser.js +25 -6
  139. package/src/lib/server/person-eraser.js.map +1 -1
  140. package/src/lib/server/pos-offline-sync.js +1 -1
  141. package/src/lib/server/pos-offline-sync.js.map +1 -1
  142. package/src/lib/server/pos-order.js +1 -1
  143. package/src/lib/server/pos-order.js.map +1 -1
  144. package/src/lib/server/product.d.ts +26 -0
  145. package/src/lib/server/product.js +18 -0
  146. package/src/lib/server/product.js.map +1 -1
  147. package/src/lib/server/products-write.js +5 -2
  148. package/src/lib/server/products-write.js.map +1 -1
  149. package/src/lib/server/quote-email.d.ts +85 -0
  150. package/src/lib/server/quote-email.js +211 -0
  151. package/src/lib/server/quote-email.js.map +1 -0
  152. package/src/lib/server/quote-link-token.d.ts +11 -0
  153. package/src/lib/server/quote-link-token.js +21 -0
  154. package/src/lib/server/quote-link-token.js.map +1 -1
  155. package/src/lib/server/quote-pay.d.ts +8 -3
  156. package/src/lib/server/quote-pay.js +105 -43
  157. package/src/lib/server/quote-pay.js.map +1 -1
  158. package/src/lib/server/quote.d.ts +11 -0
  159. package/src/lib/server/quote.js +309 -12
  160. package/src/lib/server/quote.js.map +1 -1
  161. package/src/lib/server/site-page-resolver.js +2 -1
  162. package/src/lib/server/site-page-resolver.js.map +1 -1
  163. package/src/lib/server.js +5 -0
  164. package/src/lib/server.js.map +1 -1
  165. package/src/lib/site.js +8 -0
  166. package/src/lib/site.js.map +1 -1
  167. package/src/lib/tenant-emails.js +243 -0
  168. package/src/lib/tenant-emails.js.map +1 -1
  169. package/src/lib/utils/add-to-cart.d.ts +8 -0
  170. package/src/lib/utils/add-to-cart.js +10 -2
  171. package/src/lib/utils/add-to-cart.js.map +1 -1
  172. package/src/lib/utils/commerce-document-pdf.d.ts +5 -0
  173. package/src/lib/utils/commerce-document-pdf.js +6 -1
  174. package/src/lib/utils/commerce-document-pdf.js.map +1 -1
  175. package/src/lib/utils/invoice-email-content.d.ts +45 -0
  176. package/src/lib/utils/invoice-email-content.js +126 -0
  177. package/src/lib/utils/invoice-email-content.js.map +1 -0
  178. package/src/lib/utils/order-invoice.d.ts +1 -1
  179. package/src/lib/utils/order-invoice.js +14 -6
  180. package/src/lib/utils/order-invoice.js.map +1 -1
  181. package/src/lib/utils/quote-email-content.d.ts +63 -0
  182. package/src/lib/utils/quote-email-content.js +157 -0
  183. package/src/lib/utils/quote-email-content.js.map +1 -0
@@ -0,0 +1,260 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import * as Aglyn from "@aglyn/aglyn";
20
+ import { formFieldDataKey } from "../model/commerce-product-field.js";
21
+ import { encodeProductFieldValue, productFieldLabel } from "../model/commerce-quote-request.js";
22
+ import Box from "@mui/material/Box";
23
+ import FormControl from "@mui/material/FormControl";
24
+ import FormHelperText from "@mui/material/FormHelperText";
25
+ import FormLabel from "@mui/material/FormLabel";
26
+ import MenuItem from "@mui/material/MenuItem";
27
+ import Stack from "@mui/material/Stack";
28
+ import TextField from "@mui/material/TextField";
29
+ import { forwardRef, useEffect, useId, useMemo, useRef, useState } from "react";
30
+ /** The most products the picker lists. */ const PICKER_LIMIT = 100;
31
+ /** The variant whose options are exactly the ones picked, else none. */ export function variantForOptions(detail, picked) {
32
+ var _ref, _ref1;
33
+ var _detail_variants_;
34
+ if (!detail.options.length) return (_ref = (_detail_variants_ = detail.variants[0]) == null ? void 0 : _detail_variants_.id) != null ? _ref : null;
35
+ if (detail.options.some((option)=>!picked[option.name])) return null;
36
+ const match = detail.variants.find((variant)=>detail.options.every((option)=>{
37
+ var _variant_options;
38
+ return ((_variant_options = variant.options) == null ? void 0 : _variant_options[option.name]) === picked[option.name];
39
+ }));
40
+ return (_ref1 = match == null ? void 0 : match.id) != null ? _ref1 : null;
41
+ }
42
+ /**
43
+ * The Product and options field's body (AGL-4098): a picker of the store's
44
+ * products (or the one the author chose), then one picker per option. It
45
+ * posts the readable line under its name and the exact choice beside it; a
46
+ * real, invisible input carries the line, so a required field with nothing
47
+ * picked stops the form like any required field.
48
+ *
49
+ * On the canvas and in Preview it draws with the store's products but sends
50
+ * nothing, like every form field there.
51
+ */ const ProductOptionsFieldBody = /*#__PURE__*/ forwardRef((props, ref)=>{
52
+ var _ref, _ref1;
53
+ var _products_find;
54
+ const { name: rawName, label, productId: fixedProductId, required } = props, rest = _object_without_properties_loose(props, [
55
+ "name",
56
+ "label",
57
+ "productId",
58
+ "required"
59
+ ]);
60
+ const name = String(rawName != null ? rawName : '').trim() || 'product';
61
+ const { hostId } = Aglyn.useSite();
62
+ const inputId = useId();
63
+ const answerRef = useRef(null);
64
+ const [products, setProducts] = useState(null);
65
+ const [productId, setProductId] = useState(fixedProductId != null ? fixedProductId : '');
66
+ const [detail, setDetail] = useState(null);
67
+ const [picked, setPicked] = useState({});
68
+ // The store's products, or the one the author chose.
69
+ useEffect(()=>{
70
+ if (!hostId) return;
71
+ let active = true;
72
+ const query = new URLSearchParams(_extends({
73
+ hostId
74
+ }, fixedProductId ? {
75
+ ids: fixedProductId
76
+ } : {
77
+ sort: 'name',
78
+ limit: String(PICKER_LIMIT)
79
+ }));
80
+ void fetch(`/api/commerce/catalog?${query.toString()}`).then((response)=>response.ok ? response.json() : {
81
+ items: []
82
+ }).then((payload)=>{
83
+ var _ref;
84
+ var _items_;
85
+ if (!active) return;
86
+ const items = Array.isArray(payload == null ? void 0 : payload.items) ? payload.items : [];
87
+ setProducts(items);
88
+ if (fixedProductId) setProductId((_ref = (_items_ = items[0]) == null ? void 0 : _items_.id) != null ? _ref : '');
89
+ }).catch(()=>active && setProducts([]));
90
+ return ()=>{
91
+ active = false;
92
+ };
93
+ }, [
94
+ hostId,
95
+ fixedProductId
96
+ ]);
97
+ // The chosen product's options.
98
+ const slug = (_ref = products == null ? void 0 : (_products_find = products.find((product)=>product.id === productId)) == null ? void 0 : _products_find.slug) != null ? _ref : '';
99
+ useEffect(()=>{
100
+ setDetail(null);
101
+ setPicked({});
102
+ if (!hostId || !slug) return;
103
+ let active = true;
104
+ void fetch(`/api/commerce/product?hostId=${encodeURIComponent(hostId)}&slug=${encodeURIComponent(slug)}`).then((response)=>response.ok ? response.json() : null).then((payload)=>{
105
+ var _product_options, _product_variants;
106
+ if (!active || !(payload == null ? void 0 : payload.product)) return;
107
+ const product = payload.product;
108
+ setDetail(_extends({}, product, {
109
+ options: (_product_options = product.options) != null ? _product_options : [],
110
+ variants: (_product_variants = product.variants) != null ? _product_variants : []
111
+ }));
112
+ }).catch(()=>undefined);
113
+ return ()=>{
114
+ active = false;
115
+ };
116
+ }, [
117
+ hostId,
118
+ slug
119
+ ]);
120
+ const variantId = detail ? variantForOptions(detail, picked) : null;
121
+ const complete = Boolean(detail && variantId);
122
+ const answer = complete && detail ? productFieldLabel(detail.name, picked) : '';
123
+ const value = complete && detail ? encodeProductFieldValue(_extends({
124
+ productId: detail.id
125
+ }, variantId && variantId !== 'default' ? {
126
+ variantId
127
+ } : {}, Object.keys(picked).length ? {
128
+ options: picked
129
+ } : {})) : '';
130
+ // Every change is announced, as a typed answer is, to anything listening on the form.
131
+ const announced = useRef(answer);
132
+ useEffect(()=>{
133
+ var _answerRef_current;
134
+ if (announced.current === answer) return;
135
+ announced.current = answer;
136
+ (_answerRef_current = answerRef.current) == null ? void 0 : _answerRef_current.dispatchEvent(new Event('change', {
137
+ bubbles: true
138
+ }));
139
+ }, [
140
+ answer
141
+ ]);
142
+ const listed = useMemo(()=>products != null ? products : [], [
143
+ products
144
+ ]);
145
+ // On the canvas there is no site to list from: it says what the visitor will pick from.
146
+ const placeholder = !hostId ? 'Your store’s products' : products === null ? 'Loading products…' : listed.length ? 'Choose a product' : 'No products to choose from yet';
147
+ return /*#__PURE__*/ _jsxs(FormControl, _extends({
148
+ ref: ref,
149
+ fullWidth: true,
150
+ required: Boolean(required)
151
+ }, rest, {
152
+ children: [
153
+ /*#__PURE__*/ _jsx(FormLabel, {
154
+ htmlFor: inputId,
155
+ children: label || 'Product'
156
+ }),
157
+ /*#__PURE__*/ _jsxs(Stack, {
158
+ spacing: 1.5,
159
+ sx: {
160
+ mt: 1,
161
+ position: 'relative'
162
+ },
163
+ children: [
164
+ fixedProductId ? null : /*#__PURE__*/ _jsx(TextField, {
165
+ id: inputId,
166
+ select: true,
167
+ size: "small",
168
+ fullWidth: true,
169
+ value: productId,
170
+ onChange: (event)=>setProductId(event.target.value),
171
+ disabled: !listed.length,
172
+ slotProps: {
173
+ select: {
174
+ displayEmpty: true,
175
+ renderValue: (selected)=>{
176
+ var _ref;
177
+ var _listed_find;
178
+ return (_ref = (_listed_find = listed.find((product)=>product.id === selected)) == null ? void 0 : _listed_find.name) != null ? _ref : /*#__PURE__*/ _jsx(Box, {
179
+ component: "span",
180
+ sx: {
181
+ color: 'text.secondary'
182
+ },
183
+ children: placeholder
184
+ });
185
+ }
186
+ }
187
+ },
188
+ children: listed.map((product)=>/*#__PURE__*/ _jsx(MenuItem, {
189
+ value: product.id,
190
+ children: product.name
191
+ }, product.id))
192
+ }),
193
+ fixedProductId && detail ? /*#__PURE__*/ _jsx(FormHelperText, {
194
+ sx: {
195
+ mx: 0
196
+ },
197
+ children: detail.name
198
+ }) : null,
199
+ ((_ref1 = detail == null ? void 0 : detail.options) != null ? _ref1 : []).map((option)=>{
200
+ var _picked_option_name;
201
+ return /*#__PURE__*/ _jsx(TextField, {
202
+ select: true,
203
+ size: "small",
204
+ fullWidth: true,
205
+ label: option.name,
206
+ value: (_picked_option_name = picked[option.name]) != null ? _picked_option_name : '',
207
+ onChange: (event)=>setPicked((current)=>_extends({}, current, {
208
+ [option.name]: event.target.value
209
+ })),
210
+ children: option.values.map((choice)=>/*#__PURE__*/ _jsx(MenuItem, {
211
+ value: choice,
212
+ children: choice
213
+ }, choice))
214
+ }, option.name);
215
+ }),
216
+ detail && Object.keys(picked).length === detail.options.length && detail.options.length && !variantId ? /*#__PURE__*/ _jsx(FormHelperText, {
217
+ error: true,
218
+ sx: {
219
+ mx: 0
220
+ },
221
+ children: 'That combination isn’t one we offer. Pick another.'
222
+ }) : null,
223
+ /*#__PURE__*/ _jsx(Box, {
224
+ component: "input",
225
+ ref: answerRef,
226
+ type: "text",
227
+ name: name,
228
+ value: answer,
229
+ required: Boolean(required),
230
+ onChange: ()=>undefined,
231
+ tabIndex: -1,
232
+ "aria-hidden": "true",
233
+ autoComplete: "off",
234
+ sx: {
235
+ position: 'absolute',
236
+ left: 0,
237
+ bottom: 0,
238
+ width: 1,
239
+ height: '1px',
240
+ p: 0,
241
+ border: 0,
242
+ opacity: 0,
243
+ pointerEvents: 'none'
244
+ }
245
+ }),
246
+ value ? /*#__PURE__*/ _jsx("input", {
247
+ type: "hidden",
248
+ name: formFieldDataKey(name),
249
+ value: value,
250
+ readOnly: true
251
+ }) : null
252
+ ]
253
+ })
254
+ ]
255
+ }));
256
+ });
257
+ ProductOptionsFieldBody.displayName = 'AglynProductOptionsFieldBody';
258
+ export default ProductOptionsFieldBody;
259
+
260
+ //# sourceMappingURL=product-options-field-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-options-field-body.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { formFieldDataKey } from '../model/commerce-product-field'\nimport { encodeProductFieldValue, productFieldLabel } from '../model/commerce-quote-request'\nimport Box from '@mui/material/Box'\nimport FormControl from '@mui/material/FormControl'\nimport FormHelperText from '@mui/material/FormHelperText'\nimport FormLabel from '@mui/material/FormLabel'\nimport MenuItem from '@mui/material/MenuItem'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\nimport { forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport type { ProductOptionsFieldProps } from './product-options-field'\n\n/** A product as the store's public catalog lists it. */\ninterface ListedProduct {\n id: string\n name: string\n slug: string\n}\n\n/** A product as its public page reads it: its options and the variants they make. */\ninterface ProductDetail {\n id: string\n name: string\n options: Array<{ name: string; values: string[] }>\n variants: Array<{ id: string; options?: Record<string, string> }>\n}\n\n/** The most products the picker lists. */\nconst PICKER_LIMIT = 100\n\n/** The variant whose options are exactly the ones picked, else none. */\nexport function variantForOptions(\n detail: Pick<ProductDetail, 'variants' | 'options'>,\n picked: Readonly<Record<string, string>>,\n): string | null {\n if (!detail.options.length) return detail.variants[0]?.id ?? null\n if (detail.options.some((option) => !picked[option.name])) return null\n const match = detail.variants.find((variant) =>\n detail.options.every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n return match?.id ?? null\n}\n\n/**\n * The Product and options field's body (AGL-4098): a picker of the store's\n * products (or the one the author chose), then one picker per option. It\n * posts the readable line under its name and the exact choice beside it; a\n * real, invisible input carries the line, so a required field with nothing\n * picked stops the form like any required field.\n *\n * On the canvas and in Preview it draws with the store's products but sends\n * nothing, like every form field there.\n */\nconst ProductOptionsFieldBody = forwardRef<HTMLDivElement, ProductOptionsFieldProps>((props, ref) => {\n const { name: rawName, label, productId: fixedProductId, required, ...rest } = props\n const name = String(rawName ?? '').trim() || 'product'\n const { hostId } = Aglyn.useSite()\n const inputId = useId()\n const answerRef = useRef<HTMLInputElement>(null)\n const [products, setProducts] = useState<ListedProduct[] | null>(null)\n const [productId, setProductId] = useState(fixedProductId ?? '')\n const [detail, setDetail] = useState<ProductDetail | null>(null)\n const [picked, setPicked] = useState<Record<string, string>>({})\n\n // The store's products, or the one the author chose.\n useEffect(() => {\n if (!hostId) return\n let active = true\n const query = new URLSearchParams({\n hostId,\n ...(fixedProductId ? { ids: fixedProductId } : { sort: 'name', limit: String(PICKER_LIMIT) }),\n })\n void fetch(`/api/commerce/catalog?${query.toString()}`)\n .then((response) => (response.ok ? response.json() : { items: [] }))\n .then((payload) => {\n if (!active) return\n const items = Array.isArray(payload?.items) ? (payload.items as ListedProduct[]) : []\n setProducts(items)\n if (fixedProductId) setProductId(items[0]?.id ?? '')\n })\n .catch(() => active && setProducts([]))\n return () => {\n active = false\n }\n }, [hostId, fixedProductId])\n\n // The chosen product's options.\n const slug = products?.find((product) => product.id === productId)?.slug ?? ''\n useEffect(() => {\n setDetail(null)\n setPicked({})\n if (!hostId || !slug) return\n let active = true\n void fetch(`/api/commerce/product?hostId=${encodeURIComponent(hostId)}&slug=${encodeURIComponent(slug)}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((payload) => {\n if (!active || !payload?.product) return\n const product = payload.product as ProductDetail\n setDetail({ ...product, options: product.options ?? [], variants: product.variants ?? [] })\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [hostId, slug])\n\n const variantId = detail ? variantForOptions(detail, picked) : null\n const complete = Boolean(detail && variantId)\n const answer = complete && detail ? productFieldLabel(detail.name, picked) : ''\n const value = complete && detail\n ? encodeProductFieldValue({\n productId: detail.id,\n ...(variantId && variantId !== 'default' ? { variantId } : {}),\n ...(Object.keys(picked).length ? { options: picked } : {}),\n // Add-ons are chosen on the storefront from slice 8 on: they ride here as `modifiers`.\n })\n : ''\n\n // Every change is announced, as a typed answer is, to anything listening on the form.\n const announced = useRef(answer)\n useEffect(() => {\n if (announced.current === answer) return\n announced.current = answer\n answerRef.current?.dispatchEvent(new Event('change', { bubbles: true }))\n }, [answer])\n\n const listed = useMemo(() => products ?? [], [products])\n // On the canvas there is no site to list from: it says what the visitor will pick from.\n const placeholder = !hostId\n ? 'Your store’s products'\n : products === null\n ? 'Loading products…'\n : listed.length\n ? 'Choose a product'\n : 'No products to choose from yet'\n\n return (\n <FormControl ref={ref} fullWidth required={Boolean(required)} {...rest}>\n <FormLabel htmlFor={inputId}>{label || 'Product'}</FormLabel>\n <Stack spacing={1.5} sx={{ mt: 1, position: 'relative' }}>\n {fixedProductId ? null : (\n <TextField\n id={inputId}\n select\n size=\"small\"\n fullWidth\n value={productId}\n onChange={(event) => setProductId(event.target.value)}\n disabled={!listed.length}\n slotProps={{\n select: {\n displayEmpty: true,\n renderValue: (selected: unknown) =>\n listed.find((product) => product.id === selected)?.name ?? (\n <Box component=\"span\" sx={{ color: 'text.secondary' }}>\n {placeholder}\n </Box>\n ),\n },\n }}\n >\n {listed.map((product) => (\n <MenuItem key={product.id} value={product.id}>\n {product.name}\n </MenuItem>\n ))}\n </TextField>\n )}\n {fixedProductId && detail ? <FormHelperText sx={{ mx: 0 }}>{detail.name}</FormHelperText> : null}\n {(detail?.options ?? []).map((option) => (\n <TextField\n key={option.name}\n select\n size=\"small\"\n fullWidth\n label={option.name}\n value={picked[option.name] ?? ''}\n onChange={(event) => setPicked((current) => ({ ...current, [option.name]: event.target.value }))}\n >\n {option.values.map((choice) => (\n <MenuItem key={choice} value={choice}>\n {choice}\n </MenuItem>\n ))}\n </TextField>\n ))}\n {detail && Object.keys(picked).length === detail.options.length && detail.options.length && !variantId ? (\n <FormHelperText error sx={{ mx: 0 }}>\n {'That combination isn’t one we offer. Pick another.'}\n </FormHelperText>\n ) : null}\n {/* The answer the form sends and the browser checks, and the exact choice beside it. */}\n <Box\n component=\"input\"\n ref={answerRef}\n type=\"text\"\n name={name}\n value={answer}\n required={Boolean(required)}\n onChange={() => undefined}\n tabIndex={-1}\n aria-hidden=\"true\"\n autoComplete=\"off\"\n sx={{ position: 'absolute', left: 0, bottom: 0, width: 1, height: '1px', p: 0, border: 0, opacity: 0, pointerEvents: 'none' }}\n />\n {value ? <input type=\"hidden\" name={formFieldDataKey(name)} value={value} readOnly /> : null}\n </Stack>\n </FormControl>\n )\n})\nProductOptionsFieldBody.displayName = 'AglynProductOptionsFieldBody'\n\nexport default ProductOptionsFieldBody\n"],"names":["Aglyn","formFieldDataKey","encodeProductFieldValue","productFieldLabel","Box","FormControl","FormHelperText","FormLabel","MenuItem","Stack","TextField","forwardRef","useEffect","useId","useMemo","useRef","useState","PICKER_LIMIT","variantForOptions","detail","picked","options","length","variants","id","some","option","name","match","find","variant","every","ProductOptionsFieldBody","props","ref","products","rawName","label","productId","fixedProductId","required","rest","String","trim","hostId","useSite","inputId","answerRef","setProducts","setProductId","setDetail","setPicked","active","query","URLSearchParams","ids","sort","limit","fetch","toString","then","response","ok","json","items","payload","Array","isArray","catch","slug","product","encodeURIComponent","undefined","variantId","complete","Boolean","answer","value","Object","keys","announced","current","dispatchEvent","Event","bubbles","listed","placeholder","fullWidth","htmlFor","spacing","sx","mt","position","select","size","onChange","event","target","disabled","slotProps","displayEmpty","renderValue","selected","component","color","map","mx","values","choice","error","type","tabIndex","aria-hidden","autoComplete","left","bottom","width","height","p","border","opacity","pointerEvents","input","readOnly","displayName"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import * as Aglyn from '@aglyn/aglyn';
18
+ export declare const PRODUCT_OPTIONS_FIELD_ID: Aglyn.ComponentId;
19
+ export interface ProductOptionsFieldProps {
20
+ /** What the product and options are sent as. */
21
+ name?: string;
22
+ /** The question, above the picker. */
23
+ label?: string;
24
+ /** One product to ask about; empty lets the person pick from the store. */
25
+ productId?: string;
26
+ required?: boolean;
27
+ }
28
+ /** A product and its options, picked from the store; sent in words. */
29
+ declare const ProductOptionsField: import("react").ForwardRefExoticComponent<ProductOptionsFieldProps & import("react").RefAttributes<HTMLDivElement>>;
30
+ export declare const schema: Aglyn.ComponentSchema<ProductOptionsFieldProps>;
31
+ export declare const presets: Aglyn.PresetSchema[];
32
+ export default ProductOptionsField;
@@ -0,0 +1,128 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import * as Aglyn from "@aglyn/aglyn";
19
+ import { mdiPackageVariantClosed } from "@aglyn/shared-data-mdi";
20
+ import Box from "@mui/material/Box";
21
+ import CircularProgress from "@mui/material/CircularProgress";
22
+ import { forwardRef, lazy, Suspense } from "react";
23
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
24
+ import { generatePresetId } from "../utils/generate-preset-id.js";
25
+ /*
26
+ * THE PRODUCT AND OPTIONS FIELD (AGL-4098): a form field that asks which of
27
+ * the store's products, and which of its options, a person wants — on a
28
+ * "Request a quote" form, say.
29
+ *
30
+ * It lives in Commerce because the products are Commerce's, and it is a form
31
+ * field like any other: it knows nothing about the form around it and posts
32
+ * plain strings under its name. Its answer is the product and options in
33
+ * words (`Vinyl banner — 3 × 6 ft / Matte`); beside it, under `{name}__data`,
34
+ * it posts the exact product, variant and options for this plugin to read
35
+ * back when a quote is started from the request (`parseProductFieldValue`).
36
+ * The forms plugin keeps that value apart from the answers a person reads.
37
+ *
38
+ * This shell is what a page loads until the field draws; the picker is a
39
+ * chunk of its own. The component id is persisted in documents; never rename
40
+ * it. The core's form reader restates it (`FORM_PLUGIN_ANSWER_ELEMENTS`).
41
+ */ export const PRODUCT_OPTIONS_FIELD_ID = 'product-options-field';
42
+ const ProductOptionsFieldBody = /*#__PURE__*/ lazy(()=>import("./product-options-field-body.js"));
43
+ /** A product and its options, picked from the store; sent in words. */ const ProductOptionsField = /*#__PURE__*/ forwardRef((props, ref)=>/*#__PURE__*/ _jsx(Suspense, {
44
+ fallback: /*#__PURE__*/ _jsx(Box, {
45
+ sx: {
46
+ py: 1
47
+ },
48
+ children: /*#__PURE__*/ _jsx(CircularProgress, {
49
+ size: 20,
50
+ "aria-label": "Loading the product field"
51
+ })
52
+ }),
53
+ children: /*#__PURE__*/ _jsx(ProductOptionsFieldBody, _extends({
54
+ ref: ref
55
+ }, props))
56
+ }));
57
+ ProductOptionsField.displayName = 'AglynProductOptionsField';
58
+ export const schema = {
59
+ $id: PRODUCT_OPTIONS_FIELD_ID,
60
+ pluginId: BUNDLE_ID,
61
+ displayName: 'Product and options',
62
+ description: 'A form field: the person picks one of your products and its options, such as size and finish. It arrives with the submission, and a quote started from it begins with that product.',
63
+ category: Aglyn.ComponentCategory.FORMS,
64
+ icon: {
65
+ path: mdiPackageVariantClosed.path,
66
+ sx: {
67
+ color: 'info.main'
68
+ }
69
+ },
70
+ flags: {
71
+ selfClosing: Aglyn.FEATURE_FLAG.ENABLED
72
+ },
73
+ attributes: [
74
+ {
75
+ name: 'label',
76
+ label: 'Label',
77
+ description: 'The question above the picker, such as “What would you like priced?”.',
78
+ component: Aglyn.FieldComponentType.TEXT_FIELD
79
+ },
80
+ {
81
+ name: 'name',
82
+ label: 'Field name',
83
+ description: 'The name the choice is listed under in the Inbox and exports.',
84
+ component: Aglyn.FieldComponentType.TEXT_FIELD
85
+ },
86
+ {
87
+ name: 'productId',
88
+ label: 'Product',
89
+ description: 'Ask about one product only: the person picks its options. Leave it empty to let them pick from your store.',
90
+ component: Aglyn.FieldComponentType.PRODUCT_SELECT
91
+ },
92
+ {
93
+ name: 'required',
94
+ label: 'Required?',
95
+ description: 'The form can’t be sent until a product (and each of its options) is picked.',
96
+ component: Aglyn.FieldComponentType.SWITCH
97
+ }
98
+ ]
99
+ };
100
+ export const presets = [
101
+ {
102
+ $id: generatePresetId(PRODUCT_OPTIONS_FIELD_ID),
103
+ type: Aglyn.NodeType.PRESET,
104
+ displayName: 'Product and options',
105
+ pluginId: BUNDLE_ID,
106
+ description: 'A product from your store and its options, for a quote request',
107
+ category: Aglyn.ComponentCategory.FORMS,
108
+ icon: {
109
+ path: mdiPackageVariantClosed.path,
110
+ sx: {
111
+ color: 'info.main'
112
+ }
113
+ },
114
+ data: {
115
+ $id: null,
116
+ componentId: PRODUCT_OPTIONS_FIELD_ID,
117
+ pluginId: BUNDLE_ID,
118
+ props: {
119
+ name: 'product',
120
+ label: 'Product',
121
+ required: true
122
+ }
123
+ }
124
+ }
125
+ ];
126
+ export default ProductOptionsField;
127
+
128
+ //# sourceMappingURL=product-options-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-options-field.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiPackageVariantClosed } from '@aglyn/shared-data-mdi'\nimport Box from '@mui/material/Box'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport { forwardRef, lazy, Suspense } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/*\n * THE PRODUCT AND OPTIONS FIELD (AGL-4098): a form field that asks which of\n * the store's products, and which of its options, a person wants — on a\n * \"Request a quote\" form, say.\n *\n * It lives in Commerce because the products are Commerce's, and it is a form\n * field like any other: it knows nothing about the form around it and posts\n * plain strings under its name. Its answer is the product and options in\n * words (`Vinyl banner — 3 × 6 ft / Matte`); beside it, under `{name}__data`,\n * it posts the exact product, variant and options for this plugin to read\n * back when a quote is started from the request (`parseProductFieldValue`).\n * The forms plugin keeps that value apart from the answers a person reads.\n *\n * This shell is what a page loads until the field draws; the picker is a\n * chunk of its own. The component id is persisted in documents; never rename\n * it. The core's form reader restates it (`FORM_PLUGIN_ANSWER_ELEMENTS`).\n */\nexport const PRODUCT_OPTIONS_FIELD_ID: Aglyn.ComponentId = 'product-options-field'\n\nexport interface ProductOptionsFieldProps {\n /** What the product and options are sent as. */\n name?: string\n /** The question, above the picker. */\n label?: string\n /** One product to ask about; empty lets the person pick from the store. */\n productId?: string\n required?: boolean\n}\n\nconst ProductOptionsFieldBody = lazy(() => import('./product-options-field-body'))\n\n/** A product and its options, picked from the store; sent in words. */\nconst ProductOptionsField = forwardRef<HTMLDivElement, ProductOptionsFieldProps>((props, ref) => (\n <Suspense\n fallback={\n <Box sx={{ py: 1 }}>\n <CircularProgress size={20} aria-label=\"Loading the product field\" />\n </Box>\n }\n >\n <ProductOptionsFieldBody ref={ref} {...props} />\n </Suspense>\n))\nProductOptionsField.displayName = 'AglynProductOptionsField'\n\nexport const schema: Aglyn.ComponentSchema<ProductOptionsFieldProps> = {\n $id: PRODUCT_OPTIONS_FIELD_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product and options',\n description:\n 'A form field: the person picks one of your products and its options, such as size and finish. It arrives with the submission, and a quote started from it begins with that product.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: 'info.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'label',\n label: 'Label',\n description: 'The question above the picker, such as “What would you like priced?”.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'name',\n label: 'Field name',\n description: 'The name the choice is listed under in the Inbox and exports.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'productId',\n label: 'Product',\n description: 'Ask about one product only: the person picks its options. Leave it empty to let them pick from your store.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'required',\n label: 'Required?',\n description: 'The form can’t be sent until a product (and each of its options) is picked.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(PRODUCT_OPTIONS_FIELD_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product and options',\n pluginId: BUNDLE_ID,\n description: 'A product from your store and its options, for a quote request',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: PRODUCT_OPTIONS_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { name: 'product', label: 'Product', required: true },\n },\n },\n]\n\nexport default ProductOptionsField\n"],"names":["Aglyn","mdiPackageVariantClosed","Box","CircularProgress","forwardRef","lazy","Suspense","BUNDLE_ID","generatePresetId","PRODUCT_OPTIONS_FIELD_ID","ProductOptionsFieldBody","ProductOptionsField","props","ref","fallback","sx","py","size","aria-label","displayName","schema","$id","pluginId","description","category","ComponentCategory","FORMS","icon","path","color","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD","PRODUCT_SELECT","SWITCH","presets","type","NodeType","PRESET","data","componentId","required"],"mappings":";;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,uBAAuB,QAAQ,yBAAwB;AAChE,OAAOC,SAAS,oBAAmB;AACnC,OAAOC,sBAAsB,iCAAgC;AAC7D,SAASC,UAAU,EAAEC,IAAI,EAAEC,QAAQ,QAAQ,QAAO;AAClD,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,gBAAgB,QAAQ,iCAA6B;AAE9D;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,2BAA8C,wBAAuB;AAYlF,MAAMC,wCAA0BL,KAAK,IAAM,MAAM,CAAC;AAElD,qEAAqE,GACrE,MAAMM,oCAAsBP,WAAqD,CAACQ,OAAOC,oBACvF,KAACP;QACCQ,wBACE,KAACZ;YAAIa,IAAI;gBAAEC,IAAI;YAAE;sBACf,cAAA,KAACb;gBAAiBc,MAAM;gBAAIC,cAAW;;;kBAI3C,cAAA,KAACR;YAAwBG,KAAKA;WAASD;;AAG3CD,oBAAoBQ,WAAW,GAAG;AAElC,OAAO,MAAMC,SAA0D;IACrEC,KAAKZ;IACLa,UAAUf;IACVY,aAAa;IACbI,aACE;IACFC,UAAUxB,MAAMyB,iBAAiB,CAACC,KAAK;IACvCC,MAAM;QAAEC,MAAM3B,wBAAwB2B,IAAI;QAAEb,IAAI;YAAEc,OAAO;QAAY;IAAE;IACvEC,OAAO;QAAEC,aAAa/B,MAAMgC,YAAY,CAACC,OAAO;IAAC;IACjDC,YAAY;QACV;YACEC,MAAM;YACNC,OAAO;YACPb,aAAa;YACbc,WAAWrC,MAAMsC,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEJ,MAAM;YACNC,OAAO;YACPb,aAAa;YACbc,WAAWrC,MAAMsC,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEJ,MAAM;YACNC,OAAO;YACPb,aAAa;YACbc,WAAWrC,MAAMsC,kBAAkB,CAACE,cAAc;QACpD;QACA;YACEL,MAAM;YACNC,OAAO;YACPb,aAAa;YACbc,WAAWrC,MAAMsC,kBAAkB,CAACG,MAAM;QAC5C;KACD;AACH,EAAC;AAED,OAAO,MAAMC,UAAgC;IAC3C;QACErB,KAAKb,iBAAiBC;QACtBkC,MAAM3C,MAAM4C,QAAQ,CAACC,MAAM;QAC3B1B,aAAa;QACbG,UAAUf;QACVgB,aAAa;QACbC,UAAUxB,MAAMyB,iBAAiB,CAACC,KAAK;QACvCC,MAAM;YAAEC,MAAM3B,wBAAwB2B,IAAI;YAAEb,IAAI;gBAAEc,OAAO;YAAY;QAAE;QACvEiB,MAAM;YACJzB,KAAK;YACL0B,aAAatC;YACba,UAAUf;YACVK,OAAO;gBAAEuB,MAAM;gBAAWC,OAAO;gBAAWY,UAAU;YAAK;QAC7D;IACF;CACD,CAAA;AAED,eAAerC,oBAAmB"}