pricing-renderer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +142 -25
  3. package/THIRD_PARTY_NOTICES.md +84 -6
  4. package/custom-elements.json +233 -3
  5. package/dist/core/apply-selection.d.ts +8 -0
  6. package/dist/core/apply-selection.d.ts.map +1 -0
  7. package/dist/core/apply-selection.js +76 -0
  8. package/dist/core/apply-selection.js.map +1 -0
  9. package/dist/core/breakdown.d.ts +13 -0
  10. package/dist/core/breakdown.d.ts.map +1 -0
  11. package/dist/core/breakdown.js +56 -0
  12. package/dist/core/breakdown.js.map +1 -0
  13. package/dist/core/compare-sides.d.ts +16 -0
  14. package/dist/core/compare-sides.d.ts.map +1 -0
  15. package/dist/core/compare-sides.js +73 -0
  16. package/dist/core/compare-sides.js.map +1 -0
  17. package/dist/core/compare-values.d.ts +13 -0
  18. package/dist/core/compare-values.d.ts.map +1 -0
  19. package/dist/core/compare-values.js +100 -0
  20. package/dist/core/compare-values.js.map +1 -0
  21. package/dist/core/compare.d.ts +7 -0
  22. package/dist/core/compare.d.ts.map +1 -0
  23. package/dist/core/compare.js +301 -0
  24. package/dist/core/compare.js.map +1 -0
  25. package/dist/core/expression.js +2 -2
  26. package/dist/core/expression.js.map +1 -1
  27. package/dist/core/i18n.d.ts.map +1 -1
  28. package/dist/core/i18n.js +136 -2
  29. package/dist/core/i18n.js.map +1 -1
  30. package/dist/core/index.d.ts +9 -2
  31. package/dist/core/index.d.ts.map +1 -1
  32. package/dist/core/index.js +6 -1
  33. package/dist/core/index.js.map +1 -1
  34. package/dist/core/normalize.d.ts.map +1 -1
  35. package/dist/core/normalize.js +70 -9
  36. package/dist/core/normalize.js.map +1 -1
  37. package/dist/core/plan-relation.d.ts +8 -0
  38. package/dist/core/plan-relation.d.ts.map +1 -0
  39. package/dist/core/plan-relation.js +15 -0
  40. package/dist/core/plan-relation.js.map +1 -0
  41. package/dist/core/price-stabilizer.d.ts +3 -0
  42. package/dist/core/price-stabilizer.d.ts.map +1 -0
  43. package/dist/core/price-stabilizer.js +73 -0
  44. package/dist/core/price-stabilizer.js.map +1 -0
  45. package/dist/core/resolve.d.ts +7 -1
  46. package/dist/core/resolve.d.ts.map +1 -1
  47. package/dist/core/resolve.js +66 -21
  48. package/dist/core/resolve.js.map +1 -1
  49. package/dist/core/search-params.d.ts +15 -0
  50. package/dist/core/search-params.d.ts.map +1 -0
  51. package/dist/core/search-params.js +175 -0
  52. package/dist/core/search-params.js.map +1 -0
  53. package/dist/core/selection-change.d.ts +10 -0
  54. package/dist/core/selection-change.d.ts.map +1 -0
  55. package/dist/core/selection-change.js +124 -0
  56. package/dist/core/selection-change.js.map +1 -0
  57. package/dist/core/types.d.ts +209 -0
  58. package/dist/core/types.d.ts.map +1 -1
  59. package/dist/core/units.d.ts +13 -0
  60. package/dist/core/units.d.ts.map +1 -0
  61. package/dist/core/units.js +84 -0
  62. package/dist/core/units.js.map +1 -0
  63. package/dist/core/utils.d.ts.map +1 -1
  64. package/dist/core/utils.js +91 -32
  65. package/dist/core/utils.js.map +1 -1
  66. package/dist/core/view-model.d.ts +3 -1
  67. package/dist/core/view-model.d.ts.map +1 -1
  68. package/dist/core/view-model.js +113 -7
  69. package/dist/core/view-model.js.map +1 -1
  70. package/dist/element/change-review-define.d.ts +4 -0
  71. package/dist/element/change-review-define.d.ts.map +1 -0
  72. package/dist/element/change-review-define.js +17 -0
  73. package/dist/element/change-review-define.js.map +1 -0
  74. package/dist/element/change-review-element.d.ts +53 -0
  75. package/dist/element/change-review-element.d.ts.map +1 -0
  76. package/dist/element/change-review-element.js +104 -0
  77. package/dist/element/change-review-element.js.map +1 -0
  78. package/dist/element/change-review-events.d.ts +10 -0
  79. package/dist/element/change-review-events.d.ts.map +1 -0
  80. package/dist/element/change-review-events.js +2 -0
  81. package/dist/element/change-review-events.js.map +1 -0
  82. package/dist/element/change-review-renderers.d.ts +23 -0
  83. package/dist/element/change-review-renderers.d.ts.map +1 -0
  84. package/dist/element/change-review-renderers.js +391 -0
  85. package/dist/element/change-review-renderers.js.map +1 -0
  86. package/dist/element/cta.d.ts +45 -0
  87. package/dist/element/cta.d.ts.map +1 -0
  88. package/dist/element/cta.js +90 -0
  89. package/dist/element/cta.js.map +1 -0
  90. package/dist/element/current-selection.d.ts +13 -0
  91. package/dist/element/current-selection.d.ts.map +1 -0
  92. package/dist/element/current-selection.js +57 -0
  93. package/dist/element/current-selection.js.map +1 -0
  94. package/dist/element/define-change-review.d.ts +2 -0
  95. package/dist/element/define-change-review.d.ts.map +1 -0
  96. package/dist/element/define-change-review.js +7 -0
  97. package/dist/element/define-change-review.js.map +1 -0
  98. package/dist/element/events.d.ts +32 -6
  99. package/dist/element/events.d.ts.map +1 -1
  100. package/dist/element/hide-sections.d.ts +6 -0
  101. package/dist/element/hide-sections.d.ts.map +1 -0
  102. package/dist/element/hide-sections.js +26 -0
  103. package/dist/element/hide-sections.js.map +1 -0
  104. package/dist/element/index.d.ts +8 -1
  105. package/dist/element/index.d.ts.map +1 -1
  106. package/dist/element/index.js +3 -0
  107. package/dist/element/index.js.map +1 -1
  108. package/dist/element/pricing-renderer-element.d.ts +95 -7
  109. package/dist/element/pricing-renderer-element.d.ts.map +1 -1
  110. package/dist/element/pricing-renderer-element.js +563 -217
  111. package/dist/element/pricing-renderer-element.js.map +1 -1
  112. package/dist/element/renderers.d.ts +49 -4
  113. package/dist/element/renderers.d.ts.map +1 -1
  114. package/dist/element/renderers.js +795 -263
  115. package/dist/element/renderers.js.map +1 -1
  116. package/dist/element/same-input.d.ts +3 -0
  117. package/dist/element/same-input.d.ts.map +1 -0
  118. package/dist/element/same-input.js +56 -0
  119. package/dist/element/same-input.js.map +1 -0
  120. package/dist/react/headless.d.ts +9 -0
  121. package/dist/react/headless.d.ts.map +1 -0
  122. package/dist/react/headless.js +8 -0
  123. package/dist/react/headless.js.map +1 -0
  124. package/dist/react/index.d.ts +54 -4
  125. package/dist/react/index.d.ts.map +1 -1
  126. package/dist/react/index.js +156 -6
  127. package/dist/react/index.js.map +1 -1
  128. package/dist/react/provider.d.ts +17 -0
  129. package/dist/react/provider.d.ts.map +1 -0
  130. package/dist/react/provider.js +38 -0
  131. package/dist/react/provider.js.map +1 -0
  132. package/dist/react/selection-store.d.ts +51 -0
  133. package/dist/react/selection-store.d.ts.map +1 -0
  134. package/dist/react/selection-store.js +239 -0
  135. package/dist/react/selection-store.js.map +1 -0
  136. package/dist/react/slots-types.d.ts +38 -0
  137. package/dist/react/slots-types.d.ts.map +1 -0
  138. package/dist/react/slots-types.js +2 -0
  139. package/dist/react/slots-types.js.map +1 -0
  140. package/dist/react/slots.d.ts +40 -0
  141. package/dist/react/slots.d.ts.map +1 -0
  142. package/dist/react/slots.js +165 -0
  143. package/dist/react/slots.js.map +1 -0
  144. package/dist/react/use-pricing-selection.d.ts +10 -0
  145. package/dist/react/use-pricing-selection.d.ts.map +1 -0
  146. package/dist/react/use-pricing-selection.js +44 -0
  147. package/dist/react/use-pricing-selection.js.map +1 -0
  148. package/dist/react/use-pricing-source.d.ts +34 -0
  149. package/dist/react/use-pricing-source.d.ts.map +1 -0
  150. package/dist/react/use-pricing-source.js +186 -0
  151. package/dist/react/use-pricing-source.js.map +1 -0
  152. package/dist/react/use-renderer-state.d.ts +22 -0
  153. package/dist/react/use-renderer-state.d.ts.map +1 -0
  154. package/dist/react/use-renderer-state.js +112 -0
  155. package/dist/react/use-renderer-state.js.map +1 -0
  156. package/dist/space/index.d.ts +26 -0
  157. package/dist/space/index.d.ts.map +1 -0
  158. package/dist/space/index.js +71 -0
  159. package/dist/space/index.js.map +1 -0
  160. package/dist/styles/base.css +1030 -76
  161. package/dist/styles/theme-minimal.css +103 -0
  162. package/dist/styles/theme.css +22 -3
  163. package/dist/styles/tokens-shadcn.css +40 -0
  164. package/dist/yaml/index.d.ts +2 -0
  165. package/dist/yaml/index.d.ts.map +1 -1
  166. package/dist/yaml/index.js +1 -0
  167. package/dist/yaml/index.js.map +1 -1
  168. package/dist/yaml/load.d.ts.map +1 -1
  169. package/dist/yaml/load.js +106 -17
  170. package/dist/yaml/load.js.map +1 -1
  171. package/dist/yaml/source.d.ts +18 -0
  172. package/dist/yaml/source.d.ts.map +1 -0
  173. package/dist/yaml/source.js +46 -0
  174. package/dist/yaml/source.js.map +1 -0
  175. package/package.json +25 -13
