@lumiastream/ui 0.2.8-alpha.12 → 0.2.8-alpha.13

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 (3) hide show
  1. package/dist/index.js +240 -45
  2. package/dist/se-import.js +240 -45
  3. package/package.json +131 -131
package/dist/index.js CHANGED
@@ -3341,11 +3341,11 @@ var LUMIA_DEFAULT_SIZES = {
3341
3341
  audio: { width: 200, height: 200 },
3342
3342
  slideshow: { width: 400, height: 220 },
3343
3343
  alert: { width: 700, height: 600 },
3344
- // 80 was too short for the SE-style slim goal layout (label + bar + "Ends
3345
- // in" sublabel needs ~125 px of vertical space). Bumped so editor-canvas
3346
- // rendering (which doesn't honor autoHeight) doesn't clip the sublabel
3347
- // after import.
3348
- goal: { width: 740, height: 130 },
3344
+ // Sized for the SE-style slim layout — title (~24px line) + tight gap +
3345
+ // 1.7em bar + optional "Ends in" sublabel collapses to ~70-85 px. 90 gives
3346
+ // a small safety margin for larger fonts without leaving dead space in the
3347
+ // editor canvas (which doesn't honor the layer's `autoHeight: true`).
3348
+ goal: { width: 740, height: 90 },
3349
3349
  timer: { width: 740, height: 80 },
3350
3350
  chatbox: { width: 375, height: 470 },
3351
3351
  eventlist: { width: 375, height: 470 },
@@ -3382,10 +3382,20 @@ function translateVariationCondition(seType, seCondition, requirement) {
3382
3382
  return { conditionType: "RANDOM", condition: 100 };
3383
3383
  }
3384
3384
  var LUMIA_LAYOUTS = /* @__PURE__ */ new Set(["column", "row", "onlyImage", "onlyText", "imageOver", "textOver"]);
3385
+ var SE_LAYOUT_ALIASES = {
3386
+ behind: "textOver"
3387
+ };
3385
3388
  function translateLayout(seLayout) {
3386
- if (seLayout && LUMIA_LAYOUTS.has(seLayout)) return seLayout;
3389
+ if (!seLayout) return "column";
3390
+ const alias = SE_LAYOUT_ALIASES[seLayout];
3391
+ if (alias) return alias;
3392
+ if (LUMIA_LAYOUTS.has(seLayout)) return seLayout;
3387
3393
  return "column";
3388
3394
  }
3395
+ function seLayoutUsesPushText(seLayout) {
3396
+ if (!seLayout) return true;
3397
+ return seLayout !== "behind";
3398
+ }
3389
3399
 
3390
3400
  // src/se-import/mappers/build.ts
3391
3401
  import { nanoid } from "nanoid";
@@ -3596,7 +3606,7 @@ function buildAlertEvent(seEvent, seEventType) {
3596
3606
  const importedTextShadow = css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px";
3597
3607
  const textShadow = shadowEnabled ? importedTextShadow : "none";
3598
3608
  const marginTopRaw = Number(css["margin-top"]);
3599
- const pushTextUp = Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
3609
+ const pushTextUp = seLayoutUsesPushText(seEvent.layout) && Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
3600
3610
  const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType, seEvent)).filter((v) => v != null);
3601
3611
  const defaultDuration = (seEventType && SE_DEFAULT_DURATION_BY_EVENT[seEventType]) ?? 8;
3602
3612
  return {
@@ -3726,25 +3736,52 @@ function deepMergeSeSettings(parent, variation) {
3726
3736
  }
3727
3737
  return out;
3728
3738
  }
3739
+ function pickBaseAlertFields(event) {
3740
+ const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
3741
+ return base;
3742
+ }
3729
3743
  function mapAlertBox(widget, opts = {}) {
3730
3744
  const v = widget.variables ?? {};
3731
3745
  const events = {};
3732
3746
  const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
3747
+ let firstEvent = null;
3733
3748
  for (const seKey of eventKeys) {
3734
3749
  const ev = v[seKey];
3735
3750
  if (!ev || typeof ev !== "object") continue;
3736
3751
  const lumiaAlert = SE_EVENT_TO_LUMIA_ALERT[seKey];
3737
3752
  if (!lumiaAlert) continue;
3738
- events[lumiaAlert] = buildAlertEvent(ev, seKey);
3753
+ const built = buildAlertEvent(ev, seKey);
3754
+ events[lumiaAlert] = built;
3755
+ if (firstEvent == null) firstEvent = built;
3739
3756
  }
3740
- return buildUnit(widget, "alert", { alert: { events } });
3757
+ const baseAlert = firstEvent ? pickBaseAlertFields(firstEvent) : {};
3758
+ return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
3741
3759
  }
3742
3760
  function mapSingleAlert(widget, lumiaAlertKey) {
3743
3761
  const v = widget.variables ?? {};
3762
+ const topText = widget.text;
3763
+ const merged = { ...v };
3764
+ if (topText) {
3765
+ const inner = v.text ?? {};
3766
+ const innerCss = inner.css ?? {};
3767
+ const topCss = topText.css ?? {};
3768
+ merged.text = {
3769
+ ...inner,
3770
+ message: inner.message ?? topText.value ?? "",
3771
+ enableShadow: inner.enableShadow ?? topText.enableShadow,
3772
+ // Top-level SE CSS is the source of truth for Extra Life / DonorDrive
3773
+ // (which omit `variables.text` entirely). For Tiltify both are populated
3774
+ // with the same values; letting the nested `variables.text.css` win
3775
+ // preserves any per-event overrides the streamer might have set.
3776
+ css: { ...topCss, ...innerCss }
3777
+ };
3778
+ }
3779
+ const built = buildAlertEvent(merged);
3744
3780
  const events = {
3745
- [lumiaAlertKey]: buildAlertEvent(v)
3781
+ [lumiaAlertKey]: built
3746
3782
  };
3747
- return buildUnit(widget, "alert", { alert: { events } });
3783
+ const baseAlert = pickBaseAlertFields(built);
3784
+ return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
3748
3785
  }
3749
3786
 
3750
3787
  // src/se-import/mappers/goal.ts
