zenn-embed-elements 0.4.3 → 0.4.4

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.
@@ -1,6 +1,41 @@
1
+ /**
2
+ * KaTeX数式をレンダリングするWeb Component
3
+ *
4
+ * ## 使用例
5
+ * ```html
6
+ * <embed-katex><eq>x^2 + y^2 = z^2</eq></embed-katex>
7
+ * <embed-katex display-mode><eq>\frac{-b \pm \sqrt{b^2-4ac}}{2a}</eq></embed-katex>
8
+ * ```
9
+ *
10
+ * ## MutationObserverについて
11
+ *
12
+ * morphdomなどのDOM差分更新ライブラリを使用する場合、既存のembed-katex要素の
13
+ * 内容が更新されてもconnectedCallbackが発火しない。これにより、レンダリング済みの
14
+ * 数式がrender前の状態(<eq>タグ)に戻されてしまう問題がある。
15
+ *
16
+ * MutationObserverで子要素の変更を監視し、未レンダリング状態を検出した場合に
17
+ * 自動的にrender()を呼び出すことで、この問題を解決している。
18
+ */
1
19
  export declare class EmbedKatex extends HTMLElement {
20
+ /** KaTeXレンダリング用の一時コンテナ */
2
21
  private _container;
22
+ /** DOM変更を監視するObserver */
23
+ private _observer;
24
+ /** レンダリング中フラグ(無限ループ防止用) */
25
+ private _isRendering;
3
26
  constructor();
4
27
  connectedCallback(): Promise<void>;
28
+ disconnectedCallback(): void;
29
+ /**
30
+ * MutationObserverをセットアップする
31
+ *
32
+ * 子要素の変更を監視し、morphdomなどによるDOM更新後に
33
+ * 未レンダリング状態であればrender()を再実行する。
34
+ */
35
+ private _setupObserver;
36
+ /**
37
+ * MutationObserverをクリーンアップする(メモリリーク防止)
38
+ */
39
+ private _cleanupObserver;
5
40
  render(): Promise<void>;
6
41
  }
@@ -13,9 +13,31 @@ exports.EmbedKatex = void 0;
13
13
  const load_script_1 = require("../utils/load-script");
14
14
  const load_stylesheet_1 = require("../utils/load-stylesheet");
15
15
  const containerId = 'katex-container';