@@ -38,6 +38,19 @@
38
38
  "name": "defaultSelection",
39
39
  "type": { "text": "Partial<PricingSelection>" }
40
40
  },
41
+ {
42
+ "kind": "field",
43
+ "name": "currentSelection",
44
+ "type": { "text": "Partial<PricingSelection> | undefined" },
45
+ "description": "The customer's current contract. Marks the current plan and add-ons, and labels the other plans' calls to action as upgrades or downgrades. It does not change the selection in effect and emits no pricing-selection-change. The element keeps a deep copy."
46
+ },
47
+ {
48
+ "kind": "field",
49
+ "name": "pendingActionId",
50
+ "type": { "text": "string | undefined" },
51
+ "attribute": "pending-action-id",
52
+ "description": "Action id of the call to action whose work the host is doing. That control shows a busy state, every plan call to action and the summary action get aria-disabled, and activating them dispatches no pricing-action."
53
+ },
41
54
  {
42
55
  "kind": "field",
43
56
  "name": "presentation",
@@ -91,28 +104,245 @@
91
104
  "type": { "text": "\"light\" | \"dark\" | \"auto\"" },
92
105
  "attribute": "theme"
93
106
  },
107
+ {
108
+ "kind": "field",
109
+ "name": "density",
110
+ "type": { "text": "\"comfortable\" | \"compact\"" },
111
+ "attribute": "density"
112
+ },
94
113
  {
95
114
  "kind": "field",
96
115
  "name": "loadingLabel",
97
116
  "type": { "text": "string" },
98
117
  "attribute": "loading-label"
99
118
  },
