poe-svelte-ui-lib 1.89.0 → 1.90.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/README.md CHANGED
@@ -117,6 +117,8 @@ npm install poe-svelte-ui-lib
117
117
 
118
118
  Классы ролей (`.bg-red`, `.bg-max` и другие) и классы темы (`.light`, `.dark`) остаются вне слоя. Они как раз должны перебивать утилиты.
119
119
 
120
+ Текст ячейки `Table` выводится как текст, теги в данных устройства не работают. Если ячейке нужна разметка (цвет, ссылка), у колонки ставят `html: true`, а данные в ней экранируют сами функцией `escapeHtml`. `formatting` возвращает HTML для окна и подсказки и тоже сам экранирует текст.
121
+
120
122
  Шапку `Table` можно перекрасить переменной `--table-header-color`. По умолчанию она `--accent-soft`, смарт-виджеты задают её сами от своего цвета.
121
123
 
122
124
  Посмотреть всю тему разом можно на вкладке «Тема» на главной витрины. Там палитра, все компоненты с демо-данными, типографика и Markdown.
@@ -8,6 +8,7 @@
8
8
  import { T } from "../locales/i18n"
9
9
  import { Button, Modal } from ".."
10
10
  import CopiedChip from "../CopiedChip.svelte"
11
+ import { escapeHtml, htmlToText } from "../html"
11
12
 
12
13
  let {
13
14
  id = crypto.randomUUID(),
@@ -150,18 +151,21 @@
150
151
  }, 250)
151
152
  }
152
153
 
154
+ /* Текст ячейки: из HTML-ячейки берётся видимый текст, formatting получает его и возвращает HTML */
155
+ const cellText = (value: unknown, html?: boolean) => (html ? htmlToText(String(value ?? "")) : String(value ?? ""))
156
+
153
157
  const showModal = async (text: string, formatting?: (text: string) => string) => {
154
158
  modalData = {
155
159
  isOpen: true,
156
160
  rawData: text,
157
- formattedData: formatting ? formatting(text) : (text ?? ""),
161
+ formattedData: formatting ? formatting(text) : escapeHtml(text),
158
162
  }
159
163
  }
160
164
 