16
+ /**
17
+ * KaTeX数式をレンダリングするWeb Component
18
+ *
19
+ * ## 使用例
20
+ * ```html
21
+ * <embed-katex><eq>x^2 + y^2 = z^2</eq></embed-katex>
22
+ * <embed-katex display-mode><eq>\frac{-b \pm \sqrt{b^2-4ac}}{2a}</eq></embed-katex>
23
+ * ```
24
+ *
25
+ * ## MutationObserverについて
26
+ *
27
+ * morphdomなどのDOM差分更新ライブラリを使用する場合、既存のembed-katex要素の
28
+ * 内容が更新されてもconnectedCallbackが発火しない。これにより、レンダリング済みの
29
+ * 数式がrender前の状態(<eq>タグ)に戻されてしまう問題がある。
30
+ *
31
+ * MutationObserverで子要素の変更を監視し、未レンダリング状態を検出した場合に
32
+ * 自動的にrender()を呼び出すことで、この問題を解決している。
33
+ */
16
34
  class EmbedKatex extends HTMLElement {
17
35
  constructor() {
18
36
  super();
37
+ /** DOM変更を監視するObserver */
38
+ this._observer = null;
39
+ /** レンダリング中フラグ(無限ループ防止用) */
40
+ this._isRendering = false;
19
41
  const container = document.createElement('div');
20
42
  container.setAttribute('id', containerId);
21
43
  this._container = container;
@@ -23,32 +45,79 @@ class EmbedKatex extends HTMLElement {
23
45
  connectedCallback() {
24
46
  return __awaiter(this, void 0, void 0, function* () {
25
47
  this.render();
48
+ this._setupObserver();
26
49
  });
27
50
  }
51
+ disconnectedCallback() {
52
+ this._cleanupObserver();
53
+ }
54
+ /**
55
+ * MutationObserverをセットアップする
56
+ *
57
+ * 子要素の変更を監視し、morphdomなどによるDOM更新後に
58
+ * 未レンダリング状態であればrender()を再実行する。
59
+ */
60
+ _setupObserver() {
61
+ if (this._observer)
62
+ return;
63
+ this._observer = new MutationObserver(() => {
64
+ // render()がinnerHTMLを変更するとObserverが発火するため、
65
+ // レンダリング中は無視して無限ループを防止する
66
+ if (this._isRendering)
67
+ return;
68
+ // 未レンダリング状態の判定:
69
+ // - <eq>タグが存在する = まだKaTeXでレンダリングされていない
70
+ // - .katexクラスがない = KaTeXのレンダリング結果が存在しない
71
+ if (this.querySelector('eq') && !this.querySelector('.katex')) {
72
+ this.render();
73
+ }
74
+ });
75
+ this._observer.observe(this, {
76
+ childList: true,
77
+ subtree: true,
78
+ });
79
+ }
80
+ /**
81
+ * MutationObserverをクリーンアップする(メモリリーク防止)
82
+ */
83
+ _cleanupObserver() {
84
+ if (this._observer) {
85
+ this._observer.disconnect();
86
+ this._observer = null;
87
+ }
88
+ }
28
89
  render() {
29
90
  return __awaiter(this, void 0, void 0, function* () {
30
- if (typeof katex === 'undefined') {
31
- yield (0, load_script_1.loadScript)({
91
+ if (this._isRendering)
92
+ return;
93
+ this._isRendering = true;
94
+ try {
95
+ if (typeof katex === 'undefined') {
96
+ yield (0, load_script_1.loadScript)({
97
+ // 本来はバージョンを指定した方がいいが、他の処理との兼ね合いでバージョンを固定するのは難しいので指定していません
98
+ src: `https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js`,
99
+ id: 'katex-js',
100
+ });
101
+ }
102
+ // CSSを読み込む(まだ読み込まれていない場合のみ)
103
+ (0, load_stylesheet_1.loadStylesheet)({
32
104
  // 本来はバージョンを指定した方がいいが、他の処理との兼ね合いでバージョンを固定するのは難しいので指定していません
33
- src: `https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js`,
34
- id: 'katex-js',
105
+ href: `https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css`,
106
+ id: `katex-css`,
35
107
  });
108
+ const displayMode = !!this.getAttribute('display-mode');
109
+ // detailsタグの中ではinnerTextがnullになることがあるため
110
+ const content = this.textContent || this.innerText;
111
+ katex === null || katex === void 0 ? void 0 : katex.render(content, this._container, {
112
+ macros: { '\\RR': '\\mathbb{R}' },
113
+ throwOnError: false,
114
+ displayMode,
115
+ });
116
+ this.innerHTML = this._container.innerHTML;
117
+ }
118
+ finally {
119
+ this._isRendering = false;
36
120
  }
37
- // CSSを読み込む(まだ読み込まれていない場合のみ)
38
- (0, load_stylesheet_1.loadStylesheet)({
39
- // 本来はバージョンを指定した方がいいが、他の処理との兼ね合いでバージョンを固定するのは難しいので指定していません
40
- href: `https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css`,
41
- id: `katex-css`,
42
- });
43
- const displayMode = !!this.getAttribute('display-mode');
44
- // detailsタグの中ではinnerTextがnullになることがあるため
45
- const content = this.textContent || this.innerText;
46
- katex === null || katex === void 0 ? void 0 : katex.render(content, this._container, {
47
- macros: { '\\RR': '\\mathbb{R}' },
48
- throwOnError: false,
49
- displayMode,
50
- });
51
- this.innerHTML = this._container.innerHTML;
52
121
  });
53
122
  }
54
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zenn-embed-elements",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "license": "MIT",
5
5
  "description": "Web components for embedded contents.",
6
6
  "repository": {
@@ -25,18 +25,20 @@
25
25
  "fix": "run-s fix:*",
26
26
  "fix:eslint": "eslint . --fix",
27
27
  "fix:prettier": "prettier -w .",
28
- "test": "echo 'no test yet'"
28
+ "test": "vitest run"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@eslint/js": "^9.38.0",
32
32
  "@types/node": "^24.9.1",
33
33
  "eslint": "^9.38.0",
34
34
  "eslint-config-prettier": "^10.1.8",
35
+ "happy-dom": "^20.3.9",
35
36
  "rimraf": "^6.0.1",
36
37
  "typescript": "^5.9.3",
37
- "typescript-eslint": "^8.46.2"
38
+ "typescript-eslint": "^8.46.2",
39
+ "vitest": "^4.0.18"
38
40
  },
39
- "gitHead": "35cdba273c40855cd4e89359b6a316bf92644810",
41
+ "gitHead": "de867b158201c3a387544cb88b8b627ea356aa43",
40
42
  "publishConfig": {
41
43
  "access": "public"
42
44
  }