@@ -3760,9 +3797,17 @@ function currentForListener(listener) {
3760
3797
  if (listener.startsWith("merch-")) return "{{fourthwall_total_order_count}}";
3761
3798
  return "0";
3762
3799
  }
3800
+ function charityGoalVariables(widgetType) {
3801
+ if (widgetType === "se-widget-tiltify-goal") return { current: "{{tiltify_total_raised=0}}", targetGoal: "{{tiltify_goal_amount=100}}" };
3802
+ if (widgetType === "se-widget-extralife-goal") return { current: "{{extralife_total_raised=0}}", targetGoal: "{{extralife_goal_amount=100}}" };
3803
+ if (widgetType === "se-widget-donor-drive-goal") return { current: "{{donordrive_total_raised=0}}", targetGoal: "{{donordrive_goal_amount=100}}" };
3804
+ return null;
3805
+ }
3763
3806
  function mapGoal(widget) {
3764
3807
  const v = widget.variables ?? {};
3765
3808
  const listener = v.goalListener ?? (typeof widget.listener === "string" ? widget.listener : null);
3809
+ const charity = charityGoalVariables(widget.type);
3810
+ const seTextCss = widget.text?.css ?? {};
3766
3811
  return buildUnit(widget, "goal", {
3767
3812
  content: {
3768
3813
  version: 1,
@@ -3774,11 +3819,13 @@ function mapGoal(widget) {
3774
3819
  label: translateSeText(v.title ?? "Goal"),
3775
3820
  subLabel: "",
3776
3821
  // The Goal module renders this template literal with {{current}} + {{goal}} substituted.
3777
- display: "{{current}} / {{goal}}",
3822
+ // Charity goals track donation amounts — prefix `$` so the rendered bar reads
3823
+ // "$0 / $100" the way SE's charity goals do, instead of bare integers.
3824
+ display: charity ? "${{current}} / ${{goal}}" : "{{current}} / {{goal}}",
3778
3825
  // `current` is a template variable reference (resolved at render time from session variables).
3779
3826
  // `targetGoal` is the numeric ceiling. Both required by Goal/index.tsx:28-29.
3780
- current: currentForListener(listener),
3781
- targetGoal: String(v.goalAmount ?? 100),
3827
+ current: charity ? charity.current : currentForListener(listener),
3828
+ targetGoal: charity ? charity.targetGoal : String(v.goalAmount ?? 100),
3782
3829
  image: "",
3783
3830
  audio: { decrement: "", increment: "", goal: "", goalFailed: "", volume: 1 },
3784
3831
  // `null` (rather than `''`) is the cleaner contract — Goal/index.tsx:11's
@@ -3805,13 +3852,13 @@ function mapGoal(widget) {
3805
3852
  // `content.border` and `content.borderRadius`. Writing them to
3806
3853
  // module.css just plopped useless transparent-0px rules on the
3807
3854
  // outer .overlay-module wrapper.
3808
- lineHeight: 1,
3809
- fontSize: 30,
3810
- textAlign: "center",
3811
- fontFamily: "Roboto",
3812
- fontWeight: "bold",
3813
- color: "#ffffff",
3814
- textShadow: "rgb(0, 0, 0) 1px 1px 1px",
3855
+ lineHeight: seTextCss["line-height"] ?? 1,
3856
+ fontSize: seTextCss["font-size"] ?? 30,
3857
+ textAlign: seTextCss["text-align"] ?? "center",
3858
+ fontFamily: seTextCss["font-family"] ?? "Roboto",
3859
+ fontWeight: seTextCss["font-weight"] ?? "bold",
3860
+ color: seTextCss.color ?? "#ffffff",
3861
+ textShadow: seTextCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
3815
3862
  background: "transparent"
3816
3863
  }
3817
3864
  });
@@ -4070,33 +4117,143 @@ function mapEventList(widget) {
4070
4117
  }
4071
4118
  });
4072
4119
  }
