@lumiastream/ui 0.2.8-alpha.26 → 0.2.8-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3133,31 +3133,86 @@ var SE_TO_LUMIA_PLACEHOLDER = {
3133
3133
  };
3134
3134
  var TWITCH_CONTEXTS = {
3135
3135
  "follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
3136
+ // Three SE listeners with genuinely distinct semantics — preserve the
3137
+ // distinction in the variable mapping so a widget configured for "Latest
3138
+ // new subscriber" doesn't render the latest *resub* (and vice versa).
3139
+ // `subscriber-latest` is the umbrella (anyone subscribing); the other two
3140
+ // are subtype-specific. The dedicated TWITCH_LAST_{NEW_SUBSCRIBER,
3141
+ // RESUBSCRIBER} variables are written by the Twitch event pipeline on
3142
+ // the matching sub.type event.
3136
3143
  "subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3137
- "subscriber-new-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3138
- "subscriber-resub-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3139
- "subscriber-gifted-latest": { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
3144
+ "subscriber-new-latest": { name: "twitch_last_new_subscriber", username: "twitch_last_new_subscriber" },
3145
+ "subscriber-resub-latest": { name: "twitch_last_resubscriber", username: "twitch_last_resubscriber" },
3146
+ // `subscriber-gifted-latest`: SE uses `{sender}` for the giver and `{name}` for
3147
+ // the recipient (e.g. "{sender} gifted to {name}"). The two roles must point
3148
+ // at distinct Lumia variables — collapsing both onto `twitch_last_gifter`
3149
+ // rendered "Mr.Beast gifted to Mr.Beast" while the actual recipient was
3150
+ // nowhere. `sender` keeps the gifter; `name` / `username` switch to the
3151
+ // dedicated recipient variable `twitch_last_gifted`. Amount stays on the
3152
+ // gifter (it's the count of subs in that drop, owned by the gifter).
3153
+ "subscriber-gifted-latest": { name: "twitch_last_gifted", username: "twitch_last_gifted", sender: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
3140
3154
  "subscriber-alltime-gifter": { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" },
3141
3155
  "cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
3142
3156
  "tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
3143
3157
  latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
3144
3158
  "raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
3145
- // Session counters: count/amount = the running session count.
3159
+ // Period counters: count/amount = the running counter for that window.
3160
+ // SE lets the streamer pick session / week / month / total on the same widget
3161
+ // type; the chosen period lands in `widget.listener` (e.g. `cheer-week`).
3162
+ // Each entry below mirrors a row in the Lumia variables table — the previous
3163
+ // table only carried session+total and broke any widget configured for
3164
+ // week/month because `{amount}` fell through to the unscoped placeholder
3165
+ // and rendered blank.
3146
3166
  "follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
3147
- "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
3148
- "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
3149
- "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
3150
- // Totals (all-time): count/amount = total counter.
3167
+ "follower-week": { amount: "twitch_week_follower_count", count: "twitch_week_follower_count" },
3168
+ "follower-month": { amount: "twitch_month_follower_count", count: "twitch_month_follower_count" },
3151
3169
  "follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
3170
+ "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
3171
+ "subscriber-week": { amount: "twitch_week_subscriber_count", count: "twitch_week_subscriber_count" },
3172
+ "subscriber-month": { amount: "twitch_month_subscriber_count", count: "twitch_month_subscriber_count" },
3152
3173
  "subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
3174
+ "subscriber-new-session": { amount: "twitch_session_new_subscribers_count", count: "twitch_session_new_subscribers_count" },
3175
+ "subscriber-resub-session": { amount: "twitch_session_resub_subscribers_count", count: "twitch_session_resub_subscribers_count" },
3176
+ "subscriber-gifted-session": { amount: "twitch_session_gifted_subscribers_count", count: "twitch_session_gifted_subscribers_count" },
3177
+ "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
3178
+ "cheer-week": { amount: "twitch_week_bits_count", count: "twitch_week_bits_count" },
3179
+ "cheer-month": { amount: "twitch_month_bits_count", count: "twitch_month_bits_count" },
3153
3180
  "cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
3181
+ "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
3182
+ "tip-week": { amount: "week_donation_amount", count: "week_donation_count", currency: "week_donation_amount_currency_symbol" },
3183
+ "tip-month": { amount: "month_donation_amount", count: "month_donation_count", currency: "month_donation_amount_currency_symbol" },
3154
3184
  "tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
3155
3185
  "tip-count": { amount: "donation_count", count: "donation_count" },
3156
- // Top cheer / cheerer / donator session readouts.
3157
- "cheer-session-top-donation": { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
3186
+ // Top cheer / cheerer / donator readouts — per period. SE's `se-widget-top-*`
3187
+ // widgets encode the chosen period (session / weekly / monthly / alltime) in
3188
+ // the widget's `listener` field. Without these entries the user's `{amount}`
3189
+ // / `{name}` template tokens fall through to the unscoped placeholder map
3190
+ // (`{{amount}}` only resolves inside alert variations) and render blank.
3191
+ //
3192
+ // `*-top-donation` and `*-top-donator` are paired: SE nominally treats them
3193
+ // as "top single event" vs "top cumulative contributor", but the data
3194
+ // rendered for both lives in the same Lumia variable pair (`*_top_donator`
3195
+ // / `*_top_donator_amount`, `twitch_*_top_cheerer` / `..._amount`). SE
3196
+ // widgets routinely use `{name}` against the `-donation` listener to mean
3197
+ // "the user who made the top donation", so the two rows below carry the
3198
+ // same name/username mapping — anything else falls through to the unscoped
3199
+ // `{{username}}` and renders blank.
3200
+ "cheer-session-top-donation": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
3158
3201
  "cheer-session-top-donator": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" },
3159
- "tip-session-top-donation": { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
3202
+ "cheer-weekly-top-donation": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
3203
+ "cheer-weekly-top-donator": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
3204
+ "cheer-monthly-top-donation": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
3205
+ "cheer-monthly-top-donator": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
3206
+ "cheer-alltime-top-donation": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
3207
+ "cheer-alltime-top-donator": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
3208
+ "tip-session-top-donation": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
3160
3209
  "tip-session-top-donator": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
3210
+ "tip-weekly-top-donation": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
3211
+ "tip-weekly-top-donator": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
3212
+ "tip-monthly-top-donation": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
3213
+ "tip-monthly-top-donator": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
3214
+ "tip-alltime-top-donation": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
3215
+ "tip-alltime-top-donator": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
3161
3216
  // Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
3162
3217
  // fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
3163
3218
  // when the listener arg lands as that var name we treat it as the wins source.
@@ -3194,7 +3249,11 @@ var KICK_CONTEXTS = {
3194
3249
  "subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3195
3250
  "subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3196
3251
  "subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3197
- "subscriber-gifted-latest": { name: "kick_last_gifter", username: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
3252
+ // Mirror the Twitch split: `{sender}` = giver, `{name}` = recipient.
3253
+ // `kick_last_gifted` was added alongside this mapping; see twitch context
3254
+ // above for the rationale (without the split, SE's "{sender} gifted to
3255
+ // {name}" renders the gifter on both sides).
3256
+ "subscriber-gifted-latest": { name: "kick_last_gifted", username: "kick_last_gifted", sender: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
3198
3257
  "cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
3199
3258
  "raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
3200
3259
  "follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
@@ -3226,9 +3285,17 @@ var YOUTUBE_CONTEXTS = {
3226
3285
  "subscriber-week": { amount: "youtube_week_member_count", count: "youtube_week_member_count" },
3227
3286
  "subscriber-month": { amount: "youtube_month_member_count", count: "youtube_month_member_count" }
3228
3287
  };
3288
+ var RECENT_TO_LATEST_LISTENER = {
3289
+ "follower-recent": "follower-latest",
3290
+ "subscriber-recent": "subscriber-latest",
3291
+ "cheer-recent": "cheer-latest",
3292
+ "tip-recent": "tip-latest",
3293
+ "raid-recent": "raid-latest"
3294
+ };
3229
3295
  function listenerPlaceholderContext(listener, provider = "twitch") {
3296
+ const resolved = RECENT_TO_LATEST_LISTENER[listener] ?? listener;
3230
3297
  const platformTable = provider === "kick" ? KICK_CONTEXTS : provider === "youtube" ? YOUTUBE_CONTEXTS : null;
3231
- return platformTable?.[listener] ?? TWITCH_CONTEXTS[listener] ?? {};
3298
+ return platformTable?.[resolved] ?? TWITCH_CONTEXTS[resolved] ?? {};
3232
3299
  }
3233
3300
  var SE_WIDGET_TYPE_LABELS = {
3234
3301
  "subscriber-latest": "Latest subscriber",
@@ -3264,12 +3331,13 @@ function defaultLabelForSEWidget(widget) {
3264
3331
  const tail = words.slice(1).join(" ");
3265
3332
  return tail ? `${head} ${tail}` : head;
3266
3333
  }
3267
- function translateSeText(input, listener, passthrough, provider) {
3334
+ function translateSeText(input, listener, passthrough, provider, options) {
3268
3335
  if (!input) return "";
3269
3336
  const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
3337
+ const useAlertFallback = (options?.context ?? "alert") === "alert";
3270
3338
  return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
3271
3339
  if (passthrough?.has(raw)) return `{{${raw}}}`;
3272
- const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
3340
+ const mapped = ctx[raw] ?? (useAlertFallback ? SE_TO_LUMIA_PLACEHOLDER[raw] : void 0) ?? raw;
3273
3341
  return `{{${mapped}}}`;
3274
3342
  });
3275
3343
  }
@@ -3313,14 +3381,15 @@ var YOUTUBE_LISTENER_VARS = {
3313
3381
  };
3314
3382
  function listenerToLumiaVariable(listener, provider = "twitch") {
3315
3383
  if (typeof listener !== "string" || !listener) return "username";
3316
- if (provider === "kick" && KICK_LISTENER_VARS[listener]) return KICK_LISTENER_VARS[listener];
3317
- if (provider === "youtube" && YOUTUBE_LISTENER_VARS[listener]) return YOUTUBE_LISTENER_VARS[listener];
3384
+ const resolvedListener = RECENT_TO_LATEST_LISTENER[listener] ?? listener;
3385
+ if (provider === "kick" && KICK_LISTENER_VARS[resolvedListener]) return KICK_LISTENER_VARS[resolvedListener];
3386
+ if (provider === "youtube" && YOUTUBE_LISTENER_VARS[resolvedListener]) return YOUTUBE_LISTENER_VARS[resolvedListener];
3318
3387
  const m = {
3319
3388
  // Latest events
3320
3389
  "follower-latest": "twitch_last_follower",
3321
3390
  "subscriber-latest": "twitch_last_subscriber",
3322
- "subscriber-new-latest": "twitch_last_subscriber",
3323
- "subscriber-resub-latest": "twitch_last_subscriber",
3391
+ "subscriber-new-latest": "twitch_last_new_subscriber",
3392
+ "subscriber-resub-latest": "twitch_last_resubscriber",
3324
3393
  "subscriber-gifted-latest": "twitch_last_gifter",
3325
3394
  "subscriber-alltime-gifter": "twitch_alltime_top_gifter",
3326
3395
  "tip-latest": "latest_donator",
@@ -3371,7 +3440,7 @@ function listenerToLumiaVariable(listener, provider = "twitch") {
3371
3440
  "cheer-alltime-top-donation": "twitch_alltime_top_cheerer_amount",
3372
3441
  "cheer-alltime-top-donator": "twitch_alltime_top_cheerer"
3373
3442
  };
3374
- return m[listener] ?? listener.replace(/-/g, "_");
3443
+ return m[resolvedListener] ?? resolvedListener.replace(/-/g, "_");
3375
3444
  }
3376
3445
  function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
3377
3446
  let widthIsAuto = false;
@@ -3475,9 +3544,16 @@ var LUMIA_DEFAULT_SIZES = {
3475
3544
  // just the card if they want SE's exact footprint.
3476
3545
  songrequest: { width: 480, height: 380 }
3477
3546
  };
3478
- function translateVariationCondition(seType, seCondition, requirement) {
3547
+ function translateVariationCondition(seType, seCondition, requirement, seEventType) {
3479
3548
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
3480
3549
  if (seType === "communityGift") return { conditionType: "GIFT_SUB_GREATER", condition: requirement ?? 0 };
3550
+ const isSubscriberEvent = seEventType === "subscriber";
3551
+ if (isSubscriberEvent && seCondition === "ATLEAST" && requirement != null) {
3552
+ return { conditionType: "SUBSCRIBED_MONTHS_GREATER", condition: requirement };
3553
+ }
3554
+ if (isSubscriberEvent && seCondition === "EXACT" && requirement != null) {
3555
+ return { conditionType: "SUBSCRIBED_MONTHS_EQUAL", condition: requirement };
3556
+ }
3481
3557
  if (seCondition === "EXACT" && requirement != null) return { conditionType: "EQUAL_NUMBER", condition: requirement };
3482
3558
  if (seCondition === "ATLEAST" && requirement != null) {
3483
3559
  return { conditionType: "GREATER_NUMBER", condition: requirement };
@@ -3577,7 +3653,7 @@ function seExtraTextCss(seCss, scrolling) {
3577
3653
  return extra;
3578
3654
  }
3579
3655
  function mapText(widget, ctx) {
3580
- const value = translateSeText(widget.text?.value ?? "");
3656
+ const value = translateSeText(widget.text?.value ?? "", null, void 0, ctx?.provider, { context: "static" });
3581
3657
  const seCss = widget.text?.css ?? {};
3582
3658
  return buildUnit(widget, "text", {
3583
3659
  content: {
@@ -3634,7 +3710,7 @@ function mapReadout(widget, fallbackVar, ctx) {
3634
3710
  const variable = listenerToLumiaVariable(listener, ctx?.provider);
3635
3711
  const userTemplate = widget.text?.value ?? "";
3636
3712
  const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
3637
- const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
3713
+ const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider, { context: "static" }) : `{{${variable}}}`;
3638
3714
  const seCss = widget.text?.css ?? {};
3639
3715
  return buildUnit(widget, "text", {
3640
3716
  content: { value, highlightColor: "inherit" },
@@ -3889,7 +3965,7 @@ function buildAlertEvent(seEvent, seEventType) {
3889
3965
  };
3890
3966
  }
3891
3967
  function buildAlertVariation(v, seEventType, parentSettings) {
3892
- const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
3968
+ const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null, seEventType);
3893
3969
  if (!translated) return null;
3894
3970
  const { variations: _parentVariations, ...parentInheritable } = parentSettings ?? {};
3895
3971
  const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
@@ -4025,7 +4101,8 @@ function mapGoal(widget, ctx) {
4025
4101
  // live `current` floating at the percentage position. See
4026
4102
  // components/Modules/Views/Goal/types.ts:GoalTypes.SLIM.
4027
4103
  type: "slim",
4028
- label: translateSeText(v.title ?? "Goal"),
4104
+ // Static label rendered above the goal bar — not an alert context.
4105
+ label: translateSeText(v.title ?? "Goal", null, void 0, void 0, { context: "static" }),
4029
4106
  subLabel: "",
4030
4107
  // The Goal module renders this template literal with {{current}} + {{goal}} substituted.
4031
4108
  // Charity goals track donation amounts — prefix `$` so the rendered bar reads
@@ -4342,6 +4419,9 @@ function mapEventList(widget, ctx) {
4342
4419
  const filters = computeListenerFilters(widget.listeners, widget.listener);
4343
4420
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
4344
4421
  return buildUnit(widget, "eventlist", {
4422
+ // SE event-list widgets style per-event lines via widget.text.css; the
4423
+ // Lumia eventlist reads font/size/color from module.css.
4424
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
4345
4425
  content: {
4346
4426
  theme: v.theme ?? "simple",
4347
4427
  direction: v.direction ?? "top",
@@ -4625,7 +4705,7 @@ function mapTimer(widget, ctx) {
4625
4705
  const v = widget.variables ?? {};
4626
4706
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
4627
4707
  const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
4628
- const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH);
4708
+ const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, { context: "static" });
4629
4709
  const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
4630
4710
  const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
4631
4711
  const hasValidAbsoluteDate = Number.isFinite(target);
@@ -4697,7 +4777,7 @@ function mapTimer(widget, ctx) {
4697
4777
  endAnimation: "",
4698
4778
  hideAfter: 0,
4699
4779
  highlightColor: "inherit",
4700
- messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? ""),
4780
+ messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? "", null, void 0, void 0, { context: "static" }),
4701
4781
  changeMessageAfter: !!v.changeMessageAfterCountdown,
4702
4782
  countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
4703
4783
  date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
@@ -4775,14 +4855,10 @@ function mapMediaShare(widget, ctx) {
4775
4855
  // `durationSeconds + grace`).
4776
4856
  importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
4777
4857
  },
4778
- css: {
4779
- fontFamily: "Roboto",
4780
- fontWeight: "bold",
4781
- color: "#ffffff",
4782
- textShadow: "rgb(0, 0, 0) 1px 1px 1px",
4783
- background: "transparent",
4784
- fontSize: 18
4785
- }
4858
+ // Lumia songrequest renders the queue list as styled text; pull SE's
4859
+ // widget.text.css so font/size/color carry across (defaults match the
4860
+ // previous hardcoded fallbacks for this widget specifically).
4861
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
4786
4862
  }, ctx);
4787
4863
  }
4788
4864
  function mapKappagen(widget, ctx) {
@@ -4844,25 +4920,47 @@ function mapKappagen(widget, ctx) {
4844
4920
  ctx
4845
4921
  );
4846
4922
  }
4847
- function mapLeaderboardFromVariables(widget, currency, ctx) {
4923
+ var TOP_LIST_SORT_BY = {
4924
+ tips: "higherDonations",
4925
+ cheers: "higherBits",
4926
+ gifts: "higherGifts"
4927
+ };
4928
+ var TOP_LIST_TITLE = {
4929
+ tips: "Top Tippers",
4930
+ cheers: "Top Cheerers",
4931
+ gifts: "Top Gifters"
4932
+ };
4933
+ function mapViewerProfilesLeaderboard(widget, currency, ctx) {
4848
4934
  const v = widget.variables ?? {};
4849
4935
  const count = v.limit ?? v.visibleItems ?? 10;
4850
- const window = v.period === "weekly" ? "week" : v.period === "monthly" ? "month" : "alltime";
4851
- const title = currency === "tips" ? "Top Tippers" : currency === "cheers" ? "Top Cheerers" : "Top Gifters";
4852
- return buildUnit(widget, "loyaltyleaderboard", {
4936
+ return buildUnit(widget, "viewerprofiles", {
4937
+ // Carry SE text styling so the leaderboard keeps the streamer's
4938
+ // chosen font / size / color.
4939
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
4853
4940
  content: {
4854
- source: "variables",
4855
- currency,
4856
- window,
4941
+ sortBy: TOP_LIST_SORT_BY[currency],
4857
4942
  count,
4858
- title,
4943
+ title: TOP_LIST_TITLE[currency],
4859
4944
  showTitle: true,
4860
- showHeader: false,
4861
- showCurrentPoints: false,
4862
- showAllTimePoints: true,
4945
+ showAvatar: true,
4946
+ showPlatform: true,
4947
+ // Hide noisy columns that aren't relevant for a single-metric
4948
+ // top-X list — the streamer picked "top by gifts/tips/cheers"
4949
+ // for a reason. They can re-enable in Settings if they want.
4950
+ showCommands: false,
4951
+ showAlerts: false,
4952
+ showBits: currency === "cheers",
4953
+ showLastSeen: false,
4954
+ showTopActivity: false,
4955
+ showAchievements: false,
4956
+ showAchievementProgress: false,
4957
+ refreshIntervalSeconds: 30,
4863
4958
  itemGap: 8,
4959
+ autoScroll: false,
4864
4960
  // Source-side knobs that don't map cleanly survive under the
4865
- // generic importMeta envelope so the user can re-introspect.
4961
+ // generic importMeta envelope so the user can re-introspect later.
4962
+ // `period` is interesting because viewerprofiles doesn't yet have
4963
+ // a windowed mode — if/when it does, we can promote this.
4866
4964
  importMeta: buildImportMeta(widget, {
4867
4965
  period: v.period,
4868
4966
  offset: v.offset,
@@ -4876,6 +4974,8 @@ function mapGiveaway(widget, ctx) {
4876
4974
  const primary = v.colors?.primary ?? "#393853";
4877
4975
  const accent = v.colors?.accent ?? "#FF4076";
4878
4976
  return buildUnit(widget, "raffle", {
4977
+ // Raffle renders entrants/winner text via module.css.
4978
+ css: mapSeTextCssToModuleCss(widget),
4879
4979
  content: {
4880
4980
  version: 1,
4881
4981
  type: "basic",
@@ -4960,6 +5060,9 @@ function isHypetrainCustomWidget(widget) {
4960
5060
  function mapHypetrain(widget, ctx) {
4961
5061
  const v = widget.variables ?? {};
4962
5062
  return buildUnit(widget, "hypetrain", {
5063
+ // Hypetrain renders level / progress / contributor text — carry SE
5064
+ // styling so an imported hype-train widget keeps its font + accent.
5065
+ css: mapSeTextCssToModuleCss(widget),
4963
5066
  content: {
4964
5067
  // Twitch-native is the right default — SE hype-train widgets always
4965
5068
  // reflected the Twitch native train. Users can flip to 'synthetic'
@@ -5003,6 +5106,47 @@ function mapHypetrain(widget, ctx) {
5003
5106
  }
5004
5107
  }, ctx);
5005
5108
  }
5109
+ function mapEmoteWall(widget, ctx) {
5110
+ const v = widget.variables ?? {};
5111
+ const imageSize = typeof v.emoteSize === "number" ? v.emoteSize : typeof v.size === "number" ? v.size : 40;
5112
+ const maxItemsToShow = typeof v.maxEmotes === "number" && v.maxEmotes > 0 ? v.maxEmotes : typeof v.maxItems === "number" && v.maxItems > 0 ? v.maxItems : 40;
5113
+ const duration = typeof v.duration === "number" && v.duration > 0 ? v.duration : typeof v.decay === "number" && v.decay > 0 ? v.decay : typeof v.decayTime === "number" && v.decayTime > 0 ? v.decayTime : 5;
5114
+ const sites = [];
5115
+ if (v.enableTwitch !== false && v.twitch !== false) sites.push("twitch");
5116
+ if (v.enableBttv !== false && v.bttv !== false) sites.push("bttv");
5117
+ if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
5118
+ if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
5119
+ const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
5120
+ return buildUnit(widget, "emotebox", {
5121
+ content: {
5122
+ version: 1,
5123
+ type: animationType,
5124
+ imageSize,
5125
+ animationTime: duration,
5126
+ duration,
5127
+ maxItemsToShow,
5128
+ delay: typeof v.delay === "number" ? v.delay : 0,
5129
+ sites,
5130
+ background: "transparent",
5131
+ theme: "dark",
5132
+ userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
5133
+ ignoredList: [],
5134
+ excludedAnimations: [],
5135
+ // Emote-wall is chat-driven, not alert-driven — leave alert toggles
5136
+ // off so the widget doesn't double-fire on subscribe/follower events.
5137
+ alertOn: false,
5138
+ alertTriggers: [],
5139
+ alertEmotes: [],
5140
+ alertNumberEmotes: 30,
5141
+ useAnimatedEmotes: true,
5142
+ importMeta: buildImportMeta(widget, {
5143
+ fullscreen: !!v.fullscreen,
5144
+ direction: v.direction
5145
+ })
5146
+ },
5147
+ css: { background: "transparent" }
5148
+ }, ctx);
5149
+ }
5006
5150
  function mapUnsupportedAsText(widget, ctx) {
5007
5151
  return buildUnit(widget, "text", {
5008
5152
  content: {
@@ -5352,6 +5496,23 @@ var GOAL_TYPES = /* @__PURE__ */ new Set([
5352
5496
  "se-widget-merch-goal"
5353
5497
  ]);
5354
5498
  var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
5499
+ var SCROLLING_RECENT_TYPES = /* @__PURE__ */ new Set([
5500
+ "se-widget-follower-recent",
5501
+ "se-widget-subscriber-recent",
5502
+ "se-widget-cheer-recent",
5503
+ "se-widget-tip-recent"
5504
+ ]);
5505
+ var RECENT_TO_LATEST_FALLBACK_VAR = {
5506
+ "se-widget-follower-recent": "twitch_last_follower",
5507
+ "se-widget-subscriber-recent": "twitch_last_subscriber",
5508
+ "se-widget-cheer-recent": "twitch_last_bit",
5509
+ "se-widget-tip-recent": "latest_donator"
5510
+ };
5511
+ function isScrollingRecentWidget(widget) {
5512
+ if (!SCROLLING_RECENT_TYPES.has(widget.type)) return false;
5513
+ const scrolling = widget.text?.scrolling;
5514
+ return !!(scrolling && typeof scrolling === "object" && scrolling.enabled === true);
5515
+ }
5355
5516
  function dispatch(widget, ctx) {
5356
5517
  const t = widget.type;
5357
5518
  console.log("[SE-IMPORT] dispatch widget", {
@@ -5380,16 +5541,21 @@ function dispatch(widget, ctx) {
5380
5541
  if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation", ctx), status: "direct", lumiaType: "alert" };
5381
5542
  if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation", ctx), status: "direct", lumiaType: "alert" };
5382
5543
  if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation", ctx), status: "direct", lumiaType: "alert" };
5544
+ if (isScrollingRecentWidget(widget)) {
5545
+ const fallbackVar = RECENT_TO_LATEST_FALLBACK_VAR[t] ?? "username";
5546
+ return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
5547
+ }
5383
5548
  if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
5384
- if (t === "se-widget-top-tippers-list") return { unit: mapLeaderboardFromVariables(widget, "tips", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
5385
- if (t === "se-widget-top-cheerers-list") return { unit: mapLeaderboardFromVariables(widget, "cheers", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
5386
- if (t === "se-widget-top-gifters-list") return { unit: mapLeaderboardFromVariables(widget, "gifts", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
5549
+ if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
5550
+ if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
5551
+ if (t === "se-widget-top-gifters-list") return { unit: mapViewerProfilesLeaderboard(widget, "gifts", ctx), status: "direct", lumiaType: "viewerprofiles" };
5387
5552
  if (t === "se-widget-credit-roll") return { unit: mapCredits(widget, ctx), status: "partial", lumiaType: "credits" };
5388
5553
  if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget, ctx), status: "partial", lumiaType: "chatbox" };
5389
5554
  if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget, ctx), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
5390
5555
  if (t === "se-widget-countdown") return { unit: mapTimer(widget, ctx), status: "partial", lumiaType: "timer" };
5391
5556
  if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget, ctx), status: "partial", lumiaType: "nowplaying" };
5392
5557
  if (t === "se-widget-kappagen") return { unit: mapKappagen(widget, ctx), status: "partial", lumiaType: "emotealert" };
5558
+ if (t === "se-widget-emote-wall") return { unit: mapEmoteWall(widget, ctx), status: "partial", lumiaType: "emotebox" };
5393
5559
  if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget, ctx), status: "direct", lumiaType: "raffle" };
5394
5560
  if (t === "se-widget-media-share") return { unit: mapMediaShare(widget, ctx), status: "partial", lumiaType: "songrequest" };
5395
5561
  if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget, ctx), status: "partial", lumiaType: "tipjar" };
package/dist/se-import.js CHANGED
@@ -18,31 +18,86 @@ var SE_TO_LUMIA_PLACEHOLDER = {
18
18
  };
19
19
  var TWITCH_CONTEXTS = {
20
20
  "follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
21
+ // Three SE listeners with genuinely distinct semantics — preserve the
22
+ // distinction in the variable mapping so a widget configured for "Latest
23
+ // new subscriber" doesn't render the latest *resub* (and vice versa).
24
+ // `subscriber-latest` is the umbrella (anyone subscribing); the other two
25
+ // are subtype-specific. The dedicated TWITCH_LAST_{NEW_SUBSCRIBER,
26
+ // RESUBSCRIBER} variables are written by the Twitch event pipeline on
27
+ // the matching sub.type event.
21
28
  "subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
22
- "subscriber-new-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
23
- "subscriber-resub-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
24
- "subscriber-gifted-latest": { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
29
+ "subscriber-new-latest": { name: "twitch_last_new_subscriber", username: "twitch_last_new_subscriber" },
30
+ "subscriber-resub-latest": { name: "twitch_last_resubscriber", username: "twitch_last_resubscriber" },
31
+ // `subscriber-gifted-latest`: SE uses `{sender}` for the giver and `{name}` for
32
+ // the recipient (e.g. "{sender} gifted to {name}"). The two roles must point
33
+ // at distinct Lumia variables — collapsing both onto `twitch_last_gifter`
34
+ // rendered "Mr.Beast gifted to Mr.Beast" while the actual recipient was
35
+ // nowhere. `sender` keeps the gifter; `name` / `username` switch to the
36
+ // dedicated recipient variable `twitch_last_gifted`. Amount stays on the
37
+ // gifter (it's the count of subs in that drop, owned by the gifter).
38
+ "subscriber-gifted-latest": { name: "twitch_last_gifted", username: "twitch_last_gifted", sender: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
25
39
  "subscriber-alltime-gifter": { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" },
26
40
  "cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
27
41
  "tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
28
42
  latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
29
43
  "raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
30
- // Session counters: count/amount = the running session count.
44
+ // Period counters: count/amount = the running counter for that window.
45
+ // SE lets the streamer pick session / week / month / total on the same widget
46
+ // type; the chosen period lands in `widget.listener` (e.g. `cheer-week`).
47
+ // Each entry below mirrors a row in the Lumia variables table — the previous
48
+ // table only carried session+total and broke any widget configured for
49
+ // week/month because `{amount}` fell through to the unscoped placeholder
50
+ // and rendered blank.
31
51
  "follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
32
- "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
33
- "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
34
- "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
35
- // Totals (all-time): count/amount = total counter.
52
+ "follower-week": { amount: "twitch_week_follower_count", count: "twitch_week_follower_count" },
53
+ "follower-month": { amount: "twitch_month_follower_count", count: "twitch_month_follower_count" },
36
54
  "follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
55
+ "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
56
+ "subscriber-week": { amount: "twitch_week_subscriber_count", count: "twitch_week_subscriber_count" },
57
+ "subscriber-month": { amount: "twitch_month_subscriber_count", count: "twitch_month_subscriber_count" },
37
58
  "subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
59
+ "subscriber-new-session": { amount: "twitch_session_new_subscribers_count", count: "twitch_session_new_subscribers_count" },
60
+ "subscriber-resub-session": { amount: "twitch_session_resub_subscribers_count", count: "twitch_session_resub_subscribers_count" },
61
+ "subscriber-gifted-session": { amount: "twitch_session_gifted_subscribers_count", count: "twitch_session_gifted_subscribers_count" },
62
+ "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
63
+ "cheer-week": { amount: "twitch_week_bits_count", count: "twitch_week_bits_count" },
64
+ "cheer-month": { amount: "twitch_month_bits_count", count: "twitch_month_bits_count" },
38
65
  "cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
66
+ "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
67
+ "tip-week": { amount: "week_donation_amount", count: "week_donation_count", currency: "week_donation_amount_currency_symbol" },
68
+ "tip-month": { amount: "month_donation_amount", count: "month_donation_count", currency: "month_donation_amount_currency_symbol" },
39
69
  "tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
40
70
  "tip-count": { amount: "donation_count", count: "donation_count" },
41
- // Top cheer / cheerer / donator session readouts.
42
- "cheer-session-top-donation": { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
71
+ // Top cheer / cheerer / donator readouts — per period. SE's `se-widget-top-*`
72
+ // widgets encode the chosen period (session / weekly / monthly / alltime) in
73
+ // the widget's `listener` field. Without these entries the user's `{amount}`
74
+ // / `{name}` template tokens fall through to the unscoped placeholder map
75
+ // (`{{amount}}` only resolves inside alert variations) and render blank.
76
+ //
77
+ // `*-top-donation` and `*-top-donator` are paired: SE nominally treats them
78
+ // as "top single event" vs "top cumulative contributor", but the data
79
+ // rendered for both lives in the same Lumia variable pair (`*_top_donator`
80
+ // / `*_top_donator_amount`, `twitch_*_top_cheerer` / `..._amount`). SE
81
+ // widgets routinely use `{name}` against the `-donation` listener to mean
82
+ // "the user who made the top donation", so the two rows below carry the
83
+ // same name/username mapping — anything else falls through to the unscoped
84
+ // `{{username}}` and renders blank.
85
+ "cheer-session-top-donation": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
43
86
  "cheer-session-top-donator": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" },
44
- "tip-session-top-donation": { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
87
+ "cheer-weekly-top-donation": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
88
+ "cheer-weekly-top-donator": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
89
+ "cheer-monthly-top-donation": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
90
+ "cheer-monthly-top-donator": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
91
+ "cheer-alltime-top-donation": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
92
+ "cheer-alltime-top-donator": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
93
+ "tip-session-top-donation": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
45
94
  "tip-session-top-donator": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
95
+ "tip-weekly-top-donation": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
96
+ "tip-weekly-top-donator": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
97
+ "tip-monthly-top-donation": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
98
+ "tip-monthly-top-donator": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
99
+ "tip-alltime-top-donation": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
100
+ "tip-alltime-top-donator": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
46
101
  // Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
47
102
  // fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
48
103
  // when the listener arg lands as that var name we treat it as the wins source.
@@ -79,7 +134,11 @@ var KICK_CONTEXTS = {
79
134
  "subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
80
135
  "subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
81
136
  "subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
82
- "subscriber-gifted-latest": { name: "kick_last_gifter", username: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
137
+ // Mirror the Twitch split: `{sender}` = giver, `{name}` = recipient.
138
+ // `kick_last_gifted` was added alongside this mapping; see twitch context
139
+ // above for the rationale (without the split, SE's "{sender} gifted to
140
+ // {name}" renders the gifter on both sides).
141
+ "subscriber-gifted-latest": { name: "kick_last_gifted", username: "kick_last_gifted", sender: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
83
142
  "cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
84
143
  "raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
85
144
  "follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
@@ -111,9 +170,17 @@ var YOUTUBE_CONTEXTS = {
111
170
  "subscriber-week": { amount: "youtube_week_member_count", count: "youtube_week_member_count" },
112
171
  "subscriber-month": { amount: "youtube_month_member_count", count: "youtube_month_member_count" }
113
172
  };
173
+ var RECENT_TO_LATEST_LISTENER = {
174
+ "follower-recent": "follower-latest",
175
+ "subscriber-recent": "subscriber-latest",
176
+ "cheer-recent": "cheer-latest",
177
+ "tip-recent": "tip-latest",
178
+ "raid-recent": "raid-latest"
179
+ };
114
180
  function listenerPlaceholderContext(listener, provider = "twitch") {
181
+ const resolved = RECENT_TO_LATEST_LISTENER[listener] ?? listener;
115
182
  const platformTable = provider === "kick" ? KICK_CONTEXTS : provider === "youtube" ? YOUTUBE_CONTEXTS : null;
116
- return platformTable?.[listener] ?? TWITCH_CONTEXTS[listener] ?? {};
183
+ return platformTable?.[resolved] ?? TWITCH_CONTEXTS[resolved] ?? {};
117
184
  }
118
185
  var SE_WIDGET_TYPE_LABELS = {
119
186
  "subscriber-latest": "Latest subscriber",
@@ -149,12 +216,13 @@ function defaultLabelForSEWidget(widget) {
149
216
  const tail = words.slice(1).join(" ");
150
217
  return tail ? `${head} ${tail}` : head;
151
218
  }
152
- function translateSeText(input, listener, passthrough, provider) {
219
+ function translateSeText(input, listener, passthrough, provider, options) {
153
220
  if (!input) return "";
154
221
  const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
222
+ const useAlertFallback = (options?.context ?? "alert") === "alert";
155
223
  return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
156
224
  if (passthrough?.has(raw)) return `{{${raw}}}`;
157
- const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
225
+ const mapped = ctx[raw] ?? (useAlertFallback ? SE_TO_LUMIA_PLACEHOLDER[raw] : void 0) ?? raw;
158
226
  return `{{${mapped}}}`;
159
227
  });
160
228
  }
@@ -198,14 +266,15 @@ var YOUTUBE_LISTENER_VARS = {
198
266
  };
199
267
  function listenerToLumiaVariable(listener, provider = "twitch") {
200
268
  if (typeof listener !== "string" || !listener) return "username";
201
- if (provider === "kick" && KICK_LISTENER_VARS[listener]) return KICK_LISTENER_VARS[listener];
202
- if (provider === "youtube" && YOUTUBE_LISTENER_VARS[listener]) return YOUTUBE_LISTENER_VARS[listener];
269
+ const resolvedListener = RECENT_TO_LATEST_LISTENER[listener] ?? listener;
270
+ if (provider === "kick" && KICK_LISTENER_VARS[resolvedListener]) return KICK_LISTENER_VARS[resolvedListener];
271
+ if (provider === "youtube" && YOUTUBE_LISTENER_VARS[resolvedListener]) return YOUTUBE_LISTENER_VARS[resolvedListener];
203
272
  const m = {
204
273
  // Latest events
205
274
  "follower-latest": "twitch_last_follower",
206
275
  "subscriber-latest": "twitch_last_subscriber",
207
- "subscriber-new-latest": "twitch_last_subscriber",
208
- "subscriber-resub-latest": "twitch_last_subscriber",
276
+ "subscriber-new-latest": "twitch_last_new_subscriber",
277
+ "subscriber-resub-latest": "twitch_last_resubscriber",
209
278
  "subscriber-gifted-latest": "twitch_last_gifter",
210
279
  "subscriber-alltime-gifter": "twitch_alltime_top_gifter",
211
280
  "tip-latest": "latest_donator",
@@ -256,7 +325,7 @@ function listenerToLumiaVariable(listener, provider = "twitch") {
256
325
  "cheer-alltime-top-donation": "twitch_alltime_top_cheerer_amount",
257
326
  "cheer-alltime-top-donator": "twitch_alltime_top_cheerer"
258
327
  };
259
- return m[listener] ?? listener.replace(/-/g, "_");
328
+ return m[resolvedListener] ?? resolvedListener.replace(/-/g, "_");
260
329
  }
261
330
  function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
262
331
  let widthIsAuto = false;
@@ -360,9 +429,16 @@ var LUMIA_DEFAULT_SIZES = {
360
429
  // just the card if they want SE's exact footprint.
361
430
  songrequest: { width: 480, height: 380 }
362
431
  };
363
- function translateVariationCondition(seType, seCondition, requirement) {
432
+ function translateVariationCondition(seType, seCondition, requirement, seEventType) {
364
433
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
365
434
  if (seType === "communityGift") return { conditionType: "GIFT_SUB_GREATER", condition: requirement ?? 0 };
435
+ const isSubscriberEvent = seEventType === "subscriber";
436
+ if (isSubscriberEvent && seCondition === "ATLEAST" && requirement != null) {
437
+ return { conditionType: "SUBSCRIBED_MONTHS_GREATER", condition: requirement };
438
+ }
439
+ if (isSubscriberEvent && seCondition === "EXACT" && requirement != null) {
440
+ return { conditionType: "SUBSCRIBED_MONTHS_EQUAL", condition: requirement };
441
+ }
366
442
  if (seCondition === "EXACT" && requirement != null) return { conditionType: "EQUAL_NUMBER", condition: requirement };
367
443
  if (seCondition === "ATLEAST" && requirement != null) {
368
444
  return { conditionType: "GREATER_NUMBER", condition: requirement };
@@ -462,7 +538,7 @@ function seExtraTextCss(seCss, scrolling) {
462
538
  return extra;
463
539
  }
464
540
  function mapText(widget, ctx) {
465
- const value = translateSeText(widget.text?.value ?? "");
541
+ const value = translateSeText(widget.text?.value ?? "", null, void 0, ctx?.provider, { context: "static" });
466
542
  const seCss = widget.text?.css ?? {};
467
543
  return buildUnit(widget, "text", {
468
544
  content: {
@@ -519,7 +595,7 @@ function mapReadout(widget, fallbackVar, ctx) {
519
595
  const variable = listenerToLumiaVariable(listener, ctx?.provider);
520
596
  const userTemplate = widget.text?.value ?? "";
521
597
  const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
522
- const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
598
+ const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider, { context: "static" }) : `{{${variable}}}`;
523
599
  const seCss = widget.text?.css ?? {};
524
600
  return buildUnit(widget, "text", {
525
601
  content: { value, highlightColor: "inherit" },
@@ -774,7 +850,7 @@ function buildAlertEvent(seEvent, seEventType) {
774
850
  };
775
851
  }
776
852
  function buildAlertVariation(v, seEventType, parentSettings) {
777
- const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
853
+ const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null, seEventType);
778
854
  if (!translated) return null;
779
855
  const { variations: _parentVariations, ...parentInheritable } = parentSettings ?? {};
780
856
  const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
@@ -910,7 +986,8 @@ function mapGoal(widget, ctx) {
910
986
  // live `current` floating at the percentage position. See
911
987
  // components/Modules/Views/Goal/types.ts:GoalTypes.SLIM.
912
988
  type: "slim",
913
- label: translateSeText(v.title ?? "Goal"),
989
+ // Static label rendered above the goal bar — not an alert context.
990
+ label: translateSeText(v.title ?? "Goal", null, void 0, void 0, { context: "static" }),
914
991
  subLabel: "",
915
992
  // The Goal module renders this template literal with {{current}} + {{goal}} substituted.
916
993
  // Charity goals track donation amounts — prefix `$` so the rendered bar reads
@@ -1227,6 +1304,9 @@ function mapEventList(widget, ctx) {
1227
1304
  const filters = computeListenerFilters(widget.listeners, widget.listener);
1228
1305
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
1229
1306
  return buildUnit(widget, "eventlist", {
1307
+ // SE event-list widgets style per-event lines via widget.text.css; the
1308
+ // Lumia eventlist reads font/size/color from module.css.
1309
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
1230
1310
  content: {
1231
1311
  theme: v.theme ?? "simple",
1232
1312
  direction: v.direction ?? "top",
@@ -1510,7 +1590,7 @@ function mapTimer(widget, ctx) {
1510
1590
  const v = widget.variables ?? {};
1511
1591
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
1512
1592
  const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
1513
- const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH);
1593
+ const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, { context: "static" });
1514
1594
  const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
1515
1595
  const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
1516
1596
  const hasValidAbsoluteDate = Number.isFinite(target);
@@ -1582,7 +1662,7 @@ function mapTimer(widget, ctx) {
1582
1662
  endAnimation: "",
1583
1663
  hideAfter: 0,
1584
1664
  highlightColor: "inherit",
1585
- messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? ""),
1665
+ messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? "", null, void 0, void 0, { context: "static" }),
1586
1666
  changeMessageAfter: !!v.changeMessageAfterCountdown,
1587
1667
  countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
1588
1668
  date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
@@ -1660,14 +1740,10 @@ function mapMediaShare(widget, ctx) {
1660
1740
  // `durationSeconds + grace`).
1661
1741
  importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
1662
1742
  },
1663
- css: {
1664
- fontFamily: "Roboto",
1665
- fontWeight: "bold",
1666
- color: "#ffffff",
1667
- textShadow: "rgb(0, 0, 0) 1px 1px 1px",
1668
- background: "transparent",
1669
- fontSize: 18
1670
- }
1743
+ // Lumia songrequest renders the queue list as styled text; pull SE's
1744
+ // widget.text.css so font/size/color carry across (defaults match the
1745
+ // previous hardcoded fallbacks for this widget specifically).
1746
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
1671
1747
  }, ctx);
1672
1748
  }
1673
1749
  function mapKappagen(widget, ctx) {
@@ -1729,25 +1805,47 @@ function mapKappagen(widget, ctx) {
1729
1805
  ctx
1730
1806
  );
1731
1807
  }
1732
- function mapLeaderboardFromVariables(widget, currency, ctx) {
1808
+ var TOP_LIST_SORT_BY = {
1809
+ tips: "higherDonations",
1810
+ cheers: "higherBits",
1811
+ gifts: "higherGifts"
1812
+ };
1813
+ var TOP_LIST_TITLE = {
1814
+ tips: "Top Tippers",
1815
+ cheers: "Top Cheerers",
1816
+ gifts: "Top Gifters"
1817
+ };
1818
+ function mapViewerProfilesLeaderboard(widget, currency, ctx) {
1733
1819
  const v = widget.variables ?? {};
1734
1820
  const count = v.limit ?? v.visibleItems ?? 10;
1735
- const window = v.period === "weekly" ? "week" : v.period === "monthly" ? "month" : "alltime";
1736
- const title = currency === "tips" ? "Top Tippers" : currency === "cheers" ? "Top Cheerers" : "Top Gifters";
1737
- return buildUnit(widget, "loyaltyleaderboard", {
1821
+ return buildUnit(widget, "viewerprofiles", {
1822
+ // Carry SE text styling so the leaderboard keeps the streamer's
1823
+ // chosen font / size / color.
1824
+ css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
1738
1825
  content: {
1739
- source: "variables",
1740
- currency,
1741
- window,
1826
+ sortBy: TOP_LIST_SORT_BY[currency],
1742
1827
  count,
1743
- title,
1828
+ title: TOP_LIST_TITLE[currency],
1744
1829
  showTitle: true,
1745
- showHeader: false,
1746
- showCurrentPoints: false,
1747
- showAllTimePoints: true,
1830
+ showAvatar: true,
1831
+ showPlatform: true,
1832
+ // Hide noisy columns that aren't relevant for a single-metric
1833
+ // top-X list — the streamer picked "top by gifts/tips/cheers"
1834
+ // for a reason. They can re-enable in Settings if they want.
1835
+ showCommands: false,
1836
+ showAlerts: false,
1837
+ showBits: currency === "cheers",
1838
+ showLastSeen: false,
1839
+ showTopActivity: false,
1840
+ showAchievements: false,
1841
+ showAchievementProgress: false,
1842
+ refreshIntervalSeconds: 30,
1748
1843
  itemGap: 8,
1844
+ autoScroll: false,
1749
1845
  // Source-side knobs that don't map cleanly survive under the
1750
- // generic importMeta envelope so the user can re-introspect.
1846
+ // generic importMeta envelope so the user can re-introspect later.
1847
+ // `period` is interesting because viewerprofiles doesn't yet have
1848
+ // a windowed mode — if/when it does, we can promote this.
1751
1849
  importMeta: buildImportMeta(widget, {
1752
1850
  period: v.period,
1753
1851
  offset: v.offset,
@@ -1761,6 +1859,8 @@ function mapGiveaway(widget, ctx) {
1761
1859
  const primary = v.colors?.primary ?? "#393853";
1762
1860
  const accent = v.colors?.accent ?? "#FF4076";
1763
1861
  return buildUnit(widget, "raffle", {
1862
+ // Raffle renders entrants/winner text via module.css.
1863
+ css: mapSeTextCssToModuleCss(widget),
1764
1864
  content: {
1765
1865
  version: 1,
1766
1866
  type: "basic",
@@ -1845,6 +1945,9 @@ function isHypetrainCustomWidget(widget) {
1845
1945
  function mapHypetrain(widget, ctx) {
1846
1946
  const v = widget.variables ?? {};
1847
1947
  return buildUnit(widget, "hypetrain", {
1948
+ // Hypetrain renders level / progress / contributor text — carry SE
1949
+ // styling so an imported hype-train widget keeps its font + accent.
1950
+ css: mapSeTextCssToModuleCss(widget),
1848
1951
  content: {
1849
1952
  // Twitch-native is the right default — SE hype-train widgets always
1850
1953
  // reflected the Twitch native train. Users can flip to 'synthetic'
@@ -1888,6 +1991,47 @@ function mapHypetrain(widget, ctx) {
1888
1991
  }
1889
1992
  }, ctx);
1890
1993
  }
1994
+ function mapEmoteWall(widget, ctx) {
1995
+ const v = widget.variables ?? {};
1996
+ const imageSize = typeof v.emoteSize === "number" ? v.emoteSize : typeof v.size === "number" ? v.size : 40;
1997
+ const maxItemsToShow = typeof v.maxEmotes === "number" && v.maxEmotes > 0 ? v.maxEmotes : typeof v.maxItems === "number" && v.maxItems > 0 ? v.maxItems : 40;
1998
+ const duration = typeof v.duration === "number" && v.duration > 0 ? v.duration : typeof v.decay === "number" && v.decay > 0 ? v.decay : typeof v.decayTime === "number" && v.decayTime > 0 ? v.decayTime : 5;
1999
+ const sites = [];
2000
+ if (v.enableTwitch !== false && v.twitch !== false) sites.push("twitch");
2001
+ if (v.enableBttv !== false && v.bttv !== false) sites.push("bttv");
2002
+ if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
2003
+ if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
2004
+ const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
2005
+ return buildUnit(widget, "emotebox", {
2006
+ content: {
2007
+ version: 1,
2008
+ type: animationType,
2009
+ imageSize,
2010
+ animationTime: duration,
2011
+ duration,
2012
+ maxItemsToShow,
2013
+ delay: typeof v.delay === "number" ? v.delay : 0,
2014
+ sites,
2015
+ background: "transparent",
2016
+ theme: "dark",
2017
+ userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
2018
+ ignoredList: [],
2019
+ excludedAnimations: [],
2020
+ // Emote-wall is chat-driven, not alert-driven — leave alert toggles
2021
+ // off so the widget doesn't double-fire on subscribe/follower events.
2022
+ alertOn: false,
2023
+ alertTriggers: [],
2024
+ alertEmotes: [],
2025
+ alertNumberEmotes: 30,
2026
+ useAnimatedEmotes: true,
2027
+ importMeta: buildImportMeta(widget, {
2028
+ fullscreen: !!v.fullscreen,
2029
+ direction: v.direction
2030
+ })
2031
+ },
2032
+ css: { background: "transparent" }
2033
+ }, ctx);
2034
+ }
1891
2035
  function mapUnsupportedAsText(widget, ctx) {
1892
2036
  return buildUnit(widget, "text", {
1893
2037
  content: {
@@ -2237,6 +2381,23 @@ var GOAL_TYPES = /* @__PURE__ */ new Set([
2237
2381
  "se-widget-merch-goal"
2238
2382
  ]);
2239
2383
  var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
2384
+ var SCROLLING_RECENT_TYPES = /* @__PURE__ */ new Set([
2385
+ "se-widget-follower-recent",
2386
+ "se-widget-subscriber-recent",
2387
+ "se-widget-cheer-recent",
2388
+ "se-widget-tip-recent"
2389
+ ]);
2390
+ var RECENT_TO_LATEST_FALLBACK_VAR = {
2391
+ "se-widget-follower-recent": "twitch_last_follower",
2392
+ "se-widget-subscriber-recent": "twitch_last_subscriber",
2393
+ "se-widget-cheer-recent": "twitch_last_bit",
2394
+ "se-widget-tip-recent": "latest_donator"
2395
+ };
2396
+ function isScrollingRecentWidget(widget) {
2397
+ if (!SCROLLING_RECENT_TYPES.has(widget.type)) return false;
2398
+ const scrolling = widget.text?.scrolling;
2399
+ return !!(scrolling && typeof scrolling === "object" && scrolling.enabled === true);
2400
+ }
2240
2401
  function dispatch(widget, ctx) {
2241
2402
  const t = widget.type;
2242
2403
  console.log("[SE-IMPORT] dispatch widget", {
@@ -2265,16 +2426,21 @@ function dispatch(widget, ctx) {
2265
2426
  if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation", ctx), status: "direct", lumiaType: "alert" };
2266
2427
  if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation", ctx), status: "direct", lumiaType: "alert" };
2267
2428
  if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation", ctx), status: "direct", lumiaType: "alert" };
2429
+ if (isScrollingRecentWidget(widget)) {
2430
+ const fallbackVar = RECENT_TO_LATEST_FALLBACK_VAR[t] ?? "username";
2431
+ return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
2432
+ }
2268
2433
  if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
2269
- if (t === "se-widget-top-tippers-list") return { unit: mapLeaderboardFromVariables(widget, "tips", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
2270
- if (t === "se-widget-top-cheerers-list") return { unit: mapLeaderboardFromVariables(widget, "cheers", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
2271
- if (t === "se-widget-top-gifters-list") return { unit: mapLeaderboardFromVariables(widget, "gifts", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
2434
+ if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
2435
+ if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
2436
+ if (t === "se-widget-top-gifters-list") return { unit: mapViewerProfilesLeaderboard(widget, "gifts", ctx), status: "direct", lumiaType: "viewerprofiles" };
2272
2437
  if (t === "se-widget-credit-roll") return { unit: mapCredits(widget, ctx), status: "partial", lumiaType: "credits" };
2273
2438
  if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget, ctx), status: "partial", lumiaType: "chatbox" };
2274
2439
  if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget, ctx), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
2275
2440
  if (t === "se-widget-countdown") return { unit: mapTimer(widget, ctx), status: "partial", lumiaType: "timer" };
2276
2441
  if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget, ctx), status: "partial", lumiaType: "nowplaying" };
2277
2442
  if (t === "se-widget-kappagen") return { unit: mapKappagen(widget, ctx), status: "partial", lumiaType: "emotealert" };
2443
+ if (t === "se-widget-emote-wall") return { unit: mapEmoteWall(widget, ctx), status: "partial", lumiaType: "emotebox" };
2278
2444
  if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget, ctx), status: "direct", lumiaType: "raffle" };
2279
2445
  if (t === "se-widget-media-share") return { unit: mapMediaShare(widget, ctx), status: "partial", lumiaType: "songrequest" };
2280
2446
  if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget, ctx), status: "partial", lumiaType: "tipjar" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.2.8-alpha.26",
3
+ "version": "0.2.8-alpha.28",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",