119
+ {
120
+ "kind": "field",
121
+ "name": "onlyDifferences",
122
+ "type": { "text": "boolean | undefined" },
123
+ "attribute": "only-differences",
124
+ "description": "Initial state of the Only differences checkbox of the comparison toolbar. Absent is automatic (on when the pricing has more than 100 comparison rows, at least 2 plans and the toolbar is shown, otherwise off), present or any value except \"false\" is on, \"false\" is off."
125
+ },
126
+ {
127
+ "kind": "field",
128
+ "name": "toolbarThreshold",
129
+ "type": { "text": "number" },
130
+ "attribute": "toolbar-threshold",
131
+ "description": "The toolbar is shown when the pricing has more than this many comparison rows (default 40). Invalid or negative values fall back to 40."
132
+ },
133
+ {
134
+ "kind": "field",
135
+ "name": "addOnFilterThreshold",
136
+ "type": { "text": "number" },
137
+ "attribute": "add-on-filter-threshold",
138
+ "description": "The add-on grid switches to filter mode (search, counter and a cap of 8 initial cards, selected add-ons are never hidden) when the pricing has more add-ons than this (default 13). Invalid or negative values fall back to 13."
139
+ },
140
+ {
141
+ "kind": "field",
142
+ "name": "hideSections",
143
+ "type": { "text": "readonly PricingSectionName[] | undefined" },
144
+ "attribute": "hide-sections",
145
+ "description": "Sections that are not rendered: hero, metadata, billing, variables, plans, comparison, addOns, summary. The attribute is a whitespace separated list; tokens that are not section names are ignored. Hiding the hero keeps a visually hidden title that labels the renderer. Hiding the add-ons keeps the summary."
146
+ },
147
+ {
148
+ "kind": "field",
149
+ "name": "slots",
150
+ "type": { "text": "readonly PricingSlotName[] | undefined" },
151
+ "description": "Slots the host fills: plan-cta, summary-actions, loading, error. For each requested slot the element renders an empty div with data-pr-slot and the state as data-* attributes, in place of the built-in control, and reports the nodes with pricing-slots-change. A value that is not an array, and names that are not slot names, are ignored. No attribute."
152
+ },
153
+ {
154
+ "kind": "field",
155
+ "name": "resolvedState",
156
+ "type": { "text": "PricingSelectionChangeDetail | undefined" },
157
+ "readonly": true,
158
+ "description": "Detached snapshot of the selection in effect and its resolved pricing, or undefined while the element is not ready. It resolves the pricing on every read: read it on events, not in a render loop."
159
+ },
100
160
  {
101
161
  "kind": "method",
102
162
  "name": "reload",
103
163
  "return": { "type": { "text": "Promise<void>" } }
164
+ },
165
+ {
166
+ "kind": "method",
167
+ "name": "applySelection",
168
+ "parameters": [
169
+ {
170
+ "name": "selection",
171
+ "type": { "text": "Partial<PricingSelection>" }
172
+ }
173
+ ],
174
+ "return": { "type": { "text": "ApplySelectionResult" } },
175
+ "description": "Validates the selection against the loaded pricing and replaces the internal selection. Omitted fields take the schema defaults. Emits no pricing-selection-change. Refused by result (applied: false) while the element is not ready or when the selection property is set."
176
+ },
177
+ {
178
+ "kind": "method",
179
+ "name": "triggerPlanAction",
180
+ "parameters": [{ "name": "planId", "type": { "text": "string" } }],
181
+ "return": { "type": { "text": "boolean" } },
182
+ "description": "Fires the pricing-action that activating the built-in CTA of this plan fires, and returns the result of dispatchEvent. Returns false, without an event, while pendingActionId is set, ctaEnabled is false, the element is not ready, the plans are hidden, the plan is not rendered or its CTA is disabled (the current plan)."
183
+ }
184
+ ],
185
+ "attributes": [
186
+ { "name": "src", "fieldName": "src", "type": { "text": "string" } },
187
+ { "name": "locale", "fieldName": "locale", "type": { "text": "string" } },
188
+ { "name": "pricing-path", "fieldName": "pricingPath", "type": { "text": "string" } },
189
+ {
190
+ "name": "mode",
191
+ "fieldName": "mode",
192
+ "type": { "text": "\"commercial\" | \"catalog\"" }
193
+ },
194
+ {
195
+ "name": "layout",
196
+ "fieldName": "layout",
197
+ "type": { "text": "\"auto\" | \"compact\" | \"table\"" }
198
+ },
199
+ {
200
+ "name": "visibility",
201
+ "fieldName": "visibility",
202
+ "type": { "text": "\"public-only\" | \"all\"" }
203
+ },
204
+ {
205
+ "name": "theme",
206
+ "fieldName": "theme",
207
+ "type": { "text": "\"light\" | \"dark\" | \"auto\"" }
208
+ },
209
+ {
210
+ "name": "density",
211
+ "fieldName": "density",
212
+ "type": { "text": "\"comfortable\" | \"compact\"" }
213
+ },
214
+ { "name": "loading-label", "fieldName": "loadingLabel", "type": { "text": "string" } },
215
+ {
216
+ "name": "only-differences",
217
+ "fieldName": "onlyDifferences",
218
+ "type": { "text": "boolean | undefined" }
219
+ },
220
+ {
221
+ "name": "toolbar-threshold",
222
+ "fieldName": "toolbarThreshold",
223
+ "type": { "text": "number" }
224
+ },
225
+ {
226
+ "name": "add-on-filter-threshold",
227
+ "fieldName": "addOnFilterThreshold",
228
+ "type": { "text": "number" }
229
+ },
230
+ {
231
+ "name": "hide-sections",
232
+ "fieldName": "hideSections",
233
+ "type": { "text": "readonly PricingSectionName[] | undefined" }
234
+ },
235
+ {
236
+ "name": "pending-action-id",
237
+ "fieldName": "pendingActionId",
238
+ "type": { "text": "string | undefined" }
104
239
  }
105
240
  ],