4073
- var CREDITS_DEFAULT_EVENTS_TO_SHOW = {
4120
+ var SE_CREDITS_EVENTS_TO_SHOW = {
4074
4121
  mods: false,
4075
4122
  subscribers: true,
4076
4123
  gifts: true,
4077
- donations: true,
4078
- purchases: true,
4079
- bits: true,
4080
- kicks: true,
4081
- raids: true,
4082
- hosts: true,
4083
- superstickers: true,
4084
- superchats: true,
4085
- redemptions: true,
4086
- stars: true,
4087
- fans: true,
4124
+ donations: false,
4125
+ purchases: false,
4126
+ bits: false,
4127
+ kicks: false,
4128
+ raids: false,
4129
+ hosts: false,
4130
+ superstickers: false,
4131
+ superchats: false,
4132
+ redemptions: false,
4133
+ stars: false,
4134
+ fans: false,
4088
4135
  shares: false,
4089
- spells: true,
4136
+ spells: false,
4090
4137
  likes: false,
4091
- followers: true,
4138
+ followers: false,
4092
4139
  others: false,
4093
- topGifts: true,
4094
- topBits: true,
4095
- topKicks: true,
4096
- topDonations: true,
4097
- topSuperstickers: true,
4098
- topSuperchats: true,
4099
- topRedemptions: true
4140
+ topGifts: false,
4141
+ topBits: false,
4142
+ topKicks: false,
4143
+ topDonations: false,
4144
+ topSuperstickers: false,
4145
+ topSuperchats: false,
4146
+ topRedemptions: false
4147
+ };
4148
+ var CREDITS_DEFAULT_SECTION_ORDER = [
4149
+ "topGifts",
4150
+ "topBits",
4151
+ "topKicks",
4152
+ "topDonations",
4153
+ "topSuperstickers",
4154
+ "topSuperchats",
4155
+ "topRedemptions",
4156
+ "subscribers",
4157
+ "gifts",
4158
+ "followers",
4159
+ "donations",
4160
+ "bits",
4161
+ "kicks",
4162
+ "raids",
4163
+ "hosts",
4164
+ "superstickers",
4165
+ "superchats",
4166
+ "redemptions",
4167
+ "others"
4168
+ ];
4169
+ var CREDITS_DEFAULT_TOP_LIMITS = {
4170
+ topGifts: 5,
4171
+ topBits: 5,
4172
+ topKicks: 5,
4173
+ topDonations: 5,
4174
+ topSuperstickers: 5,
4175
+ topSuperchats: 5,
4176
+ topRedemptions: 5
4177
+ };
4178
+ var CREDITS_DEFAULT_EVENT_NAMES = {
4179
+ mods: "Mods (Total {{amount}})",
4180
+ topGifts: "Top Gifters",
4181
+ topBits: "Top Cheerers",
4182
+ topKicks: "Top Kicks",
4183
+ topDonations: "Top Donators",
4184
+ topSuperstickers: "Top Super Stickers",
4185
+ topSuperchats: "Top Super Chatters",
4186
+ topRedemptions: "Top Redeemers",
4187
+ subscribers: "Subscribers (Total {{amount}})",
4188
+ gifts: "Gifts (Total {{amount}})",
4189
+ donations: "Donations (Total {{amount}})",
4190
+ purchases: "Purchases (Total {{amount}})",
4191
+ bits: "Cheers (Total {{amount}})",
4192
+ kicks: "Kicks (Total {{amount}})",
4193
+ superstickers: "Super Stickers (Total {{amount}})",
4194
+ superchats: "Super Chatters (Total {{amount}})",
4195
+ redemptions: "Redemptions (Total {{amount}})",
4196
+ stars: "Stars (Total {{amount}})",
4197
+ fans: "Fans (Total {{amount}})",
4198
+ shares: "Shares (Total {{amount}})",
4199
+ raids: "Raids (Total {{amount}})",
4200
+ hosts: "Hosts (Total {{amount}})",
4201
+ spells: "Spells (Total {{amount}})",
4202
+ retweets: "Retweets (Total {{amount}})",
4203
+ likes: "Likes (Total {{amount}})",
4204
+ followers: "Followers (Total {{amount}})",
4205
+ others: "Other (Total {{amount}})"
4206
+ };
4207
+ var CREDITS_DEFAULT_PREFIXES = {
4208
+ mods: "x",
4209
+ subscribers: "x",
4210
+ gifts: "x",
4211
+ donations: "x",
4212
+ purchases: "x",
4213
+ bits: "x",
4214
+ kicks: "x",
4215
+ superstickers: "x",
4216
+ superchats: "x",
4217
+ redemptions: "x",
4218
+ stars: "x",
4219
+ fans: "x",
4220
+ shares: "x",
4221
+ raids: "x",
4222
+ hosts: "x",
4223
+ spells: "x",
4224
+ retweets: "x",
4225
+ likes: "x",
4226
+ followers: "",
4227
+ others: ""
4228
+ };
4229
+ var CREDITS_DEFAULT_TEMPLATES = {
4230
+ mods: "{{username}}",
4231
+ subscribers: "{{username}} x{{amount}}",
4232
+ gifts: "{{username}} x{{amount}}",
4233
+ donations: "{{username}} x{{amount}}",
4234
+ purchases: "{{username}} x{{amount}}",
4235
+ bits: "{{username}} x{{amount}}",
4236
+ kicks: "{{username}} x{{amount}}",
4237
+ raids: "{{username}} x{{amount}}",
4238
+ hosts: "{{username}} x{{amount}}",
4239
+ superstickers: "{{username}} x{{amount}}",
4240
+ superchats: "{{username}} x{{amount}}",
4241
+ redemptions: "{{username}} x{{amount}}",
4242
+ stars: "{{username}} x{{amount}}",
4243
+ fans: "{{username}} x{{amount}}",
4244
+ shares: "{{username}}",
4245
+ spells: "{{username}} x{{amount}}",
4246
+ retweets: "{{username}}",
4247
+ likes: "{{username}}",
4248
+ followers: "{{username}}",
4249
+ others: "{{username}} x{{amount}}",
4250
+ topGifts: "{{username}} x{{amount}}",
4251
+ topBits: "{{username}} x{{amount}}",
4252
+ topKicks: "{{username}} x{{amount}}",
4253
+ topDonations: "{{username}} x{{amount}}",
4254
+ topSuperstickers: "{{username}} x{{amount}}",
4255
+ topSuperchats: "{{username}} x{{amount}}",
4256
+ topRedemptions: "{{username}} x{{amount}}"
4100
4257
  };
4101
4258
  function mapCredits(widget) {
4102
4259
  const v = widget.variables ?? {};
@@ -4104,20 +4261,53 @@ function mapCredits(widget) {
4104
4261
  const reverseFlow = v.direction === "bottom";
4105
4262
  return buildUnit(widget, "credits", {
4106
4263
  content: {
4264
+ version: 1,
4107
4265
  type: "basic",
4108
4266
  title: "Stream Credits",
4109
- subtitle: "",
4110
- speed: 1,
4267
+ titleColor: "#ffffff",
4268
+ subtitle: "A massive thanks to everyone below",
4269
+ subtitleColor: "#ffffff",
4270
+ headerColor: "#ffffff",
4271
+ endMessage: "See you next time!",
4272
+ endMessageColor: "#ffffff",
4273
+ eventColor: "#ffffff",
4111
4274
  loop: false,
4112
- eventsToShow: { ...CREDITS_DEFAULT_EVENTS_TO_SHOW },
4275
+ speed: 1,
4276
+ sectionOrder: [...CREDITS_DEFAULT_SECTION_ORDER],
4277
+ // SE only ships subscribers + gift-subs, so import with just those
4278
+ // enabled. The Manager can re-enable additional sections at any time;
4279
+ // the underlying activity feed is fetched session-wide regardless.
4280
+ eventsToShow: { ...SE_CREDITS_EVENTS_TO_SHOW },
4281
+ topLimits: { ...CREDITS_DEFAULT_TOP_LIMITS },
4282
+ eventNames: { ...CREDITS_DEFAULT_EVENT_NAMES },
4283
+ prefixes: { ...CREDITS_DEFAULT_PREFIXES },
4284
+ templates: { ...CREDITS_DEFAULT_TEMPLATES },
4285
+ daysToShow: "session",
4113
4286
  itemGap,
4114
4287
  reverseFlow,
4288
+ media: {
4289
+ type: "image",
4290
+ content: { src: [], name: [], loop: true, playAudio: true, volume: 1 }
4291
+ },
4292
+ audio: {
4293
+ content: { src: [], name: [], loop: false, volume: 1 }
4294
+ },
4115
4295
  // SE-specific knobs preserved as provenance; no direct Lumia equivalent for
4116
4296
  // these. `chosenFilter` would narrow eventsToShow if we had a clean mapping;
4117
4297
  // `separator` would join entries but Lumia credits uses sectioned cards instead.
4118
4298
  se_chosen_filter: v.chosenFilter,
4119
4299
  se_separator: v.separator,
4120
4300
  se_amount: v.amount
4301
+ },
4302
+ css: {
4303
+ fontSize: 30,
4304
+ lineHeight: 1,
4305
+ fontFamily: "Roboto",
4306
+ fontWeight: "normal",
4307
+ color: "#ffffff",
4308
+ textShadow: "",
4309
+ textAlign: "center",
4310
+ background: "transparent"
4121
4311
  }
4122
4312
  });
4123
4313
  }
@@ -4560,6 +4750,11 @@ function mapHypetrain(widget) {
4560
4750
  levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
4561
4751
  trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
4562
4752
  audioVolume: 0.5,
4753
+ // Match native defaults so SE-imported trains get the same polished
4754
+ // sprite pack the native module ships with.
4755
+ trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
4756
+ rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
4757
+ infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
4563
4758
  // SE config preserved verbatim as provenance — no Lumia equivalent for
4564
4759
  // most of these knobs (per-widget animation tunables, sound URLs the
4565
4760
  // streamer might want to migrate manually, etc.). Users can inspect
package/dist/se-import.js CHANGED
@@ -226,11 +226,11 @@ var LUMIA_DEFAULT_SIZES = {
226
226
  audio: { width: 200, height: 200 },
227
227
  slideshow: { width: 400, height: 220 },
228
228
  alert: { width: 700, height: 600 },
229
- // 80 was too short for the SE-style slim goal layout (label + bar + "Ends
230
- // in" sublabel needs ~125 px of vertical space). Bumped so editor-canvas
231
- // rendering (which doesn't honor autoHeight) doesn't clip the sublabel
232
- // after import.
233
- goal: { width: 740, height: 130 },
229
+ // Sized for the SE-style slim layout — title (~24px line) + tight gap +
230
+ // 1.7em bar + optional "Ends in" sublabel collapses to ~70-85 px. 90 gives
231
+ // a small safety margin for larger fonts without leaving dead space in the
232
+ // editor canvas (which doesn't honor the layer's `autoHeight: true`).
233
+ goal: { width: 740, height: 90 },
234
234
  timer: { width: 740, height: 80 },
235
235
  chatbox: { width: 375, height: 470 },
236
236
  eventlist: { width: 375, height: 470 },
@@ -267,10 +267,20 @@ function translateVariationCondition(seType, seCondition, requirement) {
267
267
  return { conditionType: "RANDOM", condition: 100 };
268
268
  }
269
269
  var LUMIA_LAYOUTS = /* @__PURE__ */ new Set(["column", "row", "onlyImage", "onlyText", "imageOver", "textOver"]);
270
+ var SE_LAYOUT_ALIASES = {
271
+ behind: "textOver"
272
+ };
270
273
  function translateLayout(seLayout) {
271
- if (seLayout && LUMIA_LAYOUTS.has(seLayout)) return seLayout;
274
+ if (!seLayout) return "column";
275
+ const alias = SE_LAYOUT_ALIASES[seLayout];
276
+ if (alias) return alias;
277
+ if (LUMIA_LAYOUTS.has(seLayout)) return seLayout;
272
278
  return "column";
273
279
  }
280
+ function seLayoutUsesPushText(seLayout) {
281
+ if (!seLayout) return true;
282
+ return seLayout !== "behind";
283
+ }
274
284
 
275
285
  // src/se-import/mappers/build.ts
276
286
  import { nanoid } from "nanoid";
@@ -481,7 +491,7 @@ function buildAlertEvent(seEvent, seEventType) {
481
491
  const importedTextShadow = css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px";
482
492
  const textShadow = shadowEnabled ? importedTextShadow : "none";
483
493
  const marginTopRaw = Number(css["margin-top"]);
484
- const pushTextUp = Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
494
+ const pushTextUp = seLayoutUsesPushText(seEvent.layout) && Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
485
495
  const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType, seEvent)).filter((v) => v != null);
486
496
  const defaultDuration = (seEventType && SE_DEFAULT_DURATION_BY_EVENT[seEventType]) ?? 8;
487
497
  return {
@@ -611,25 +621,52 @@ function deepMergeSeSettings(parent, variation) {
611
621
  }
612
622
  return out;
613
623
  }
624
+ function pickBaseAlertFields(event) {
625
+ const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
626
+ return base;
627
+ }
614
628
  function mapAlertBox(widget, opts = {}) {
615
629
  const v = widget.variables ?? {};
616
630
  const events = {};
617
631
  const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
632
+ let firstEvent = null;
618
633
  for (const seKey of eventKeys) {
619
634
  const ev = v[seKey];
620
635
  if (!ev || typeof ev !== "object") continue;
621
636
  const lumiaAlert = SE_EVENT_TO_LUMIA_ALERT[seKey];
622
637
  if (!lumiaAlert) continue;
623
- events[lumiaAlert] = buildAlertEvent(ev, seKey);
638
+ const built = buildAlertEvent(ev, seKey);
639
+ events[lumiaAlert] = built;
640
+ if (firstEvent == null) firstEvent = built;
624
641
  }
625
- return buildUnit(widget, "alert", { alert: { events } });
642
+ const baseAlert = firstEvent ? pickBaseAlertFields(firstEvent) : {};
643
+ return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
626
644
  }
627
645
  function mapSingleAlert(widget, lumiaAlertKey) {
628
646
  const v = widget.variables ?? {};
647
+ const topText = widget.text;
648
+ const merged = { ...v };
649
+ if (topText) {
650
+ const inner = v.text ?? {};
651
+ const innerCss = inner.css ?? {};
652
+ const topCss = topText.css ?? {};
653
+ merged.text = {
654
+ ...inner,
655
+ message: inner.message ?? topText.value ?? "",
656
+ enableShadow: inner.enableShadow ?? topText.enableShadow,
657
+ // Top-level SE CSS is the source of truth for Extra Life / DonorDrive
658
+ // (which omit `variables.text` entirely). For Tiltify both are populated
659
+ // with the same values; letting the nested `variables.text.css` win
660
+ // preserves any per-event overrides the streamer might have set.
661
+ css: { ...topCss, ...innerCss }
662
+ };
663
+ }
664
+ const built = buildAlertEvent(merged);
629
665
  const events = {
630
- [lumiaAlertKey]: buildAlertEvent(v)
666
+ [lumiaAlertKey]: built
631
667
  };
632
- return buildUnit(widget, "alert", { alert: { events } });
668
+ const baseAlert = pickBaseAlertFields(built);
669
+ return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
633
670
  }
634
671
 
635
672
  // src/se-import/mappers/goal.ts
@@ -645,9 +682,17 @@ function currentForListener(listener) {
645
682
  if (listener.startsWith("merch-")) return "{{fourthwall_total_order_count}}";
646
683
  return "0";
647
684
  }
685
+ function charityGoalVariables(widgetType) {
686
+ if (widgetType === "se-widget-tiltify-goal") return { current: "{{tiltify_total_raised=0}}", targetGoal: "{{tiltify_goal_amount=100}}" };
687
+ if (widgetType === "se-widget-extralife-goal") return { current: "{{extralife_total_raised=0}}", targetGoal: "{{extralife_goal_amount=100}}" };
688
+ if (widgetType === "se-widget-donor-drive-goal") return { current: "{{donordrive_total_raised=0}}", targetGoal: "{{donordrive_goal_amount=100}}" };
689
+ return null;
690
+ }
648
691
  function mapGoal(widget) {
649
692
  const v = widget.variables ?? {};
650
693
  const listener = v.goalListener ?? (typeof widget.listener === "string" ? widget.listener : null);
694
+ const charity = charityGoalVariables(widget.type);
695
+ const seTextCss = widget.text?.css ?? {};
651
696
  return buildUnit(widget, "goal", {
652
697
  content: {
653
698
  version: 1,
@@ -659,11 +704,13 @@ function mapGoal(widget) {
659
704
  label: translateSeText(v.title ?? "Goal"),
660
705
  subLabel: "",
661
706
  // The Goal module renders this template literal with {{current}} + {{goal}} substituted.
662
- display: "{{current}} / {{goal}}",
707
+ // Charity goals track donation amounts — prefix `$` so the rendered bar reads
708
+ // "$0 / $100" the way SE's charity goals do, instead of bare integers.
709
+ display: charity ? "${{current}} / ${{goal}}" : "{{current}} / {{goal}}",
663
710
  // `current` is a template variable reference (resolved at render time from session variables).
664
711
  // `targetGoal` is the numeric ceiling. Both required by Goal/index.tsx:28-29.
665
- current: currentForListener(listener),
666
- targetGoal: String(v.goalAmount ?? 100),
712
+ current: charity ? charity.current : currentForListener(listener),
713
+ targetGoal: charity ? charity.targetGoal : String(v.goalAmount ?? 100),
667
714
  image: "",
668
715
  audio: { decrement: "", increment: "", goal: "", goalFailed: "", volume: 1 },
669
716
  // `null` (rather than `''`) is the cleaner contract — Goal/index.tsx:11's
@@ -690,13 +737,13 @@ function mapGoal(widget) {
690
737
  // `content.border` and `content.borderRadius`. Writing them to
691
738
  // module.css just plopped useless transparent-0px rules on the
692
739
  // outer .overlay-module wrapper.
693
- lineHeight: 1,
694
- fontSize: 30,
695
- textAlign: "center",
696
- fontFamily: "Roboto",
697
- fontWeight: "bold",
698
- color: "#ffffff",
699
- textShadow: "rgb(0, 0, 0) 1px 1px 1px",
740
+ lineHeight: seTextCss["line-height"] ?? 1,
741
+ fontSize: seTextCss["font-size"] ?? 30,
742
+ textAlign: seTextCss["text-align"] ?? "center",
743
+ fontFamily: seTextCss["font-family"] ?? "Roboto",
744
+ fontWeight: seTextCss["font-weight"] ?? "bold",
745
+ color: seTextCss.color ?? "#ffffff",
746
+ textShadow: seTextCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
700
747
  background: "transparent"
701
748
  }
702
749
  });
@@ -955,33 +1002,143 @@ function mapEventList(widget) {
955
1002
  }
956
1003
  });
957
1004
  }
958
- var CREDITS_DEFAULT_EVENTS_TO_SHOW = {
1005
+ var SE_CREDITS_EVENTS_TO_SHOW = {
959
1006
  mods: false,
960
1007
  subscribers: true,
961
1008
  gifts: true,
962
- donations: true,
963
- purchases: true,
964
- bits: true,
965
- kicks: true,
966
- raids: true,
967
- hosts: true,
968
- superstickers: true,
969
- superchats: true,
970
- redemptions: true,
971
- stars: true,
972
- fans: true,
1009
+ donations: false,
1010
+ purchases: false,
1011
+ bits: false,
1012
+ kicks: false,
1013
+ raids: false,
1014
+ hosts: false,
1015
+ superstickers: false,
1016
+ superchats: false,
1017
+ redemptions: false,
1018
+ stars: false,
1019
+ fans: false,
973
1020
  shares: false,
974
- spells: true,
1021
+ spells: false,
975
1022
  likes: false,
976
- followers: true,
1023
+ followers: false,
977
1024
  others: false,
978
- topGifts: true,
979
- topBits: true,
980
- topKicks: true,
981
- topDonations: true,
982
- topSuperstickers: true,
983
- topSuperchats: true,
984
- topRedemptions: true
1025
+ topGifts: false,
1026
+ topBits: false,
1027
+ topKicks: false,
1028
+ topDonations: false,
1029
+ topSuperstickers: false,
1030
+ topSuperchats: false,
1031
+ topRedemptions: false
1032
+ };
1033
+ var CREDITS_DEFAULT_SECTION_ORDER = [
1034
+ "topGifts",
1035
+ "topBits",
1036
+ "topKicks",
1037
+ "topDonations",
1038
+ "topSuperstickers",
1039
+ "topSuperchats",
1040
+ "topRedemptions",
1041
+ "subscribers",
1042
+ "gifts",
1043
+ "followers",
1044
+ "donations",
1045
+ "bits",
1046
+ "kicks",
1047
+ "raids",
1048
+ "hosts",
1049
+ "superstickers",
1050
+ "superchats",
1051
+ "redemptions",
1052
+ "others"
1053
+ ];
1054
+ var CREDITS_DEFAULT_TOP_LIMITS = {
1055
+ topGifts: 5,
1056
+ topBits: 5,
1057
+ topKicks: 5,
1058
+ topDonations: 5,
1059
+ topSuperstickers: 5,
1060
+ topSuperchats: 5,
1061
+ topRedemptions: 5
1062
+ };
1063
+ var CREDITS_DEFAULT_EVENT_NAMES = {
1064
+ mods: "Mods (Total {{amount}})",
1065
+ topGifts: "Top Gifters",
1066
+ topBits: "Top Cheerers",
1067
+ topKicks: "Top Kicks",
1068
+ topDonations: "Top Donators",
1069
+ topSuperstickers: "Top Super Stickers",
1070
+ topSuperchats: "Top Super Chatters",
1071
+ topRedemptions: "Top Redeemers",
1072
+ subscribers: "Subscribers (Total {{amount}})",
1073
+ gifts: "Gifts (Total {{amount}})",
1074
+ donations: "Donations (Total {{amount}})",
1075
+ purchases: "Purchases (Total {{amount}})",
1076
+ bits: "Cheers (Total {{amount}})",
1077
+ kicks: "Kicks (Total {{amount}})",
1078
+ superstickers: "Super Stickers (Total {{amount}})",
1079
+ superchats: "Super Chatters (Total {{amount}})",
1080
+ redemptions: "Redemptions (Total {{amount}})",
1081
+ stars: "Stars (Total {{amount}})",
1082
+ fans: "Fans (Total {{amount}})",
1083
+ shares: "Shares (Total {{amount}})",
1084
+ raids: "Raids (Total {{amount}})",
1085
+ hosts: "Hosts (Total {{amount}})",
1086
+ spells: "Spells (Total {{amount}})",
1087
+ retweets: "Retweets (Total {{amount}})",
1088
+ likes: "Likes (Total {{amount}})",
1089
+ followers: "Followers (Total {{amount}})",
1090
+ others: "Other (Total {{amount}})"
1091
+ };
1092
+ var CREDITS_DEFAULT_PREFIXES = {
1093
+ mods: "x",
1094
+ subscribers: "x",
1095
+ gifts: "x",
1096
+ donations: "x",
1097
+ purchases: "x",
1098
+ bits: "x",
1099
+ kicks: "x",
1100
+ superstickers: "x",
1101
+ superchats: "x",
1102
+ redemptions: "x",
1103
+ stars: "x",
1104
+ fans: "x",
1105
+ shares: "x",
1106
+ raids: "x",
1107
+ hosts: "x",
1108
+ spells: "x",
1109
+ retweets: "x",
1110
+ likes: "x",
1111
+ followers: "",
1112
+ others: ""
1113
+ };
1114
+ var CREDITS_DEFAULT_TEMPLATES = {
1115
+ mods: "{{username}}",
1116
+ subscribers: "{{username}} x{{amount}}",
1117
+ gifts: "{{username}} x{{amount}}",
1118
+ donations: "{{username}} x{{amount}}",
1119
+ purchases: "{{username}} x{{amount}}",
1120
+ bits: "{{username}} x{{amount}}",
1121
+ kicks: "{{username}} x{{amount}}",
1122
+ raids: "{{username}} x{{amount}}",
1123
+ hosts: "{{username}} x{{amount}}",
1124
+ superstickers: "{{username}} x{{amount}}",
1125
+ superchats: "{{username}} x{{amount}}",
1126
+ redemptions: "{{username}} x{{amount}}",
1127
+ stars: "{{username}} x{{amount}}",
1128
+ fans: "{{username}} x{{amount}}",
1129
+ shares: "{{username}}",
1130
+ spells: "{{username}} x{{amount}}",
1131
+ retweets: "{{username}}",
1132
+ likes: "{{username}}",
1133
+ followers: "{{username}}",
1134
+ others: "{{username}} x{{amount}}",
1135
+ topGifts: "{{username}} x{{amount}}",
1136
+ topBits: "{{username}} x{{amount}}",
1137
+ topKicks: "{{username}} x{{amount}}",
1138
+ topDonations: "{{username}} x{{amount}}",
1139
+ topSuperstickers: "{{username}} x{{amount}}",
1140
+ topSuperchats: "{{username}} x{{amount}}",
1141
+ topRedemptions: "{{username}} x{{amount}}"
985
1142
  };
986
1143
  function mapCredits(widget) {
987
1144
  const v = widget.variables ?? {};
@@ -989,20 +1146,53 @@ function mapCredits(widget) {
989
1146
  const reverseFlow = v.direction === "bottom";
990
1147
  return buildUnit(widget, "credits", {
991
1148
  content: {
1149
+ version: 1,
992
1150
  type: "basic",
993
1151
  title: "Stream Credits",
994
- subtitle: "",
995
- speed: 1,
1152
+ titleColor: "#ffffff",
1153
+ subtitle: "A massive thanks to everyone below",
1154
+ subtitleColor: "#ffffff",
1155
+ headerColor: "#ffffff",
1156
+ endMessage: "See you next time!",
1157
+ endMessageColor: "#ffffff",
1158
+ eventColor: "#ffffff",
996
1159
  loop: false,
997
- eventsToShow: { ...CREDITS_DEFAULT_EVENTS_TO_SHOW },
1160
+ speed: 1,
1161
+ sectionOrder: [...CREDITS_DEFAULT_SECTION_ORDER],
1162
+ // SE only ships subscribers + gift-subs, so import with just those
1163
+ // enabled. The Manager can re-enable additional sections at any time;
1164
+ // the underlying activity feed is fetched session-wide regardless.
1165
+ eventsToShow: { ...SE_CREDITS_EVENTS_TO_SHOW },
1166
+ topLimits: { ...CREDITS_DEFAULT_TOP_LIMITS },
1167
+ eventNames: { ...CREDITS_DEFAULT_EVENT_NAMES },
1168
+ prefixes: { ...CREDITS_DEFAULT_PREFIXES },
1169
+ templates: { ...CREDITS_DEFAULT_TEMPLATES },
1170
+ daysToShow: "session",
998
1171
  itemGap,
999
1172
  reverseFlow,
1173
+ media: {
1174
+ type: "image",
1175
+ content: { src: [], name: [], loop: true, playAudio: true, volume: 1 }
1176
+ },
1177
+ audio: {
1178
+ content: { src: [], name: [], loop: false, volume: 1 }
1179
+ },
1000
1180
  // SE-specific knobs preserved as provenance; no direct Lumia equivalent for
1001
1181
  // these. `chosenFilter` would narrow eventsToShow if we had a clean mapping;
1002
1182
  // `separator` would join entries but Lumia credits uses sectioned cards instead.
1003
1183
  se_chosen_filter: v.chosenFilter,
1004
1184
  se_separator: v.separator,
1005
1185
  se_amount: v.amount
1186
+ },
1187
+ css: {
1188
+ fontSize: 30,
1189
+ lineHeight: 1,
1190
+ fontFamily: "Roboto",
1191
+ fontWeight: "normal",
1192
+ color: "#ffffff",
1193
+ textShadow: "",
1194
+ textAlign: "center",
1195
+ background: "transparent"
1006
1196
  }
1007
1197
  });
1008
1198
  }
@@ -1445,6 +1635,11 @@ function mapHypetrain(widget) {
1445
1635
  levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
1446
1636
  trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
1447
1637
  audioVolume: 0.5,
1638
+ // Match native defaults so SE-imported trains get the same polished
1639
+ // sprite pack the native module ships with.
1640
+ trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
1641
+ rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
1642
+ infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
1448
1643
  // SE config preserved verbatim as provenance — no Lumia equivalent for
1449
1644
  // most of these knobs (per-widget animation tunables, sound URLs the
1450
1645
  // streamer might want to migrate manually, etc.). Users can inspect
package/package.json CHANGED
@@ -1,133 +1,133 @@
1
1
  {
2
- "name": "@lumiastream/ui",
3
- "version": "0.2.8-alpha.12",
4
- "author": "Lumia Stream",
5
- "license": "ISC",
6
- "description": "Lumia UI Kit",
7
- "packageManager": "npm@11.9.0",
8
- "type": "module",
9
- "main": "dist/index.js",
10
- "module": "dist/index.js",
11
- "types": "dist/index.d.ts",
12
- "files": [
13
- "dist"
14
- ],
15
- "exports": {
16
- ".": {
17
- "types": "./dist/index.d.ts",
18
- "import": "./dist/index.js"
19
- },
20
- "./components": {
21
- "types": "./dist/components.d.ts",
22
- "import": "./dist/components.js"
23
- },
24
- "./LSButton": {
25
- "types": "./dist/LSButton.d.ts",
26
- "import": "./dist/LSButton.js"
27
- },
28
- "./LSCheckbox": {
29
- "types": "./dist/LSCheckbox.d.ts",
30
- "import": "./dist/LSCheckbox.js"
31
- },
32
- "./LSColorPicker": {
33
- "types": "./dist/LSColorPicker.d.ts",
34
- "import": "./dist/LSColorPicker.js"
35
- },
36
- "./LSDatePicker": {
37
- "types": "./dist/LSDatePicker.d.ts",
38
- "import": "./dist/LSDatePicker.js"
39
- },
40
- "./LSFontPicker": {
41
- "types": "./dist/LSFontPicker.d.ts",
42
- "import": "./dist/LSFontPicker.js"
43
- },
44
- "./LSInput": {
45
- "types": "./dist/LSInput.d.ts",
46
- "import": "./dist/LSInput.js"
47
- },
48
- "./LSMultiSelect": {
49
- "types": "./dist/LSMultiSelect.d.ts",
50
- "import": "./dist/LSMultiSelect.js"
51
- },
52
- "./LSRadio": {
53
- "types": "./dist/LSRadio.d.ts",
54
- "import": "./dist/LSRadio.js"
55
- },
56
- "./LSSelect": {
57
- "types": "./dist/LSSelect.d.ts",
58
- "import": "./dist/LSSelect.js"
59
- },
60
- "./LSSliderInput": {
61
- "types": "./dist/LSSliderInput.d.ts",
62
- "import": "./dist/LSSliderInput.js"
63
- },
64
- "./LSTextField": {
65
- "types": "./dist/LSTextField.d.ts",
66
- "import": "./dist/LSTextField.js"
67
- },
68
- "./LSVariableInputField": {
69
- "types": "./dist/LSVariableInputField.d.ts",
70
- "import": "./dist/LSVariableInputField.js"
71
- },
72
- "./utils": {
73
- "types": "./dist/utils.d.ts",
74
- "import": "./dist/utils.js"
75
- },
76
- "./utils/chatMedia": {
77
- "types": "./dist/utils/chatMedia.d.ts",
78
- "import": "./dist/utils/chatMedia.js"
79
- },
80
- "./se-import": {
81
- "types": "./dist/se-import.d.ts",
82
- "import": "./dist/se-import.js"
83
- }
84
- },
85
- "scripts": {
86
- "start": "storybook dev -p 6006",
87
- "build": "tsup",
88
- "build-storybook": "storybook build",
89
- "watch": "tsup --watch",
90
- "release": "changeset && changeset tag && npm run build && npm publish",
91
- "prepublishOnly": "npm run build",
92
- "postpublish": "npm cache clean --force && node ./scripts/postpublish-install.mjs"
93
- },
94
- "peerDependencies": {
95
- "@codemirror/autocomplete": "^6.0.0",
96
- "@emotion/react": "^11.14.0",
97
- "@emotion/styled": "^11.14.1",
98
- "@mui/icons-material": "^9.0.0",
99
- "@mui/material": "^9.0.0",
100
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
101
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
102
- "react-hook-form": "^7.62.0"
103
- },
104
- "devDependencies": {
105
- "@codemirror/autocomplete": "^6.20.1",
106
- "@emotion/react": "^11.14.0",
107
- "@emotion/styled": "^11.14.1",
108
- "@mui/icons-material": "^9.0.0",
109
- "@mui/material": "^9.0.0",
110
- "@storybook/builder-vite": "^10.2.17",
111
- "@storybook/react-vite": "^10.2.17",
112
- "@types/node": "^25.5.0",
113
- "@types/react": "^19.2.14",
114
- "@types/react-dom": "19.2.3",
115
- "postcss": "^8.5.14",
116
- "react": "^19.2.0",
117
- "react-dom": "^19.2.0",
118
- "react-hook-form": "^7.73.1",
119
- "sass-embedded": "^1.99.0",
120
- "storybook": "^10.2.17",
121
- "tsup": "^8.5.1",
122
- "typescript": "^5.9.3",
123
- "vite": "^8.0.8"
124
- },
125
- "dependencies": {
126
- "@lumiastream/lumia-translations": "1.15.7",
127
- "@lumiastream/lumia-types": "^3.3.7-alpha.2",
128
- "classnames": "^2.5.1",
129
- "globals": "^17.4.0",
130
- "nanoid": "^5.1.11",
131
- "react-best-gradient-color-picker": "^3.0.14"
132
- }
2
+ "name": "@lumiastream/ui",
3
+ "version": "0.2.8-alpha.13",
4
+ "author": "Lumia Stream",
5
+ "license": "ISC",
6
+ "description": "Lumia UI Kit",
7
+ "packageManager": "npm@11.9.0",
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./components": {
21
+ "types": "./dist/components.d.ts",
22
+ "import": "./dist/components.js"
23
+ },
24
+ "./LSButton": {
25
+ "types": "./dist/LSButton.d.ts",
26
+ "import": "./dist/LSButton.js"
27
+ },
28
+ "./LSCheckbox": {
29
+ "types": "./dist/LSCheckbox.d.ts",
30
+ "import": "./dist/LSCheckbox.js"
31
+ },
32
+ "./LSColorPicker": {
33
+ "types": "./dist/LSColorPicker.d.ts",
34
+ "import": "./dist/LSColorPicker.js"
35
+ },
36
+ "./LSDatePicker": {
37
+ "types": "./dist/LSDatePicker.d.ts",
38
+ "import": "./dist/LSDatePicker.js"
39
+ },
40
+ "./LSFontPicker": {
41
+ "types": "./dist/LSFontPicker.d.ts",
42
+ "import": "./dist/LSFontPicker.js"
43
+ },
44
+ "./LSInput": {
45
+ "types": "./dist/LSInput.d.ts",
46
+ "import": "./dist/LSInput.js"
47
+ },
48
+ "./LSMultiSelect": {
49
+ "types": "./dist/LSMultiSelect.d.ts",
50
+ "import": "./dist/LSMultiSelect.js"
51
+ },
52
+ "./LSRadio": {
53
+ "types": "./dist/LSRadio.d.ts",
54
+ "import": "./dist/LSRadio.js"
55
+ },
56
+ "./LSSelect": {
57
+ "types": "./dist/LSSelect.d.ts",
58
+ "import": "./dist/LSSelect.js"
59
+ },
60
+ "./LSSliderInput": {
61
+ "types": "./dist/LSSliderInput.d.ts",
62
+ "import": "./dist/LSSliderInput.js"
63
+ },
64
+ "./LSTextField": {
65
+ "types": "./dist/LSTextField.d.ts",
66
+ "import": "./dist/LSTextField.js"
67
+ },
68
+ "./LSVariableInputField": {
69
+ "types": "./dist/LSVariableInputField.d.ts",
70
+ "import": "./dist/LSVariableInputField.js"
71
+ },
72
+ "./utils": {
73
+ "types": "./dist/utils.d.ts",
74
+ "import": "./dist/utils.js"
75
+ },
76
+ "./utils/chatMedia": {
77
+ "types": "./dist/utils/chatMedia.d.ts",
78
+ "import": "./dist/utils/chatMedia.js"
79
+ },
80
+ "./se-import": {
81
+ "types": "./dist/se-import.d.ts",
82
+ "import": "./dist/se-import.js"
83
+ }
84
+ },
85
+ "scripts": {
86
+ "start": "storybook dev -p 6006",
87
+ "build": "tsup",
88
+ "build-storybook": "storybook build",
89
+ "watch": "tsup --watch",
90
+ "release": "changeset && changeset tag && npm run build && npm publish",
91
+ "prepublishOnly": "npm run build",
92
+ "postpublish": "npm cache clean --force && node ./scripts/postpublish-install.mjs"
93
+ },
94
+ "peerDependencies": {
95
+ "@codemirror/autocomplete": "^6.0.0",
96
+ "@emotion/react": "^11.14.0",
97
+ "@emotion/styled": "^11.14.1",
98
+ "@mui/icons-material": "^9.0.0",
99
+ "@mui/material": "^9.0.0",
100
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
101
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
102
+ "react-hook-form": "^7.62.0"
103
+ },
104
+ "devDependencies": {
105
+ "@codemirror/autocomplete": "^6.20.1",
106
+ "@emotion/react": "^11.14.0",
107
+ "@emotion/styled": "^11.14.1",
108
+ "@mui/icons-material": "^9.0.0",
109
+ "@mui/material": "^9.0.0",
110
+ "@storybook/builder-vite": "^10.2.17",
111
+ "@storybook/react-vite": "^10.2.17",
112
+ "@types/node": "^25.5.0",
113
+ "@types/react": "^19.2.14",
114
+ "@types/react-dom": "19.2.3",
115
+ "postcss": "^8.5.14",
116
+ "react": "^19.2.0",
117
+ "react-dom": "^19.2.0",
118
+ "react-hook-form": "^7.73.1",
119
+ "sass-embedded": "^1.99.0",
120
+ "storybook": "^10.2.17",
121
+ "tsup": "^8.5.1",
122
+ "typescript": "^5.9.3",
123
+ "vite": "^8.0.8"
124
+ },
125
+ "dependencies": {
126
+ "@lumiastream/lumia-translations": "1.15.7",
127
+ "@lumiastream/lumia-types": "^3.3.7-alpha.2",
128
+ "classnames": "^2.5.1",
129
+ "globals": "^17.4.0",
130
+ "nanoid": "^5.1.11",
131
+ "react-best-gradient-color-picker": "^3.0.14"
132
+ }
133
133
  }