1ch 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ type MediaRef = {
2
+ tag: string;
3
+ src?: string;
4
+ alt?: string;
5
+ widthCells: number;
6
+ heightLines: number;
7
+ attrs?: Record<string, string>;
8
+ };
1
9
  type Style = {
2
10
  text: string;
3
11
  color?: string;
@@ -10,6 +18,7 @@ type Style = {
10
18
  onClick?: () => void;
11
19
  noSelect?: boolean;
12
20
  cell?: number;
21
+ media?: MediaRef;
13
22
  };
14
23
  type Segment = string | Style;
15
24
  type Line = Segment[];
@@ -209,7 +218,6 @@ declare function tabs(names: string[], active: number, opts?: {
209
218
  separatorColor?: string;
210
219
  onSelect?: (index: number) => void;
211
220
  onClicks?: ((() => void) | undefined)[];
212
- interactionIds?: (string | undefined)[];
213
221
  theme?: Theme;
214
222
  }): LayoutFn;
215
223
  declare function badge(label: string, opts?: {
@@ -482,6 +490,14 @@ type TermIRNode = {
482
490
  text: string;
483
491
  color?: string;
484
492
  action: TermAction;
493
+ } | {
494
+ kind: "media";
495
+ tag: string;
496
+ src?: string;
497
+ alt?: string;
498
+ widthCells?: number;
499
+ heightLines?: number;
500
+ attrs?: Record<string, string>;
485
501
  };
486
502
 
487
503
  type ActionSeed = {
package/dist/index.js CHANGED
@@ -497,20 +497,17 @@ function tabs(names, active, opts) {
497
497
  const name = names[i];
498
498
  const label = " " + (isActive ? name.toUpperCase() : name) + " ";
499
499
  const onClick = opts?.onClicks?.[i] ?? (onSelect ? () => onSelect(i) : void 0);
500
- const interactionId = onClick ? opts?.interactionIds?.[i] : void 0;
501
500
  if (isActive) {
502
501
  line.push({
503
502
  text: label,
504
503
  color: opts?.activeColor ?? t?.components.tabs.activeFg,
505
504
  bg: opts?.activeBg ?? t?.components.tabs.activeBg,
506
- interactionId,
507
505
  onClick
508
506
  });
509
507
  } else {
510
508
  line.push({
511
509
  text: label,
512
510
  color: opts?.inactiveColor ?? t?.components.tabs.inactiveFg,
513
- interactionId,
514
511
  onClick
515
512
  });
516
513
  }
@@ -1863,6 +1860,49 @@ function registerDefaultCompilers() {
1863
1860
  action: ctx.actionFromElement(el, { type: "action", id, text })
1864
1861
  };
1865
1862
  });
1863
+ htmlTagRegistry.set("img", (el) => {
1864
+ const src = el.getAttribute("src")?.trim();
1865
+ if (!src) return null;
1866
+ return {
1867
+ kind: "media",
1868
+ tag: "img",
1869
+ src,
1870
+ alt: el.getAttribute("alt")?.trim() ?? void 0,
1871
+ widthCells: parseIntegerAttrs(el, ["width"]) ?? void 0,
1872
+ heightLines: parseIntegerAttrs(el, ["height"]) ?? void 0
1873
+ };
1874
+ });
1875
+ htmlTagRegistry.set("video", (el) => {
1876
+ const src = el.getAttribute("src")?.trim() ?? void 0;
1877
+ const attrs = {};
1878
+ if (el.hasAttribute("controls")) attrs.controls = "";
1879
+ if (el.hasAttribute("autoplay")) attrs.autoplay = "";
1880
+ if (el.hasAttribute("loop")) attrs.loop = "";
1881
+ if (el.hasAttribute("muted")) attrs.muted = "";
1882
+ const poster = el.getAttribute("poster")?.trim();
1883
+ if (poster) attrs.poster = poster;
1884
+ return {
1885
+ kind: "media",
1886
+ tag: "video",
1887
+ src,
1888
+ widthCells: parseIntegerAttrs(el, ["width"]) ?? void 0,
1889
+ heightLines: parseIntegerAttrs(el, ["height"]) ?? void 0,
1890
+ attrs: Object.keys(attrs).length > 0 ? attrs : void 0
1891
+ };
1892
+ });
1893
+ htmlTagRegistry.set("audio", (el) => {
1894
+ const src = el.getAttribute("src")?.trim() ?? void 0;
1895
+ const attrs = {};
1896
+ if (el.hasAttribute("controls")) attrs.controls = "";
1897
+ if (el.hasAttribute("autoplay")) attrs.autoplay = "";
1898
+ if (el.hasAttribute("loop")) attrs.loop = "";
1899
+ return {
1900
+ kind: "media",
1901
+ tag: "audio",
1902
+ src,
1903
+ attrs: Object.keys(attrs).length > 0 ? attrs : void 0
1904
+ };
1905
+ });
1866
1906
  htmlTagRegistry.set("progress", (el, ctx) => compileBarNode(el, ctx));
1867
1907
  htmlTagRegistry.set("a", (el, ctx) => {
1868
1908
  const text = normalizeHtmlText(el.textContent ?? "");
@@ -2882,6 +2922,29 @@ function layoutFromIR(node, options) {
2882
2922
  return buttonLayout(node, options);
2883
2923
  case "link":
2884
2924
  return linkLayout(node, options);
2925
+ case "media": {
2926
+ const heightLines = node.heightLines ?? 6;
2927
+ return (w) => {
2928
+ const widthCells = node.widthCells != null ? Math.min(node.widthCells, w) : w;
2929
+ const block = [];
2930
+ const markerSeg = {
2931
+ text: " ".repeat(widthCells),
2932
+ media: {
2933
+ tag: node.tag,
2934
+ src: node.src,
2935
+ alt: node.alt,
2936
+ widthCells,
2937
+ heightLines,
2938
+ attrs: node.attrs
2939
+ }
2940
+ };
2941
+ block.push(padLine([markerSeg], w));
2942
+ for (let i = 1; i < heightLines; i++) {
2943
+ block.push(padLine([" ".repeat(w)], w));
2944
+ }
2945
+ return block;
2946
+ };
2947
+ }
2885
2948
  default:
2886
2949
  return blank();
2887
2950
  }
@@ -3116,18 +3179,79 @@ function patchScrollRegion(region, lines2, meta, theme, actions, regionKey) {
3116
3179
  }
3117
3180
  }
3118
3181
  function patchRoot(target, units, theme, actions) {
3182
+ const existingNonMedia = [];
3183
+ for (const child of Array.from(target.children)) {
3184
+ if (child.classList.contains("termui-media")) continue;
3185
+ existingNonMedia.push(child);
3186
+ }
3119
3187
  for (let unitIndex = 0; unitIndex < units.length; unitIndex++) {
3120
3188
  const unit = units[unitIndex];
3121
3189
  if (unit.kind === "line") {
3122
- const lineDiv = createOrReplaceChildDiv(target, unitIndex, "termui-line");
3190
+ const existing2 = existingNonMedia[unitIndex];
3191
+ let lineDiv;
3192
+ if (existing2 instanceof HTMLDivElement && existing2.className === "termui-line") {
3193
+ lineDiv = existing2;
3194
+ } else {
3195
+ lineDiv = document.createElement("div");
3196
+ lineDiv.className = "termui-line";
3197
+ if (existing2) target.replaceChild(lineDiv, existing2);
3198
+ else target.insertBefore(lineDiv, target.querySelector(".termui-media"));
3199
+ }
3123
3200
  patchLine(lineDiv, unit.line, theme, actions, `u${unitIndex}:l0`);
3124
3201
  continue;
3125
3202
  }
3126
- const region = createOrReplaceChildDiv(target, unitIndex, "termui-scroll-region");
3203
+ const existing = existingNonMedia[unitIndex];
3204
+ let region;
3205
+ if (existing instanceof HTMLDivElement && existing.className === "termui-scroll-region") {
3206
+ region = existing;
3207
+ } else {
3208
+ region = document.createElement("div");
3209
+ region.className = "termui-scroll-region";
3210
+ if (existing) target.replaceChild(region, existing);
3211
+ else target.insertBefore(region, target.querySelector(".termui-media"));
3212
+ }
3127
3213
  patchScrollRegion(region, unit.lines, unit.meta, theme, actions, `u${unitIndex}`);
3128
3214
  }
3129
- while (target.children.length > units.length) {
3130
- target.lastElementChild?.remove();
3215
+ const nonMediaChildren = Array.from(target.children).filter(
3216
+ (c) => !c.classList.contains("termui-media")
3217
+ );
3218
+ while (nonMediaChildren.length > units.length) {
3219
+ nonMediaChildren.pop().remove();
3220
+ }
3221
+ }
3222
+ function renderMediaOverlays(target, block) {
3223
+ for (const old of Array.from(target.querySelectorAll(".termui-media"))) {
3224
+ old.remove();
3225
+ }
3226
+ for (let lineIndex = 0; lineIndex < block.length; lineIndex++) {
3227
+ const line = block[lineIndex];
3228
+ let colOffset = 0;
3229
+ for (const seg of line) {
3230
+ if (typeof seg === "string") {
3231
+ colOffset += seg.length;
3232
+ continue;
3233
+ }
3234
+ if (seg.media) {
3235
+ const el = document.createElement(seg.media.tag);
3236
+ if (seg.media.src) el.setAttribute("src", seg.media.src);
3237
+ if (seg.media.alt && seg.media.tag === "img") el.setAttribute("alt", seg.media.alt);
3238
+ if (seg.media.attrs) {
3239
+ for (const [k, v] of Object.entries(seg.media.attrs)) el.setAttribute(k, v);
3240
+ }
3241
+ el.className = "termui-media";
3242
+ el.style.position = "absolute";
3243
+ el.style.top = `calc(${lineIndex} * var(--cell-h))`;
3244
+ el.style.left = `${colOffset}ch`;
3245
+ el.style.width = `${seg.media.widthCells}ch`;
3246
+ el.style.height = `calc(${seg.media.heightLines} * var(--cell-h))`;
3247
+ if (seg.media.tag === "img" || seg.media.tag === "video") {
3248
+ el.style.objectFit = "cover";
3249
+ }
3250
+ el.style.display = "block";
3251
+ target.appendChild(el);
3252
+ }
3253
+ colOffset += seg.text.length;
3254
+ }
3131
3255
  }
3132
3256
  }
3133
3257
  function renderTermBlock(target, block, theme) {
@@ -3135,6 +3259,7 @@ function renderTermBlock(target, block, theme) {
3135
3259
  const actions = /* @__PURE__ */ new Map();
3136
3260
  const units = buildRenderUnits(block);
3137
3261
  patchRoot(target, units, theme, actions);
3262
+ renderMediaOverlays(target, block);
3138
3263
  state.actions = actions;
3139
3264
  }
3140
3265
 
@@ -3162,6 +3287,7 @@ var TERM_UI_CSS = `
3162
3287
  }
3163
3288
  .termui-root {
3164
3289
  white-space: pre;
3290
+ position: relative;
3165
3291
  }
3166
3292
  .termui-line {
3167
3293
  line-height: var(--cell-h);
@@ -3177,6 +3303,9 @@ var TERM_UI_CSS = `
3177
3303
  .termui-scroll-region {
3178
3304
  overflow: hidden;
3179
3305
  }
3306
+ .termui-media {
3307
+ pointer-events: auto;
3308
+ }
3180
3309
  .termui-probe {
3181
3310
  font: inherit;
3182
3311
  white-space: pre;