161
165
  const showTooltip = (event: MouseEvent, text: string, formatting?: (text: string) => string) => {
162
166
  tooltip = {
163
167
  show: true,
164
- text: formatting ? formatting(text) : (text ?? ""),
168
+ text: formatting ? htmlToText(formatting(text)) : text,
165
169
  x: event.clientX,
166
170
  y: event.clientY,
167
171
  }
@@ -442,7 +446,7 @@
442
446
  <div class="flex w-full min-w-0 items-center">
443
447
  <div
444
448
  class="w-full max-w-full min-w-0 wrap-break-word {text?.truncated ? 'truncate' : ' whitespace-normal'}"
445
- onmouseenter={text?.tooltip ? (e) => showTooltip(e, row[text?.key ?? ""], text?.formatting) : undefined}
449
+ onmouseenter={text?.tooltip ? (e) => showTooltip(e, cellText(row[text?.key ?? ""], text?.html), text?.formatting) : undefined}
446
450
  onmouseleave={() => (text?.tooltip ? (tooltip.show = false) : undefined)}
447
451
  onmousemove={text?.tooltip
448
452
  ? (e) => {
@@ -458,13 +462,15 @@
458
462
  class="cursor-pointer text-left {text?.truncated ? 'block w-full max-w-full truncate' : 'w-fit'}"
459
463
  onclick={(e) => {
460
464
  e.stopPropagation()
461
- showModal(data.replace(/<[^>]*>/g, ""), text?.formatting)
465
+ showModal(cellText(data, text?.html), text?.formatting)
462
466
  }}
463
467
  >
464
- {@html data}
468
+ {#if text?.html}{@html data}{:else}{data}{/if}
465
469
  </button>
466
- {:else}
470
+ {:else if text?.html}
467
471
  {@html data}
472
+ {:else}
473
+ {data}
468
474
  {/if}
469
475
  </div>
470
476
 
@@ -473,7 +479,7 @@
473
479
  class="ml-1.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded border-none bg-transparent text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)"
474
480
  onclick={(e) => {
475
481
  e.preventDefault()
476
- navigator.clipboard.writeText(row[text?.key ?? ""].replace(/<[^>]*>/g, ""))
482
+ navigator.clipboard.writeText(cellText(row[text?.key ?? ""], text?.html))
477
483
  copiedCell = { x: j, y: i }
478
484
  setTimeout(() => (copiedCell = null), 1000)
479
485
  }}
@@ -514,7 +520,7 @@
514
520
  transition:fly={{ y: 8, duration: 200 }}
515
521
  role="tooltip"
516
522
  >
517
- {@html tooltip.text.replace(/<[^>]*>/g, "")}
523
+ {tooltip.text}
518
524
  </div>
519
525
  {/if}
520
526
  <!-- Нижнее поле для сводной информации -->
@@ -8,7 +8,7 @@ export const xPoeDebugTableColumns = [
8
8
  { label: { name: "ARG" }, content: [{ type: "text", data: { key: "argument" } }], width: tableMonoWidth(16, 3), align: "left" },
9
9
  {
10
10
  label: { name: "VALUE" },
11
- content: [{ type: "text", data: { key: "value", truncated: true, formatting: formatXPoeDebugModal, copy: true, modal: true } }],
11
+ content: [{ type: "text", data: { key: "value", html: true, truncated: true, formatting: formatXPoeDebugModal, copy: true, modal: true } }],
12
12
  width: "1fr",
13
13
  align: "left",
14
14
  },
@@ -78,8 +78,6 @@
78
78
  }
79
79
  const percentColor = (p: number) => (p >= 90 ? "var(--red-color)" : p >= 75 ? "var(--orange-color)" : "var(--muted-color)")
80
80
 
81
- /* Имена задач приходят с устройства, а Table выводит ячейки через {@html} - экранируем */
82
- const escapeHtml = (text: string) => text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`)
83
81
  let STATES = $derived(["running", "ready", "blocked", "suspended", "deleted"].map((state) => $T(`widget.stack.state.${state}`)))
84
82
  const stackColor = (bytes: number) => (bytes < warnBytes ? "var(--red-color)" : bytes < warnBytes * 2 ? "var(--orange-color)" : "")
85
83
 
@@ -97,7 +95,7 @@
97
95
  const color = stackColor(t.FreeMin)
98
96
  const cpu = t.CPU === undefined ? "" : t.CPU.toFixed(1)
99
97
  return {
100
- Name: escapeHtml(t.Name),
98
+ Name: t.Name,
101
99
  StateText: STATES[t.State] ?? "-",
102
100
  Prio: t.Prio,
103
101
  CoreText: t.Core === undefined ? "" : t.Core < 0 ? $T("widget.stack.core_any") : String(t.Core),
@@ -124,13 +122,13 @@
124
122
  ? [
125
123
  {
126
124
  label: { name: $T("widget.stack.cpu") },
127
- content: [{ type: "text", data: { key: "CpuText" } }],
125
+ content: [{ type: "text", data: { key: "CpuText", html: true } }],
128
126
  width: "5.5rem",
129
127
  align: "right",
130
128
  } as ITableHeader<TaskRow>,
131
129
  ]
132
130
  : []),
133
- { label: { name: $T("widget.stack.free_min") }, content: [{ type: "text", data: { key: "FreeMinText" } }], width: "6rem", align: "right" },
131
+ { label: { name: $T("widget.stack.free_min") }, content: [{ type: "text", data: { key: "FreeMinText", html: true } }], width: "6rem", align: "right" },
134
132
  ])
135
133
  </script>
136
134
 
package/dist/html.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const escapeHtml: (value: unknown) => string;
2
+ export declare const htmlToText: (html: string) => string;
package/dist/html.js ADDED
@@ -0,0 +1,10 @@
1
+ /* $lib/html.ts */
2
+ const HTML_ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
3
+ /* Текст для вставки в HTML: спецсимволы заменяются сущностями */
4
+ export const escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
5
+ /* Видимый текст HTML-строки: DOMParser создаёт неактивный документ, скрипты и обработчики в нём не запускаются */
6
+ export const htmlToText = (html) => {
7
+ if (typeof DOMParser === "undefined")
8
+ return html.replace(/<[^>]*>?/g, "");
9
+ return new DOMParser().parseFromString(html, "text/html").body.textContent ?? "";
10
+ };
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export * from "./locales/i18n";
60
60
  export * from "./locales/languages";
61
61
  export * from "./locales/localize";
62
62
  export { widgetAccentStyle } from "./widgetAccent";
63
+ export { escapeHtml, htmlToText } from "./html";
63
64
  export { hintsSetting, hint, type HintParams } from "./hints";
64
65
  export { applyDragGhost, clearDragGhost, createDropPlaceholder } from "./dragStyle";
65
66
  export * from "./ModalStackStore";
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ export * from "./locales/i18n";
61
61
  export * from "./locales/languages";
62
62
  export * from "./locales/localize";
63
63
  export { widgetAccentStyle } from "./widgetAccent";
64
+ export { escapeHtml, htmlToText } from "./html";
64
65
  export { hintsSetting, hint } from "./hints";
65
66
  export { applyDragGhost, clearDragGhost, createDropPlaceholder } from "./dragStyle";
66
67
  export * from "./ModalStackStore";
@@ -207,7 +207,7 @@ export function formatXPoeDebugModal(text) {
207
207
  const hexIndex = text.lastIndexOf(separator);
208
208
  const hex = hexIndex < 0 ? "" : text.slice(hexIndex + separator.length);
209
209
  const pretty = hex ? formatXPoeDebugPrettyHtml(hex) : null;
210
- const decoded = pretty === null ? (hexIndex < 0 ? text : text.slice(0, hexIndex)) : pretty;
210
+ const decoded = pretty === null ? escapeHtml(hexIndex < 0 ? text : text.slice(0, hexIndex)) : pretty;
211
211
  const modalStyle = "display:flex;flex-direction:column;gap:.75rem;max-height:80vh;min-width:0;overflow-y:auto;overflow-x:hidden";
212
212
  const sectionStyle = "min-width:0;padding:.25rem 0 .75rem";
213
213
  const headingStyle = "display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:0 0 .5rem;font-weight:600";
@@ -215,5 +215,5 @@ export function formatXPoeDebugModal(text) {
215
215
  const buttonClass = "flex size-7 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)";
216
216
  const textStyle = "display:block;min-width:0;max-width:100%;margin:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:inherit";
217
217
  const pane = (title, target, value) => `<section style="${sectionStyle}"><div style="${headingStyle}"><span>${title}</span><button type="button" class="${buttonClass}" data-xpoe-copy="${target}" aria-label="Копировать ${title}" style="${buttonStyle}">${COPY_ICON}</button></div><pre style="${textStyle}">${value}</pre></section>`;
218
- return `<div data-xpoe-debug-modal style="${modalStyle}">${pane("Value", "value", decoded)}${hexIndex < 0 ? "" : pane("HEX", "hex", hex)}</div>`;
218
+ return `<div data-xpoe-debug-modal style="${modalStyle}">${pane("Value", "value", decoded)}${hexIndex < 0 ? "" : pane("HEX", "hex", escapeHtml(hex))}</div>`;
219
219
  }
package/dist/types.d.ts CHANGED
@@ -316,6 +316,7 @@ export interface ITableText<T extends object> {
316
316
  sortable?: boolean;
317
317
  truncated?: boolean;
318
318
  tooltip?: boolean;
319
+ html?: boolean;
319
320
  formatting?: (text: string) => string;
320
321
  copy?: boolean;
321
322
  modal?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.89.0",
3
+ "version": "1.90.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"