106
241
  "events": [
107
- { "name": "pricing-ready", "type": { "text": "CustomEvent<PricingReadyDetail>" } },
242
+ {
243
+ "name": "pricing-ready",
244
+ "type": { "text": "CustomEvent<PricingReadyDetail>" },
245
+ "description": "Fired after a successful load. The detail has pricing, diagnostics, and the selection in effect and its resolved pricing as selection and resolved."
246
+ },
108
247
  {
109
248
  "name": "pricing-selection-change",
110
249
  "type": { "text": "CustomEvent<PricingSelectionChangeDetail>" }
111
250
  },
112
- { "name": "pricing-action", "type": { "text": "CustomEvent<PricingActionDetail>" } },
251
+ {
252
+ "name": "pricing-action",
253
+ "type": { "text": "CustomEvent<PricingActionDetail>" },
254
+ "description": "Fired when a call to action is activated. While currentSelection names a plan of the pricing, the detail also has relation and source ('plan' or 'summary'). Cancelable; a canceled link does not navigate."
255
+ },
113
256
  {
114
257
  "name": "pricing-diagnostic",
115
- "type": { "text": "CustomEvent<PricingDiagnosticDetail>" }
258
+ "type": { "text": "CustomEvent<PricingDiagnosticDetail>" },
259
+ "description": "Fired with the diagnostics of a load, after an applySelection call that returns diagnostics, including its PR_SELECTION_NOT_READY and PR_SELECTION_CONTROLLED refusals, and when currentSelection or the pricing changes and currentSelection has a plan or add-on id the pricing does not define (PR_CURRENT_SELECTION_UNKNOWN_PLAN, PR_CURRENT_SELECTION_UNKNOWN_ADDON). Check the diagnostic code before treating an error as a load failure."
260
+ },
261
+ {
262
+ "name": "pricing-slots-change",
263
+ "type": { "text": "CustomEvent<PricingSlotsChangeDetail>" },
264
+ "description": "Fired after an update when the slot nodes the element rendered (detail.nodes, in document order) or their data-* values changed, or, with a summary-actions node, when the selection or its resolved prices changed. Fired with an empty list when the slots go away. Bubbles, composed, not cancelable. Nodes of a nested renderer or added by the host are not listed."
265
+ }
266
+ ]
267
+ }
268
+ ]
269
+ },
270
+ {
271
+ "kind": "javascript-module",
272
+ "path": "dist/element/change-review-element.js",
273
+ "declarations": [
274
+ {
275
+ "kind": "class",
276
+ "name": "PricingChangeReviewElement",
277
+ "customElement": true,
278
+ "tagName": "pricing-change-review",
279
+ "description": "The review of a subscription change: it shows the comparison of the customer's current subscription with the edit, and asks the host to confirm it. Registered by importing pricing-renderer/define-change-review.",
280
+ "members": [
281
+ {
282
+ "kind": "field",
283
+ "name": "comparison",
284
+ "type": { "text": "SelectionComparison | undefined" },
285
+ "description": "The result of compareSelections. Nothing is rendered while it is unset."
286
+ },
287
+ {
288
+ "kind": "field",
289
+ "name": "notices",
290
+ "type": { "text": "string[]" },
291
+ "description": "Notices from the host, rendered as text. Defaults to an empty list."
292
+ },
293
+ {
294
+ "kind": "field",
295
+ "name": "pending",
296
+ "type": { "text": "boolean" },
297
+ "attribute": "pending",
298
+ "description": "True while the host applies the change. Confirm ignores activations, and the attribute is reflected. Confirm sets it when pricing-change-confirm is not canceled."
299
+ },
300
+ {
301
+ "kind": "field",
302
+ "name": "error",
303
+ "type": { "text": "string | undefined" },
304
+ "attribute": "error",
305
+ "description": "A plain-text error message from the host, rendered as text."
306
+ },
307
+ {
308
+ "kind": "field",
309
+ "name": "locale",
310
+ "type": { "text": "string" },
311
+ "attribute": "locale"
312
+ },
313
+ {
314
+ "kind": "field",
315
+ "name": "messages",
316
+ "type": { "text": "MessageCatalog | undefined" }
317
+ },
318
+ {
319
+ "kind": "field",
320
+ "name": "confirmLabel",
321
+ "type": { "text": "string | undefined" },
322
+ "attribute": "confirm-label",
323
+ "description": "Replaces the text of the Confirm button (pricing.reviewConfirm)."
324
+ }
325
+ ],
326
+ "attributes": [
327
+ { "name": "pending", "fieldName": "pending", "type": { "text": "boolean" } },
328
+ { "name": "error", "fieldName": "error", "type": { "text": "string | undefined" } },
329
+ { "name": "locale", "fieldName": "locale", "type": { "text": "string" } },
330
+ {
331
+ "name": "confirm-label",
332
+ "fieldName": "confirmLabel",
333
+ "type": { "text": "string | undefined" }
334
+ }
335
+ ],
336
+ "events": [
337
+ {
338
+ "name": "pricing-change-confirm",
339
+ "type": { "text": "CustomEvent<PricingChangeConfirmDetail>" },
340
+ "description": "Fired when Confirm is activated while pending is false and the relation is not current. Cancelable. When it is not canceled, the element sets pending to true. The detail has a deep copy of the comparison."
341
+ },
342
+ {
343
+ "name": "pricing-change-back",
344
+ "type": { "text": "CustomEvent<PricingChangeBackDetail>" },
345
+ "description": "Fired when Back is activated. Cancelable, with no default action. The detail has a deep copy of the comparison."
116
346
  }
117
347
  ]
118
348
  }
@@ -0,0 +1,8 @@
1
+ import type { NormalizedPricing, PricingDiagnostic, PricingSelection } from './types.js';
2
+ export interface ApplySelectionInputResult {
3
+ selection: PricingSelection;
4
+ removedAddOnIds: string[];
5
+ diagnostics: PricingDiagnostic[];
6
+ }
7
+ export declare function applySelectionInput(pricing: NormalizedPricing, current: PricingSelection, input: Partial<PricingSelection>): ApplySelectionInputResult;
8
+ //# sourceMappingURL=apply-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-selection.d.ts","sourceRoot":"","sources":["../../src/core/apply-selection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAC/B,yBAAyB,CA6E3B"}
@@ -0,0 +1,76 @@
1
+ import { mergeSelection, normalizeAddOnQuantity, reconcileSelection } from './resolve.js';
2
+ import { cloneValue, isRecord } from './utils.js';
3
+ export function applySelectionInput(pricing, current, input) {
4
+ const copy = cloneValue(input);
5
+ const diagnostics = [];
6
+ const validated = {};
7
+ if (copy.planId !== undefined) {
8
+ if (pricing.plans.some((plan) => plan.id === copy.planId)) {
9
+ validated.planId = copy.planId;
10
+ }
11
+ else {
12
+ diagnostics.push({
13
+ code: 'PR_SELECTION_UNKNOWN_PLAN',
14
+ severity: 'warning',
15
+ message: `Unknown plan "${copy.planId}"; the current plan is kept.`,
16
+ path: 'selection.planId',
17
+ });
18
+ if (current.planId !== undefined)
19
+ validated.planId = current.planId;
20
+ }
21
+ }
22
+ if (copy.billingPeriod !== undefined) {
23
+ if (pricing.billing.some((period) => period.id === copy.billingPeriod)) {
24
+ validated.billingPeriod = copy.billingPeriod;
25
+ }
26
+ else {
27
+ diagnostics.push({
28
+ code: 'PR_SELECTION_UNKNOWN_BILLING_PERIOD',
29
+ severity: 'warning',
30
+ message: `Unknown billing period "${copy.billingPeriod}"; the current billing period is kept.`,
31
+ path: 'selection.billingPeriod',
32
+ });
33
+ if (current.billingPeriod !== undefined)
34
+ validated.billingPeriod = current.billingPeriod;
35
+ }
36
+ }
37
+ if (isRecord(copy.variables)) {
38
+ validated.variables = copy.variables;
39
+ }
40
+ if (isRecord(copy.addOns)) {
41
+ const inputAddOns = copy.addOns;
42
+ const addOns = {};
43
+ for (const id of Object.keys(inputAddOns)) {
44
+ const entry = inputAddOns[id];
45
+ if (!pricing.addOns.some((addOn) => addOn.id === id)) {
46
+ diagnostics.push({
47
+ code: 'PR_SELECTION_UNKNOWN_ADDON',
48
+ severity: 'warning',
49
+ message: `Unknown add-on "${id}"; it is ignored.`,
50
+ path: `selection.addOns.${id}`,
51
+ });
52
+ continue;
53
+ }
54
+ // a known add-on without a plain-object entry counts as omitted: the default applies
55
+ if (!isRecord(entry))
56
+ continue;
57
+ const accepted = entry;
58
+ addOns[id] = entry.selected
59
+ ? { ...accepted, quantity: normalizeAddOnQuantity(pricing, id, accepted) }
60
+ : accepted;
61
+ }
62
+ validated.addOns = addOns;
63
+ }
64
+ const { selection, removedAddOnIds } = reconcileSelection(pricing, mergeSelection(pricing, validated));
65
+ for (const id of removedAddOnIds) {
66
+ diagnostics.push({
67
+ code: 'PR_SELECTION_ADDON_REMOVED',
68
+ severity: 'info',
69
+ message: `Add-on "${id}" is not available with the selected plan and was removed.`,
70
+ path: `selection.addOns.${id}`,
71
+ details: { addOnId: id },
72
+ });
73
+ }
74
+ return { selection, removedAddOnIds, diagnostics };
75
+ }
76
+ //# sourceMappingURL=apply-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-selection.js","sourceRoot":"","sources":["../../src/core/apply-selection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { NormalizedBillingPeriod, NormalizedPricing, PriceBreakdown, PricingSelection, ResolvedPrice } from './types.js';
2
+ interface BreakdownInput {
3
+ pricing: NormalizedPricing;
4
+ selection: PricingSelection;
5
+ reconciled: PricingSelection;
6
+ planPrices: Record<string, ResolvedPrice>;
7
+ addOnPrices: Record<string, ResolvedPrice>;
8
+ billingPeriod: NormalizedBillingPeriod | undefined;
9
+ addOnQuantity: (addOnId: string) => number;
10
+ }
11
+ export declare function buildBreakdown(input: BreakdownInput): PriceBreakdown;
12
+ export {};
13
+ //# sourceMappingURL=breakdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakdown.d.ts","sourceRoot":"","sources":["../../src/core/breakdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EAEd,gBAAgB,EAChB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,UAAU,cAAc;IACtB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC3C,aAAa,EAAE,uBAAuB,GAAG,SAAS,CAAC;IACnD,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CAC5C;AAyBD,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAuDpE"}
@@ -0,0 +1,56 @@
1
+ function createLine(kind, id, name, quantity, billingMultiplier, price) {
2
+ const base = { kind, id, name, quantity, billingMultiplier };
3
+ if (price?.kind === 'amount') {
4
+ return {
5
+ ...base,
6
+ unitAmount: price.baseAmount,
7
+ amount: price.baseAmount * billingMultiplier * quantity,
8
+ requiresQuote: false,
9
+ };
10
+ }
11
+ if (price?.kind === 'label') {
12
+ return { ...base, requiresQuote: true, label: price.label };
13
+ }
14
+ return { ...base, requiresQuote: true };
15
+ }
16
+ export function buildBreakdown(input) {
17
+ const { pricing, selection, reconciled, planPrices, addOnPrices, billingPeriod } = input;
18
+ const billingMultiplier = billingPeriod?.multiplier ?? 1;
19
+ const plan = pricing.plans.find((candidate) => candidate.id === selection.planId);
20
+ const planLine = plan
21
+ ? createLine('plan', plan.id, plan.name, 1, billingMultiplier, planPrices[plan.id])
22
+ : undefined;
23
+ const addOns = pricing.addOns
24
+ .filter((addOn) => reconciled.addOns[addOn.id]?.selected)
25
+ .map((addOn) => createLine('addOn', addOn.id, addOn.name, input.addOnQuantity(addOn.id), billingMultiplier, addOnPrices[addOn.id]));
26
+ let subtotal = planLine?.amount ?? 0;
27
+ for (const line of addOns) {
28
+ if (line.amount !== undefined)
29
+ subtotal += line.amount;
30
+ }
31
+ const knownAddOnIds = new Set(pricing.addOns.map((addOn) => addOn.id));
32
+ const requiresQuote = planLine === undefined ||
33
+ planLine.requiresQuote ||
34
+ addOns.some((line) => line.requiresQuote) ||
35
+ Object.entries(reconciled.addOns).some(([id, state]) => state.selected && !knownAddOnIds.has(id));
36
+ return {
37
+ currency: pricing.metadata.currency,
38
+ ...(plan?.unit ? { unit: plan.unit } : {}),
39
+ ...(billingPeriod
40
+ ? {
41
+ billingPeriod: {
42
+ id: billingPeriod.id,
43
+ label: billingPeriod.label,
44
+ multiplier: billingPeriod.multiplier,
45
+ // carried through, so a consumer can tell an invented period from a real choice
46
+ ...(billingPeriod.synthesized ? { synthesized: true } : {}),
47
+ },
48
+ }
49
+ : {}),
50
+ ...(planLine ? { plan: planLine } : {}),
51
+ addOns,
52
+ subtotal,
53
+ requiresQuote,
54
+ };
55
+ }
56
+ //# sourceMappingURL=breakdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakdown.js","sourceRoot":"","sources":["../../src/core/breakdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ExpressionOptions, PriceBreakdown, PricingDiagnostic, PricingSelection, SelectionComparisonSide } from './types.js';
2
+ export type SideName = 'before' | 'after';
3
+ export interface ComparedSideState {
4
+ /** Merged, sanitized, quantity-normalized and reconciled selection. */
5
+ selection: PricingSelection;
6
+ /** Ids the input selects, including ids the pricing does not define. */
7
+ requestedAddOnIds: Set<string>;
8
+ /** Add-ons that reconciliation deselected, in pricing declaration order. */
9
+ removedAddOnIds: string[];
10
+ breakdown: PriceBreakdown;
11
+ /** Unknown plan, billing period and add-on warnings, in that order. */
12
+ diagnostics: PricingDiagnostic[];
13
+ }
14
+ /** Decision 2, steps 1 to 5: derive one side without throwing. */
15
+ export declare function deriveSide(name: SideName, side: SelectionComparisonSide, expression: ExpressionOptions | undefined): ComparedSideState;
16
+ //# sourceMappingURL=compare-sides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-sides.d.ts","sourceRoot":"","sources":["../../src/core/compare-sides.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,SAAS,EAAE,gBAAgB,CAAC;IAC5B,wEAAwE;IACxE,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,cAAc,CAAC;IAC1B,uEAAuE;IACvE,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AA8BD,kEAAkE;AAClE,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,uBAAuB,EAC7B,UAAU,EAAE,iBAAiB,GAAG,SAAS,GACxC,iBAAiB,CA4DnB"}
@@ -0,0 +1,73 @@
1
+ import { mergeSelection, normalizeAddOnQuantity, reconcileSelection, resolvePricing, } from './resolve.js';
2
+ import { cloneValue, isRecord } from './utils.js';
3
+ function jsonCopy(value) {
4
+ try {
5
+ const copy = JSON.parse(JSON.stringify(value));
6
+ return isRecord(copy) ? copy : {};
7
+ }
8
+ catch {
9
+ return {};
10
+ }
11
+ }
12
+ function readInput(selection) {
13
+ if (!isRecord(selection))
14
+ return {};
15
+ const input = {};
16
+ if (typeof selection.planId === 'string')
17
+ input.planId = selection.planId;
18
+ if (typeof selection.billingPeriod === 'string')
19
+ input.billingPeriod = selection.billingPeriod;
20
+ if (isRecord(selection.variables))
21
+ input.variables = jsonCopy(selection.variables);
22
+ if (isRecord(selection.addOns)) {
23
+ input.addOns = { ...selection.addOns };
24
+ }
25
+ return input;
26
+ }
27
+ const warning = (code, message, path) => ({
28
+ code,
29
+ severity: 'warning',
30
+ message,
31
+ path,
32
+ });
33
+ /** Decision 2, steps 1 to 5: derive one side without throwing. */
34
+ export function deriveSide(name, side, expression) {
35
+ const { pricing } = side;
36
+ const merged = mergeSelection(pricing, readInput(side.selection));
37
+ const diagnostics = [];
38
+ if (merged.planId !== undefined && !pricing.plans.some((plan) => plan.id === merged.planId)) {
39
+ diagnostics.push(warning('PR_COMPARE_UNKNOWN_PLAN', `Unknown plan "${merged.planId}" in the ${name} selection.`, `${name}.selection.planId`));
40
+ }
41
+ if (merged.billingPeriod !== undefined &&
42
+ !pricing.billing.some((period) => period.id === merged.billingPeriod)) {
43
+ diagnostics.push(warning('PR_COMPARE_UNKNOWN_BILLING_PERIOD', `Unknown billing period "${merged.billingPeriod}" in the ${name} selection.`, `${name}.selection.billingPeriod`));
44
+ }
45
+ const addOns = {};
46
+ const requestedAddOnIds = new Set();
47
+ for (const [id, value] of Object.entries(merged.addOns)) {
48
+ const entry = isRecord(value) && typeof value.selected === 'boolean' ? value : undefined;
49
+ if (entry?.selected)
50
+ requestedAddOnIds.add(id);
51
+ if (!pricing.addOns.some((addOn) => addOn.id === id)) {
52
+ diagnostics.push(warning('PR_COMPARE_UNKNOWN_ADDON', `Unknown add-on "${id}" in the ${name} selection; it is ignored.`, `${name}.selection.addOns.${id}`));
53
+ continue;
54
+ }
55
+ if (!entry)
56
+ continue;
57
+ addOns[id] = entry.selected
58
+ ? {
59
+ selected: true,
60
+ quantity: normalizeAddOnQuantity(pricing, id, entry),
61
+ }
62
+ : {
63
+ selected: false,
64
+ quantity: typeof entry.quantity === 'number' && Number.isFinite(entry.quantity)
65
+ ? entry.quantity + 0
66
+ : 0,
67
+ };
68
+ }
69
+ const { selection, removedAddOnIds } = reconcileSelection(pricing, { ...merged, addOns });
70
+ const breakdown = cloneValue(resolvePricing(pricing, selection, { expression }).breakdown);
71
+ return { selection, requestedAddOnIds, removedAddOnIds, breakdown, diagnostics };
72
+ }
73
+ //# sourceMappingURL=compare-sides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-sides.js","sourceRoot":"","sources":["../../src/core/compare-sides.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,kBAAkB,EAClB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgBlD,SAAS,QAAQ,CAAC,KAA8B;IAC9C,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACxD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,SAAkB;IACnC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,KAAK,GAA8B,EAAE,CAAC;IAC5C,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC1E,IAAI,OAAO,SAAS,CAAC,aAAa,KAAK,QAAQ;QAAE,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC;IAC/F,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;QAAE,KAAK,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACnF,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,EAAoC,CAAC;IAC3E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,IAAY,EAAqB,EAAE,CAAC,CAAC;IACnF,IAAI;IACJ,QAAQ,EAAE,SAAS;IACnB,OAAO;IACP,IAAI;CACL,CAAC,CAAC;AAEH,kEAAkE;AAClE,MAAM,UAAU,UAAU,CACxB,IAAc,EACd,IAA6B,EAC7B,UAAyC;IAEzC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzB,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAClE,MAAM,WAAW,GAAwB,EAAE,CAAC;IAE5C,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5F,WAAW,CAAC,IAAI,CACd,OAAO,CACL,yBAAyB,EACzB,iBAAiB,MAAM,CAAC,MAAM,YAAY,IAAI,aAAa,EAC3D,GAAG,IAAI,mBAAmB,CAC3B,CACF,CAAC;IACJ,CAAC;IACD,IACE,MAAM,CAAC,aAAa,KAAK,SAAS;QAClC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,aAAa,CAAC,EACrE,CAAC;QACD,WAAW,CAAC,IAAI,CACd,OAAO,CACL,mCAAmC,EACnC,2BAA2B,MAAM,CAAC,aAAa,YAAY,IAAI,aAAa,EAC5E,GAAG,IAAI,0BAA0B,CAClC,CACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAmC,EAAE,CAAC;IAClD,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC5C,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAiC,CAAC,EAAE,CAAC;QACnF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,KAAK,EAAE,QAAQ;YAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YACrD,WAAW,CAAC,IAAI,CACd,OAAO,CACL,0BAA0B,EAC1B,mBAAmB,EAAE,YAAY,IAAI,4BAA4B,EACjE,GAAG,IAAI,qBAAqB,EAAE,EAAE,CACjC,CACF,CAAC;YACF,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ;YACzB,CAAC,CAAC;gBACE,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,sBAAsB,CAAC,OAAO,EAAE,EAAE,EAAE,KAAkC,CAAC;aAClF;YACH,CAAC,CAAC;gBACE,QAAQ,EAAE,KAAK;gBACf,QAAQ,EACN,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;oBACnE,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC;oBACpB,CAAC,CAAC,CAAC;aACR,CAAC;IACR,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,kBAAkB,CAAC,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1F,MAAM,SAAS,GAAG,UAAU,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;IAC3F,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AACnF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ComparedValue, NormalizedPricing, PricingSelection, UsageLimitChange } from './types.js';
2
+ export type ComparedValueKind = 'feature' | 'usage-limit';
3
+ export declare const UNLIMITED_TEXT: Set<string>;
4
+ export declare function toComparedValue(raw: unknown, kind: ComparedValueKind): ComparedValue;
5
+ /**
6
+ * The value of a feature or usage limit for a reconciled selection: the plan value (or the
7
+ * definition's default), replaced or extended by each selected add-on in declaration order.
8
+ */
9
+ export declare function effectiveValue(pricing: NormalizedPricing, selection: PricingSelection, kind: ComparedValueKind, id: string, defaultValue: unknown): ComparedValue;
10
+ export declare function hasCapability(value: ComparedValue): boolean;
11
+ export declare function limitDirection(before: ComparedValue, after: ComparedValue): UsageLimitChange['direction'];
12
+ export declare function exceedsUsage(after: ComparedValue, consumed: number): boolean;
13
+ //# sourceMappingURL=compare-values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-values.d.ts","sourceRoot":"","sources":["../../src/core/compare-values.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE1D,eAAO,MAAM,cAAc,aAA6C,CAAC;AAKzE,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAoBpF;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,gBAAgB,EAC3B,IAAI,EAAE,iBAAiB,EACvB,EAAE,EAAE,MAAM,EACV,YAAY,EAAE,OAAO,GACpB,aAAa,CAoBf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAa3D;AAQD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,GACnB,gBAAgB,CAAC,WAAW,CAAC,CAQ/B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI5E"}