@lumiastream/ui 0.3.9 → 0.4.1

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
@@ -3878,7 +3878,11 @@ var LUMIA_DEFAULT_SIZES = {
3878
3878
  // card + 5-item queue list needs a bit more vertical room, so we add ~80px
3879
3879
  // for the queue list. Users can hide the queue list and shrink back to
3880
3880
  // just the card if they want SE's exact footprint.
3881
- songrequest: { width: 480, height: 380 }
3881
+ songrequest: { width: 480, height: 380 },
3882
+ // Train: slim horizontal bar. SE train widgets vary in size but Lumia's
3883
+ // layout is always a single-line ticker, so we fix the import footprint.
3884
+ // (mapTrain also forces dimensions directly so this is a backstop only.)
3885
+ train: { width: 540, height: 60 }
3882
3886
  };
3883
3887
  function translateVariationCondition(seType, seCondition, requirement, seEventType) {
3884
3888
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
@@ -4744,10 +4748,7 @@ function mapCustom(widget, ctx) {
4744
4748
  }
4745
4749
 
4746
4750
  // src/se-import/mappers/misc.ts
4747
- import {
4748
- LumiaEventListTypes,
4749
- LumiaMapAlertTypeToEventListType
4750
- } from "@lumiastream/lumia-types";
4751
+ import { LumiaEventListTypes, LumiaMapAlertTypeToEventListType } from "@lumiastream/lumia-types";
4751
4752
 
4752
4753
  // src/se-import/mappers/module-defaults.ts
4753
4754
  var CHATBOX_DEFAULT_CONTENT_FIELDS = {
@@ -4808,13 +4809,24 @@ var CHATBOX_DEFAULT_MODULE = {
4808
4809
  function mapChatboxTheme(seTheme) {
4809
4810
  const t = (seTheme ?? "").toString().toLowerCase();
4810
4811
  if (!t || t === "default") return "simple";
4811
- if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
4812
- return "bubbly";
4812
+ if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly")) return "bubbly";
4813
4813
  if (t.includes("glass") || t.includes("transparent")) return "glass";
4814
4814
  if (t.includes("boxy") || t.includes("checker")) return "boxy";
4815
4815
  if (t.includes("basic")) return "basic";
4816
4816
  return "simple";
4817
4817
  }
4818
+ function defaultChatboxBackgroundForSeTheme(seTheme) {
4819
+ const t = (seTheme ?? "").toString().toLowerCase();
4820
+ if (!t || t === "default") return "transparent";
4821
+ if (t === "custom") return "transparent";
4822
+ if (t.includes("glass") || t.includes("transparent") || t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
4823
+ return "transparent";
4824
+ if (t.includes("white") || t.includes("light") || t.includes("clean") && !t.includes("dark"))
4825
+ return "transparent";
4826
+ if (t.includes("dark") || t.includes("boxy") || t.includes("checker") || t.includes("black"))
4827
+ return "#1e1e1e";
4828
+ return "#1e1e1e";
4829
+ }
4818
4830
  var KAPPAGEN_EVENT_CATEGORIES = {
4819
4831
  tip: [LumiaEventListTypes.DONATION],
4820
4832
  cheer: [
@@ -4830,9 +4842,7 @@ var KAPPAGEN_EVENT_CATEGORIES = {
4830
4842
  subscriber: [LumiaEventListTypes.SUBSCRIBERS],
4831
4843
  purchase: [LumiaEventListTypes.PURCHASES]
4832
4844
  };
4833
- var alertKeysByEventListType = Object.entries(
4834
- LumiaMapAlertTypeToEventListType
4835
- ).reduce(
4845
+ var alertKeysByEventListType = Object.entries(LumiaMapAlertTypeToEventListType).reduce(
4836
4846
  (acc, [alertKey, eventListType]) => {
4837
4847
  acc[eventListType] = acc[eventListType] ?? [];
4838
4848
  acc[eventListType].push(alertKey);
@@ -4877,21 +4887,15 @@ function mapChatbox(widget, ctx) {
4877
4887
  const messagePadding = paddingShorthand(messageCss);
4878
4888
  const DEFAULT_CHATBOX_FONT_SIZE = 14;
4879
4889
  const moduleCss = {};
4880
- if (textCss["font-family"] != null)
4881
- moduleCss.fontFamily = textCss["font-family"];
4890
+ if (textCss["font-family"] != null) moduleCss.fontFamily = textCss["font-family"];
4882
4891
  moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
4883
4892
  if (textCss["color"] != null) moduleCss.color = textCss["color"];
4884
- if (textCss["font-weight"] != null)
4885
- moduleCss.fontWeight = textCss["font-weight"];
4886
- if (textCss["text-shadow"] != null)
4887
- moduleCss.textShadow = textCss["text-shadow"];
4888
- if (textCss["text-align"] != null)
4889
- moduleCss.textAlign = textCss["text-align"];
4890
- if (textCss["line-height"] != null)
4891
- moduleCss.lineHeight = textCss["line-height"];
4893
+ if (textCss["font-weight"] != null) moduleCss.fontWeight = textCss["font-weight"];
4894
+ if (textCss["text-shadow"] != null) moduleCss.textShadow = textCss["text-shadow"];
4895
+ if (textCss["text-align"] != null) moduleCss.textAlign = textCss["text-align"];
4896
+ if (textCss["line-height"] != null) moduleCss.lineHeight = textCss["line-height"];
4892
4897
  if (messagePadding) moduleCss.padding = messagePadding;
4893
- const DEFAULT_CHATBOX_BG = "#1e1e1e";
4894
- const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
4898
+ const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : defaultChatboxBackgroundForSeTheme(v.theme);
4895
4899
  moduleCss.background = messageBackground;
4896
4900
  const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
4897
4901
  const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
@@ -5001,9 +5005,7 @@ var SE_LISTENER_TO_LUMIA_CATEGORY = {
5001
5005
  function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
5002
5006
  const enabled = [];
5003
5007
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
5004
- for (const [k, v] of Object.entries(
5005
- seListeners
5006
- )) {
5008
+ for (const [k, v] of Object.entries(seListeners)) {
5007
5009
  if (v) enabled.push(k);
5008
5010
  }
5009
5011
  } else if (Array.isArray(seListeners)) {
@@ -5021,6 +5023,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
5021
5023
  if (allowedCategories.size === 0) return [];
5022
5024
  const allowedAlerts = [];
5023
5025
  Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
5026
+ if (alertKey.includes("session")) return;
5024
5027
  const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
5025
5028
  if (mappedCategory && allowedCategories.has(mappedCategory)) {
5026
5029
  allowedAlerts.push(alertKey);
@@ -5028,7 +5031,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
5028
5031
  });
5029
5032
  return allowedAlerts;
5030
5033
  }
5031
- var LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND = 20;
5034
+ var LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND = 70;
5032
5035
  function seScrollSpeedToPixelsPerSecond(_seSpeed) {
5033
5036
  return LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND;
5034
5037
  }
@@ -5038,10 +5041,7 @@ function seTemplateToMarqueeTemplate(seTemplate) {
5038
5041
  }
5039
5042
  function mapEventList(widget, ctx) {
5040
5043
  const v = widget.variables ?? {};
5041
- const allowedEvents = computeAllowedEventsFromSEListeners(
5042
- widget.listeners,
5043
- widget.listener
5044
- );
5044
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
5045
5045
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
5046
5046
  const seText = widget.text;
5047
5047
  const scrolling = seText?.scrolling;
@@ -5090,7 +5090,7 @@ function mapEventList(widget, ctx) {
5090
5090
  ...isMarquee ? { marqueeItemGap: 24 } : {},
5091
5091
  showAlertIcon: !isMarquee,
5092
5092
  showSiteIcon: !isMarquee,
5093
- showUserAvatar: !isMarquee,
5093
+ showUserAvatar: true,
5094
5094
  showDetailedText: false,
5095
5095
  hideAlertMessage: false
5096
5096
  }
@@ -5328,10 +5328,7 @@ function mapSlideshow(widget, ctx) {
5328
5328
  const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
5329
5329
  if (!url) return null;
5330
5330
  return {
5331
- type: inferSlideshowMediaType(
5332
- typeof raw === "object" ? raw?.type : void 0,
5333
- url
5334
- ),
5331
+ type: inferSlideshowMediaType(typeof raw === "object" ? raw?.type : void 0, url),
5335
5332
  src: url,
5336
5333
  name: nameFromUrl(url)
5337
5334
  };
@@ -5376,6 +5373,46 @@ function mapSlideshow(widget, ctx) {
5376
5373
  ctx
5377
5374
  );
5378
5375
  }
5376
+ function mapTrain(widget, ctx) {
5377
+ const v = widget.variables ?? {};
5378
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
5379
+ const TRAIN_WIDTH = 540;
5380
+ const TRAIN_HEIGHT = v.vertical === true ? 200 : 60;
5381
+ const widgetWithSize = {
5382
+ ...widget,
5383
+ css: { ...widget.css, width: TRAIN_WIDTH, height: TRAIN_HEIGHT }
5384
+ };
5385
+ return buildUnit(
5386
+ widgetWithSize,
5387
+ "train",
5388
+ {
5389
+ css: mapSeTextCssToModuleCss(widget, {
5390
+ fontSize: 24,
5391
+ fontFamily: "Nunito",
5392
+ fontWeight: "bold",
5393
+ color: "#ffffff",
5394
+ textShadow: "rgb(0, 0, 0) 1px 1px 1px",
5395
+ textAlign: "left",
5396
+ lineHeight: 1.3,
5397
+ background: "transparent"
5398
+ }),
5399
+ content: {
5400
+ version: 1,
5401
+ // SE stores train duration in minutes; Lumia stores it in seconds.
5402
+ duration: typeof v.duration === "number" && v.duration > 0 ? v.duration * 60 : 300,
5403
+ autoHide: false,
5404
+ showCount: v.showCount !== false,
5405
+ showTime: v.showTime !== false,
5406
+ showLatest: v.show_latest !== false,
5407
+ showIcons: v.icons !== false,
5408
+ vertical: v.vertical === true,
5409
+ ...allowedEvents !== void 0 ? { allowedEvents } : { allowedEvents: [] }
5410
+ }
5411
+ },
5412
+ ctx,
5413
+ { width: 540, height: 60 }
5414
+ );
5415
+ }
5379
5416
  function mapTimer(widget, ctx) {
5380
5417
  const v = widget.variables ?? {};
5381
5418
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
@@ -5402,9 +5439,7 @@ function mapTimer(widget, ctx) {
5402
5439
  resolved = true;
5403
5440
  }
5404
5441
  }
5405
- if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
5406
- (part) => typeof part === "number"
5407
- )) {
5442
+ if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some((part) => typeof part === "number")) {
5408
5443
  days = typeof v.days === "number" ? v.days : 0;
5409
5444
  hours = typeof v.hours === "number" ? v.hours : 0;
5410
5445
  minutes = typeof v.minutes === "number" ? v.minutes : 0;
@@ -5512,8 +5547,7 @@ function mapNowPlaying(widget, ctx) {
5512
5547
  };
5513
5548
  if (typeof seTextCss["text-transform"] === "string")
5514
5549
  importedCss.textTransform = seTextCss["text-transform"];
5515
- if (typeof seTextCss["font-style"] === "string")
5516
- importedCss.fontStyle = seTextCss["font-style"];
5550
+ if (typeof seTextCss["font-style"] === "string") importedCss.fontStyle = seTextCss["font-style"];
5517
5551
  if (typeof seTextCss["text-decoration"] === "string")
5518
5552
  importedCss.textDecoration = seTextCss["text-decoration"];
5519
5553
  if (typeof seTextCss["letter-spacing"] === "string" || typeof seTextCss["letter-spacing"] === "number") {
@@ -6542,6 +6576,12 @@ function dispatch(widget, ctx) {
6542
6576
  status: "direct",
6543
6577
  lumiaType: "image"
6544
6578
  };
6579
+ if (t === "se-widget-train")
6580
+ return {
6581
+ unit: mapTrain(widget, ctx),
6582
+ status: "direct",
6583
+ lumiaType: "train"
6584
+ };
6545
6585
  return {
6546
6586
  unit: mapUnsupportedAsText(widget, ctx),
6547
6587
  status: "placeholder",
@@ -9577,7 +9617,6 @@ var REVIEW_REASONS = {
9577
9617
  // `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
9578
9618
  // never reaches the placeholder-reason fallback. If a future feature flag
9579
9619
  // disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
9580
- "se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
9581
9620
  "se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
9582
9621
  "se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
9583
9622
  "se-widget-giveaway": "No native Lumia equivalent \u2014 SE giveaway service.",
@@ -9608,9 +9647,7 @@ function extractSEOverlayId(input) {
9608
9647
  }
9609
9648
  function extractSEPreviewParts(input) {
9610
9649
  const trimmed = input.trim();
9611
- const m = trimmed.match(
9612
- /streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
9613
- );
9650
+ const m = trimmed.match(/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i);
9614
9651
  if (!m) return null;
9615
9652
  const overlayId = m[1];
9616
9653
  const second = m[2];
@@ -9645,9 +9682,7 @@ async function fetchSEBootstrap({
9645
9682
  }
9646
9683
  const json = await res.json();
9647
9684
  if (!isSEBootstrap(json)) {
9648
- throw new Error(
9649
- "Server returned a response that does not look like an SE bootstrap payload."
9650
- );
9685
+ throw new Error("Server returned a response that does not look like an SE bootstrap payload.");
9651
9686
  }
9652
9687
  return json;
9653
9688
  }
@@ -9690,11 +9725,7 @@ function importSEBootstrap(bootstrap) {
9690
9725
  const lumiaGroupId = seUidToLumiaGroupId[uid];
9691
9726
  const parentUid = typeof w.group === "string" ? w.group : null;
9692
9727
  const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
9693
- const bounds = seCssToBounds(
9694
- w.css,
9695
- { width: 200, height: 200 },
9696
- importCanvas
9697
- );
9728
+ const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
9698
9729
  const layer = {
9699
9730
  id: lumiaGroupId,
9700
9731
  type: "group",
@@ -9827,9 +9858,7 @@ function applyReviewAction(result, moduleId, action, payload) {
9827
9858
  if (action === "skip") {
9828
9859
  delete modules[moduleId];
9829
9860
  const nextLayers = layers.filter((l) => l.id !== moduleId);
9830
- const reviewItems2 = result.reviewItems.filter(
9831
- (r) => r.moduleId !== moduleId
9832
- );
9861
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
9833
9862
  return {
9834
9863
  ...result,
9835
9864
  overlay: {
@@ -9878,9 +9907,7 @@ function applyReviewAction(result, moduleId, action, payload) {
9878
9907
  modules[extra.module.id] = extra.module;
9879
9908
  }
9880
9909
  modules[moduleId] = newModule;
9881
- const reviewItems2 = result.reviewItems.filter(
9882
- (r) => r.moduleId !== moduleId
9883
- );
9910
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
9884
9911
  return {
9885
9912
  ...result,
9886
9913
  overlay: {
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
 
4
- type ModuleType = 'alert' | 'emotebox' | 'emotealert' | 'text' | 'image' | 'audio' | 'video' | 'camera' | 'slideshow' | 'shape' | 'svg' | 'colorbox' | 'goal' | 'timer' | 'gradientlight' | 'browsersource' | 'chatbox' | 'credits' | 'eventlist' | 'eventrotate' | 'hfx' | 'hfxalert' | 'clip' | 'youtube' | 'spinwheel' | 'raffle' | 'viewerqueue' | 'loyaltyleaderboard' | 'viewerprofiles' | 'achievements' | 'poll' | 'tournaments' | 'spotify' | 'nowplaying' | 'youtubemusic' | 'brbscreen' | 'vlc' | 'tts' | 'livecaption' | 'custom' | 'tipjar' | 'hypetrain' | 'streamboss' | 'songrequest' | 'group';
4
+ type ModuleType = 'alert' | 'emotebox' | 'emotealert' | 'text' | 'image' | 'audio' | 'video' | 'camera' | 'slideshow' | 'shape' | 'svg' | 'colorbox' | 'goal' | 'timer' | 'gradientlight' | 'browsersource' | 'chatbox' | 'credits' | 'eventlist' | 'eventrotate' | 'hfx' | 'hfxalert' | 'clip' | 'youtube' | 'spinwheel' | 'raffle' | 'viewerqueue' | 'loyaltyleaderboard' | 'viewerprofiles' | 'achievements' | 'poll' | 'tournaments' | 'spotify' | 'nowplaying' | 'youtubemusic' | 'brbscreen' | 'vlc' | 'tts' | 'livecaption' | 'custom' | 'tipjar' | 'hypetrain' | 'streamboss' | 'songrequest' | 'train' | 'group';
5
5
  type OverlayLayerType = 'layer' | 'group';
6
6
  type OverlayLayerBounds = {
7
7
  height: number;
package/dist/se-import.js CHANGED
@@ -744,7 +744,11 @@ var LUMIA_DEFAULT_SIZES = {
744
744
  // card + 5-item queue list needs a bit more vertical room, so we add ~80px
745
745
  // for the queue list. Users can hide the queue list and shrink back to
746
746
  // just the card if they want SE's exact footprint.
747
- songrequest: { width: 480, height: 380 }
747
+ songrequest: { width: 480, height: 380 },
748
+ // Train: slim horizontal bar. SE train widgets vary in size but Lumia's
749
+ // layout is always a single-line ticker, so we fix the import footprint.
750
+ // (mapTrain also forces dimensions directly so this is a backstop only.)
751
+ train: { width: 540, height: 60 }
748
752
  };
749
753
  function translateVariationCondition(seType, seCondition, requirement, seEventType) {
750
754
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
@@ -1610,10 +1614,7 @@ function mapCustom(widget, ctx) {
1610
1614
  }
1611
1615
 
1612
1616
  // src/se-import/mappers/misc.ts
1613
- import {
1614
- LumiaEventListTypes,
1615
- LumiaMapAlertTypeToEventListType
1616
- } from "@lumiastream/lumia-types";
1617
+ import { LumiaEventListTypes, LumiaMapAlertTypeToEventListType } from "@lumiastream/lumia-types";
1617
1618
 
1618
1619
  // src/se-import/mappers/module-defaults.ts
1619
1620
  var CHATBOX_DEFAULT_CONTENT_FIELDS = {
@@ -1674,13 +1675,24 @@ var CHATBOX_DEFAULT_MODULE = {
1674
1675
  function mapChatboxTheme(seTheme) {
1675
1676
  const t = (seTheme ?? "").toString().toLowerCase();
1676
1677
  if (!t || t === "default") return "simple";
1677
- if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
1678
- return "bubbly";
1678
+ if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly")) return "bubbly";
1679
1679
  if (t.includes("glass") || t.includes("transparent")) return "glass";
1680
1680
  if (t.includes("boxy") || t.includes("checker")) return "boxy";
1681
1681
  if (t.includes("basic")) return "basic";
1682
1682
  return "simple";
1683
1683
  }
1684
+ function defaultChatboxBackgroundForSeTheme(seTheme) {
1685
+ const t = (seTheme ?? "").toString().toLowerCase();
1686
+ if (!t || t === "default") return "transparent";
1687
+ if (t === "custom") return "transparent";
1688
+ if (t.includes("glass") || t.includes("transparent") || t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
1689
+ return "transparent";
1690
+ if (t.includes("white") || t.includes("light") || t.includes("clean") && !t.includes("dark"))
1691
+ return "transparent";
1692
+ if (t.includes("dark") || t.includes("boxy") || t.includes("checker") || t.includes("black"))
1693
+ return "#1e1e1e";
1694
+ return "#1e1e1e";
1695
+ }
1684
1696
  var KAPPAGEN_EVENT_CATEGORIES = {
1685
1697
  tip: [LumiaEventListTypes.DONATION],
1686
1698
  cheer: [
@@ -1696,9 +1708,7 @@ var KAPPAGEN_EVENT_CATEGORIES = {
1696
1708
  subscriber: [LumiaEventListTypes.SUBSCRIBERS],
1697
1709
  purchase: [LumiaEventListTypes.PURCHASES]
1698
1710
  };
1699
- var alertKeysByEventListType = Object.entries(
1700
- LumiaMapAlertTypeToEventListType
1701
- ).reduce(
1711
+ var alertKeysByEventListType = Object.entries(LumiaMapAlertTypeToEventListType).reduce(
1702
1712
  (acc, [alertKey, eventListType]) => {
1703
1713
  acc[eventListType] = acc[eventListType] ?? [];
1704
1714
  acc[eventListType].push(alertKey);
@@ -1743,21 +1753,15 @@ function mapChatbox(widget, ctx) {
1743
1753
  const messagePadding = paddingShorthand(messageCss);
1744
1754
  const DEFAULT_CHATBOX_FONT_SIZE = 14;
1745
1755
  const moduleCss = {};
1746
- if (textCss["font-family"] != null)
1747
- moduleCss.fontFamily = textCss["font-family"];
1756
+ if (textCss["font-family"] != null) moduleCss.fontFamily = textCss["font-family"];
1748
1757
  moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
1749
1758
  if (textCss["color"] != null) moduleCss.color = textCss["color"];
1750
- if (textCss["font-weight"] != null)
1751
- moduleCss.fontWeight = textCss["font-weight"];
1752
- if (textCss["text-shadow"] != null)
1753
- moduleCss.textShadow = textCss["text-shadow"];
1754
- if (textCss["text-align"] != null)
1755
- moduleCss.textAlign = textCss["text-align"];
1756
- if (textCss["line-height"] != null)
1757
- moduleCss.lineHeight = textCss["line-height"];
1759
+ if (textCss["font-weight"] != null) moduleCss.fontWeight = textCss["font-weight"];
1760
+ if (textCss["text-shadow"] != null) moduleCss.textShadow = textCss["text-shadow"];
1761
+ if (textCss["text-align"] != null) moduleCss.textAlign = textCss["text-align"];
1762
+ if (textCss["line-height"] != null) moduleCss.lineHeight = textCss["line-height"];
1758
1763
  if (messagePadding) moduleCss.padding = messagePadding;
1759
- const DEFAULT_CHATBOX_BG = "#1e1e1e";
1760
- const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
1764
+ const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : defaultChatboxBackgroundForSeTheme(v.theme);
1761
1765
  moduleCss.background = messageBackground;
1762
1766
  const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
1763
1767
  const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
@@ -1867,9 +1871,7 @@ var SE_LISTENER_TO_LUMIA_CATEGORY = {
1867
1871
  function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1868
1872
  const enabled = [];
1869
1873
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
1870
- for (const [k, v] of Object.entries(
1871
- seListeners
1872
- )) {
1874
+ for (const [k, v] of Object.entries(seListeners)) {
1873
1875
  if (v) enabled.push(k);
1874
1876
  }
1875
1877
  } else if (Array.isArray(seListeners)) {
@@ -1887,6 +1889,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1887
1889
  if (allowedCategories.size === 0) return [];
1888
1890
  const allowedAlerts = [];
1889
1891
  Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
1892
+ if (alertKey.includes("session")) return;
1890
1893
  const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
1891
1894
  if (mappedCategory && allowedCategories.has(mappedCategory)) {
1892
1895
  allowedAlerts.push(alertKey);
@@ -1894,7 +1897,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1894
1897
  });
1895
1898
  return allowedAlerts;
1896
1899
  }
1897
- var LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND = 20;
1900
+ var LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND = 70;
1898
1901
  function seScrollSpeedToPixelsPerSecond(_seSpeed) {
1899
1902
  return LUMIA_DEFAULT_MARQUEE_PIXELS_PER_SECOND;
1900
1903
  }
@@ -1904,10 +1907,7 @@ function seTemplateToMarqueeTemplate(seTemplate) {
1904
1907
  }
1905
1908
  function mapEventList(widget, ctx) {
1906
1909
  const v = widget.variables ?? {};
1907
- const allowedEvents = computeAllowedEventsFromSEListeners(
1908
- widget.listeners,
1909
- widget.listener
1910
- );
1910
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
1911
1911
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
1912
1912
  const seText = widget.text;
1913
1913
  const scrolling = seText?.scrolling;
@@ -1956,7 +1956,7 @@ function mapEventList(widget, ctx) {
1956
1956
  ...isMarquee ? { marqueeItemGap: 24 } : {},
1957
1957
  showAlertIcon: !isMarquee,
1958
1958
  showSiteIcon: !isMarquee,
1959
- showUserAvatar: !isMarquee,
1959
+ showUserAvatar: true,
1960
1960
  showDetailedText: false,
1961
1961
  hideAlertMessage: false
1962
1962
  }
@@ -2194,10 +2194,7 @@ function mapSlideshow(widget, ctx) {
2194
2194
  const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
2195
2195
  if (!url) return null;
2196
2196
  return {
2197
- type: inferSlideshowMediaType(
2198
- typeof raw === "object" ? raw?.type : void 0,
2199
- url
2200
- ),
2197
+ type: inferSlideshowMediaType(typeof raw === "object" ? raw?.type : void 0, url),
2201
2198
  src: url,
2202
2199
  name: nameFromUrl(url)
2203
2200
  };
@@ -2242,6 +2239,46 @@ function mapSlideshow(widget, ctx) {
2242
2239
  ctx
2243
2240
  );
2244
2241
  }
2242
+ function mapTrain(widget, ctx) {
2243
+ const v = widget.variables ?? {};
2244
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
2245
+ const TRAIN_WIDTH = 540;
2246
+ const TRAIN_HEIGHT = v.vertical === true ? 200 : 60;
2247
+ const widgetWithSize = {
2248
+ ...widget,
2249
+ css: { ...widget.css, width: TRAIN_WIDTH, height: TRAIN_HEIGHT }
2250
+ };
2251
+ return buildUnit(
2252
+ widgetWithSize,
2253
+ "train",
2254
+ {
2255
+ css: mapSeTextCssToModuleCss(widget, {
2256
+ fontSize: 24,
2257
+ fontFamily: "Nunito",
2258
+ fontWeight: "bold",
2259
+ color: "#ffffff",
2260
+ textShadow: "rgb(0, 0, 0) 1px 1px 1px",
2261
+ textAlign: "left",
2262
+ lineHeight: 1.3,
2263
+ background: "transparent"
2264
+ }),
2265
+ content: {
2266
+ version: 1,
2267
+ // SE stores train duration in minutes; Lumia stores it in seconds.
2268
+ duration: typeof v.duration === "number" && v.duration > 0 ? v.duration * 60 : 300,
2269
+ autoHide: false,
2270
+ showCount: v.showCount !== false,
2271
+ showTime: v.showTime !== false,
2272
+ showLatest: v.show_latest !== false,
2273
+ showIcons: v.icons !== false,
2274
+ vertical: v.vertical === true,
2275
+ ...allowedEvents !== void 0 ? { allowedEvents } : { allowedEvents: [] }
2276
+ }
2277
+ },
2278
+ ctx,
2279
+ { width: 540, height: 60 }
2280
+ );
2281
+ }
2245
2282
  function mapTimer(widget, ctx) {
2246
2283
  const v = widget.variables ?? {};
2247
2284
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
@@ -2268,9 +2305,7 @@ function mapTimer(widget, ctx) {
2268
2305
  resolved = true;
2269
2306
  }
2270
2307
  }
2271
- if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
2272
- (part) => typeof part === "number"
2273
- )) {
2308
+ if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some((part) => typeof part === "number")) {
2274
2309
  days = typeof v.days === "number" ? v.days : 0;
2275
2310
  hours = typeof v.hours === "number" ? v.hours : 0;
2276
2311
  minutes = typeof v.minutes === "number" ? v.minutes : 0;
@@ -2378,8 +2413,7 @@ function mapNowPlaying(widget, ctx) {
2378
2413
  };
2379
2414
  if (typeof seTextCss["text-transform"] === "string")
2380
2415
  importedCss.textTransform = seTextCss["text-transform"];
2381
- if (typeof seTextCss["font-style"] === "string")
2382
- importedCss.fontStyle = seTextCss["font-style"];
2416
+ if (typeof seTextCss["font-style"] === "string") importedCss.fontStyle = seTextCss["font-style"];
2383
2417
  if (typeof seTextCss["text-decoration"] === "string")
2384
2418
  importedCss.textDecoration = seTextCss["text-decoration"];
2385
2419
  if (typeof seTextCss["letter-spacing"] === "string" || typeof seTextCss["letter-spacing"] === "number") {
@@ -3408,6 +3442,12 @@ function dispatch(widget, ctx) {
3408
3442
  status: "direct",
3409
3443
  lumiaType: "image"
3410
3444
  };
3445
+ if (t === "se-widget-train")
3446
+ return {
3447
+ unit: mapTrain(widget, ctx),
3448
+ status: "direct",
3449
+ lumiaType: "train"
3450
+ };
3411
3451
  return {
3412
3452
  unit: mapUnsupportedAsText(widget, ctx),
3413
3453
  status: "placeholder",
@@ -8447,7 +8487,6 @@ var REVIEW_REASONS = {
8447
8487
  // `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
8448
8488
  // never reaches the placeholder-reason fallback. If a future feature flag
8449
8489
  // disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
8450
- "se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
8451
8490
  "se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
8452
8491
  "se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
8453
8492
  "se-widget-giveaway": "No native Lumia equivalent \u2014 SE giveaway service.",
@@ -8478,9 +8517,7 @@ function extractSEOverlayId(input) {
8478
8517
  }
8479
8518
  function extractSEPreviewParts(input) {
8480
8519
  const trimmed = input.trim();
8481
- const m = trimmed.match(
8482
- /streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
8483
- );
8520
+ const m = trimmed.match(/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i);
8484
8521
  if (!m) return null;
8485
8522
  const overlayId = m[1];
8486
8523
  const second = m[2];
@@ -8515,9 +8552,7 @@ async function fetchSEBootstrap({
8515
8552
  }
8516
8553
  const json = await res.json();
8517
8554
  if (!isSEBootstrap(json)) {
8518
- throw new Error(
8519
- "Server returned a response that does not look like an SE bootstrap payload."
8520
- );
8555
+ throw new Error("Server returned a response that does not look like an SE bootstrap payload.");
8521
8556
  }
8522
8557
  return json;
8523
8558
  }
@@ -8560,11 +8595,7 @@ function importSEBootstrap(bootstrap) {
8560
8595
  const lumiaGroupId = seUidToLumiaGroupId[uid];
8561
8596
  const parentUid = typeof w.group === "string" ? w.group : null;
8562
8597
  const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
8563
- const bounds = seCssToBounds(
8564
- w.css,
8565
- { width: 200, height: 200 },
8566
- importCanvas
8567
- );
8598
+ const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
8568
8599
  const layer = {
8569
8600
  id: lumiaGroupId,
8570
8601
  type: "group",
@@ -8697,9 +8728,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8697
8728
  if (action === "skip") {
8698
8729
  delete modules[moduleId];
8699
8730
  const nextLayers = layers.filter((l) => l.id !== moduleId);
8700
- const reviewItems2 = result.reviewItems.filter(
8701
- (r) => r.moduleId !== moduleId
8702
- );
8731
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
8703
8732
  return {
8704
8733
  ...result,
8705
8734
  overlay: {
@@ -8748,9 +8777,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8748
8777
  modules[extra.module.id] = extra.module;
8749
8778
  }
8750
8779
  modules[moduleId] = newModule;
8751
- const reviewItems2 = result.reviewItems.filter(
8752
- (r) => r.moduleId !== moduleId
8753
- );
8780
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
8754
8781
  return {
8755
8782
  ...result,
8756
8783
  overlay: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.3.9",
3
+ "version": "0.4.1",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",