@seorii/tiptap 0.4.1 → 0.4.3

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.
@@ -0,0 +1,51 @@
1
+ declare const _default: {
2
+ target: string[];
3
+ lang: string;
4
+ country: string;
5
+ text: string;
6
+ block: string;
7
+ loading: string;
8
+ delete: string;
9
+ close: string;
10
+ cancel: string;
11
+ insert: string;
12
+ noResult: string;
13
+ default: string;
14
+ auto: string;
15
+ title: string;
16
+ paragraph: string;
17
+ link: string;
18
+ alignLeft: string;
19
+ alignCenter: string;
20
+ alignRight: string;
21
+ alignJustify: string;
22
+ unorderedList: string;
23
+ numberList: string;
24
+ codeBlock: string;
25
+ mathBlock: string;
26
+ table: string;
27
+ twoColumns: string;
28
+ threeColumns: string;
29
+ image: string;
30
+ iframe: string;
31
+ youtube: string;
32
+ blockquote: string;
33
+ title1Info: string;
34
+ title2Info: string;
35
+ title3Info: string;
36
+ unorderedListInfo: string;
37
+ numberListInfo: string;
38
+ codeBlockInfo: string;
39
+ mathBlockInfo: string;
40
+ tableInfo: string;
41
+ twoColumnsInfo: string;
42
+ threeColumnsInfo: string;
43
+ imageInfo: string;
44
+ iframeInfo: string;
45
+ youtubeInfo: string;
46
+ blockquoteInfo: string;
47
+ newLineInfo: string;
48
+ placeholder: string;
49
+ insertCode: string;
50
+ };
51
+ export default _default;
@@ -0,0 +1,50 @@
1
+ export default {
2
+ target: ['es', 'es-ES', 'es-419', 'es-MX'],
3
+ lang: 'es',
4
+ country: 'ES',
5
+ text: 'Texto',
6
+ block: 'Bloque',
7
+ loading: 'Cargando...',
8
+ delete: 'Eliminar',
9
+ close: 'Cerrar',
10
+ cancel: 'Cancelar',
11
+ insert: 'Insertar',
12
+ noResult: 'Sin resultados',
13
+ default: 'predeterminado',
14
+ auto: 'auto',
15
+ title: 'Título',
16
+ paragraph: 'Párrafo',
17
+ link: 'Enlace',
18
+ alignLeft: 'Alinear a la izquierda',
19
+ alignCenter: 'Centrar',
20
+ alignRight: 'Alinear a la derecha',
21
+ alignJustify: 'Justificar',
22
+ unorderedList: 'Lista con viñetas',
23
+ numberList: 'Lista numerada',
24
+ codeBlock: 'Bloque de código',
25
+ mathBlock: 'Bloque matemático',
26
+ table: 'Tabla',
27
+ twoColumns: '2 columnas',
28
+ threeColumns: '3 columnas',
29
+ image: 'Imagen',
30
+ iframe: 'iframe',
31
+ youtube: 'YouTube',
32
+ blockquote: 'Cita',
33
+ title1Info: 'Título grande',
34
+ title2Info: 'Título más pequeño',
35
+ title3Info: 'Título mediano',
36
+ unorderedListInfo: 'Lista sin orden',
37
+ numberListInfo: '1, 2, 3, 4',
38
+ codeBlockInfo: 'Bloque de código con resaltado de sintaxis',
39
+ mathBlockInfo: 'Bloque matemático',
40
+ tableInfo: 'Tabla',
41
+ twoColumnsInfo: 'Diseño de dos columnas',
42
+ threeColumnsInfo: 'Diseño de tres columnas',
43
+ imageInfo: 'Imagen',
44
+ iframeInfo: 'Insertar otro sitio web',
45
+ youtubeInfo: 'Insertar video de YouTube',
46
+ blockquoteInfo: 'Cita',
47
+ newLineInfo: 'Pulsa / para abrir comandos. O',
48
+ placeholder: 'Escribe contenido aquí...',
49
+ insertCode: 'Insertar código'
50
+ };
@@ -96,6 +96,202 @@ declare const locales: readonly [{
96
96
  newLineInfo: string;
97
97
  placeholder: string;
98
98
  insertCode: string;
99
+ }, {
100
+ target: string[];
101
+ lang: string;
102
+ country: string;
103
+ text: string;
104
+ block: string;
105
+ loading: string;
106
+ delete: string;
107
+ close: string;
108
+ cancel: string;
109
+ insert: string;
110
+ noResult: string;
111
+ default: string;
112
+ auto: string;
113
+ title: string;
114
+ paragraph: string;
115
+ link: string;
116
+ alignLeft: string;
117
+ alignCenter: string;
118
+ alignRight: string;
119
+ alignJustify: string;
120
+ unorderedList: string;
121
+ numberList: string;
122
+ codeBlock: string;
123
+ mathBlock: string;
124
+ table: string;
125
+ twoColumns: string;
126
+ threeColumns: string;
127
+ image: string;
128
+ iframe: string;
129
+ youtube: string;
130
+ blockquote: string;
131
+ title1Info: string;
132
+ title2Info: string;
133
+ title3Info: string;
134
+ unorderedListInfo: string;
135
+ numberListInfo: string;
136
+ codeBlockInfo: string;
137
+ mathBlockInfo: string;
138
+ tableInfo: string;
139
+ twoColumnsInfo: string;
140
+ threeColumnsInfo: string;
141
+ imageInfo: string;
142
+ iframeInfo: string;
143
+ youtubeInfo: string;
144
+ blockquoteInfo: string;
145
+ newLineInfo: string;
146
+ placeholder: string;
147
+ insertCode: string;
148
+ }, {
149
+ target: string[];
150
+ lang: string;
151
+ country: string;
152
+ text: string;
153
+ block: string;
154
+ loading: string;
155
+ delete: string;
156
+ close: string;
157
+ cancel: string;
158
+ insert: string;
159
+ noResult: string;
160
+ default: string;
161
+ auto: string;
162
+ title: string;
163
+ paragraph: string;
164
+ link: string;
165
+ alignLeft: string;
166
+ alignCenter: string;
167
+ alignRight: string;
168
+ alignJustify: string;
169
+ unorderedList: string;
170
+ numberList: string;
171
+ codeBlock: string;
172
+ mathBlock: string;
173
+ table: string;
174
+ twoColumns: string;
175
+ threeColumns: string;
176
+ image: string;
177
+ iframe: string;
178
+ youtube: string;
179
+ blockquote: string;
180
+ title1Info: string;
181
+ title2Info: string;
182
+ title3Info: string;
183
+ unorderedListInfo: string;
184
+ numberListInfo: string;
185
+ codeBlockInfo: string;
186
+ mathBlockInfo: string;
187
+ tableInfo: string;
188
+ twoColumnsInfo: string;
189
+ threeColumnsInfo: string;
190
+ imageInfo: string;
191
+ iframeInfo: string;
192
+ youtubeInfo: string;
193
+ blockquoteInfo: string;
194
+ newLineInfo: string;
195
+ placeholder: string;
196
+ insertCode: string;
197
+ }, {
198
+ target: string[];
199
+ lang: string;
200
+ country: string;
201
+ text: string;
202
+ block: string;
203
+ loading: string;
204
+ delete: string;
205
+ close: string;
206
+ cancel: string;
207
+ insert: string;
208
+ noResult: string;
209
+ default: string;
210
+ auto: string;
211
+ title: string;
212
+ paragraph: string;
213
+ link: string;
214
+ alignLeft: string;
215
+ alignCenter: string;
216
+ alignRight: string;
217
+ alignJustify: string;
218
+ unorderedList: string;
219
+ numberList: string;
220
+ codeBlock: string;
221
+ mathBlock: string;
222
+ table: string;
223
+ twoColumns: string;
224
+ threeColumns: string;
225
+ image: string;
226
+ iframe: string;
227
+ youtube: string;
228
+ blockquote: string;
229
+ title1Info: string;
230
+ title2Info: string;
231
+ title3Info: string;
232
+ unorderedListInfo: string;
233
+ numberListInfo: string;
234
+ codeBlockInfo: string;
235
+ mathBlockInfo: string;
236
+ tableInfo: string;
237
+ twoColumnsInfo: string;
238
+ threeColumnsInfo: string;
239
+ imageInfo: string;
240
+ iframeInfo: string;
241
+ youtubeInfo: string;
242
+ blockquoteInfo: string;
243
+ newLineInfo: string;
244
+ placeholder: string;
245
+ insertCode: string;
246
+ }, {
247
+ target: string[];
248
+ lang: string;
249
+ country: string;
250
+ text: string;
251
+ block: string;
252
+ loading: string;
253
+ delete: string;
254
+ close: string;
255
+ cancel: string;
256
+ insert: string;
257
+ noResult: string;
258
+ default: string;
259
+ auto: string;
260
+ title: string;
261
+ paragraph: string;
262
+ link: string;
263
+ alignLeft: string;
264
+ alignCenter: string;
265
+ alignRight: string;
266
+ alignJustify: string;
267
+ unorderedList: string;
268
+ numberList: string;
269
+ codeBlock: string;
270
+ mathBlock: string;
271
+ table: string;
272
+ twoColumns: string;
273
+ threeColumns: string;
274
+ image: string;
275
+ iframe: string;
276
+ youtube: string;
277
+ blockquote: string;
278
+ title1Info: string;
279
+ title2Info: string;
280
+ title3Info: string;
281
+ unorderedListInfo: string;
282
+ numberListInfo: string;
283
+ codeBlockInfo: string;
284
+ mathBlockInfo: string;
285
+ tableInfo: string;
286
+ twoColumnsInfo: string;
287
+ threeColumnsInfo: string;
288
+ imageInfo: string;
289
+ iframeInfo: string;
290
+ youtubeInfo: string;
291
+ blockquoteInfo: string;
292
+ newLineInfo: string;
293
+ placeholder: string;
294
+ insertCode: string;
99
295
  }];
100
296
  type Locale = (typeof locales)[number];
101
297
  export type LocaleInput = string | Locale | null | undefined;
@@ -1,8 +1,25 @@
1
1
  import enUs from './en-us/index';
2
2
  import koKr from './ko-kr/index';
3
- const locales = [enUs, koKr];
3
+ import es from './es/index';
4
+ import ja from './ja/index';
5
+ import zhHans from './zh-hans/index';
6
+ import zhHant from './zh-hant/index';
7
+ const locales = [enUs, koKr, es, ja, zhHans, zhHant];
4
8
  export const I18N_CONTEXT = Symbol('tiptap-i18n');
5
- const normalizeLocaleCode = (value) => (value ?? '').trim().replace(/_/g, '-').toLowerCase();
9
+ const normalizeLocaleCode = (value) => {
10
+ const normalized = (value ?? '').trim().replace(/_/g, '-').toLowerCase();
11
+ if (!normalized)
12
+ return '';
13
+ if (!normalized.startsWith('zh'))
14
+ return normalized;
15
+ if (normalized.includes('hant') ||
16
+ normalized.startsWith('zh-tw') ||
17
+ normalized.startsWith('zh-hk') ||
18
+ normalized.startsWith('zh-mo')) {
19
+ return 'zh-hant';
20
+ }
21
+ return 'zh-hans';
22
+ };
6
23
  const findLocale = (localeList, language) => {
7
24
  const normalized = normalizeLocaleCode(language);
8
25
  if (!normalized)
@@ -0,0 +1,51 @@
1
+ declare const _default: {
2
+ target: string[];
3
+ lang: string;
4
+ country: string;
5
+ text: string;
6
+ block: string;
7
+ loading: string;
8
+ delete: string;
9
+ close: string;
10
+ cancel: string;
11
+ insert: string;
12
+ noResult: string;
13
+ default: string;
14
+ auto: string;
15
+ title: string;
16
+ paragraph: string;
17
+ link: string;
18
+ alignLeft: string;
19
+ alignCenter: string;
20
+ alignRight: string;
21
+ alignJustify: string;
22
+ unorderedList: string;
23
+ numberList: string;
24
+ codeBlock: string;
25
+ mathBlock: string;
26
+ table: string;
27
+ twoColumns: string;
28
+ threeColumns: string;
29
+ image: string;
30
+ iframe: string;
31
+ youtube: string;
32
+ blockquote: string;
33
+ title1Info: string;
34
+ title2Info: string;
35
+ title3Info: string;
36
+ unorderedListInfo: string;
37
+ numberListInfo: string;
38
+ codeBlockInfo: string;
39
+ mathBlockInfo: string;
40
+ tableInfo: string;
41
+ twoColumnsInfo: string;
42
+ threeColumnsInfo: string;
43
+ imageInfo: string;
44
+ iframeInfo: string;
45
+ youtubeInfo: string;
46
+ blockquoteInfo: string;
47
+ newLineInfo: string;
48
+ placeholder: string;
49
+ insertCode: string;
50
+ };
51
+ export default _default;
@@ -0,0 +1,50 @@
1
+ export default {
2
+ target: ['ja', 'ja-JP'],
3
+ lang: 'ja',
4
+ country: 'JP',
5
+ text: 'テキスト',
6
+ block: 'ブロック',
7
+ loading: '読み込み中...',
8
+ delete: '削除',
9
+ close: '閉じる',
10
+ cancel: 'キャンセル',
11
+ insert: '挿入',
12
+ noResult: '結果なし',
13
+ default: 'デフォルト',
14
+ auto: '自動',
15
+ title: '見出し',
16
+ paragraph: '本文',
17
+ link: 'リンク',
18
+ alignLeft: '左揃え',
19
+ alignCenter: '中央揃え',
20
+ alignRight: '右揃え',
21
+ alignJustify: '両端揃え',
22
+ unorderedList: '箇条書き',
23
+ numberList: '番号付きリスト',
24
+ codeBlock: 'コードブロック',
25
+ mathBlock: '数式ブロック',
26
+ table: '表',
27
+ twoColumns: '2カラム',
28
+ threeColumns: '3カラム',
29
+ image: '画像',
30
+ iframe: 'iframe',
31
+ youtube: 'YouTube',
32
+ blockquote: '引用',
33
+ title1Info: '大きな見出し',
34
+ title2Info: 'やや小さい見出し',
35
+ title3Info: '中くらいの見出し',
36
+ unorderedListInfo: '順序なしリスト',
37
+ numberListInfo: '1, 2, 3, 4',
38
+ codeBlockInfo: 'シンタックスハイライト付きコードブロック',
39
+ mathBlockInfo: '数式ブロック',
40
+ tableInfo: '表',
41
+ twoColumnsInfo: '2カラムレイアウト',
42
+ threeColumnsInfo: '3カラムレイアウト',
43
+ imageInfo: '画像',
44
+ iframeInfo: '別のウェブサイトを埋め込む',
45
+ youtubeInfo: 'YouTube動画を埋め込む',
46
+ blockquoteInfo: '引用ブロック',
47
+ newLineInfo: '/ でコマンドを開く。または',
48
+ placeholder: 'ここに内容を入力...',
49
+ insertCode: 'コードを挿入'
50
+ };
@@ -0,0 +1,51 @@
1
+ declare const _default: {
2
+ target: string[];
3
+ lang: string;
4
+ country: string;
5
+ text: string;
6
+ block: string;
7
+ loading: string;
8
+ delete: string;
9
+ close: string;
10
+ cancel: string;
11
+ insert: string;
12
+ noResult: string;
13
+ default: string;
14
+ auto: string;
15
+ title: string;
16
+ paragraph: string;
17
+ link: string;
18
+ alignLeft: string;
19
+ alignCenter: string;
20
+ alignRight: string;
21
+ alignJustify: string;
22
+ unorderedList: string;
23
+ numberList: string;
24
+ codeBlock: string;
25
+ mathBlock: string;
26
+ table: string;
27
+ twoColumns: string;
28
+ threeColumns: string;
29
+ image: string;
30
+ iframe: string;
31
+ youtube: string;
32
+ blockquote: string;
33
+ title1Info: string;
34
+ title2Info: string;
35
+ title3Info: string;
36
+ unorderedListInfo: string;
37
+ numberListInfo: string;
38
+ codeBlockInfo: string;
39
+ mathBlockInfo: string;
40
+ tableInfo: string;
41
+ twoColumnsInfo: string;
42
+ threeColumnsInfo: string;
43
+ imageInfo: string;
44
+ iframeInfo: string;
45
+ youtubeInfo: string;
46
+ blockquoteInfo: string;
47
+ newLineInfo: string;
48
+ placeholder: string;
49
+ insertCode: string;
50
+ };
51
+ export default _default;
@@ -0,0 +1,50 @@
1
+ export default {
2
+ target: ['zh', 'zh-CN', 'zh-SG', 'zh-Hans'],
3
+ lang: 'zh',
4
+ country: 'CN',
5
+ text: '文本',
6
+ block: '区块',
7
+ loading: '加载中...',
8
+ delete: '删除',
9
+ close: '关闭',
10
+ cancel: '取消',
11
+ insert: '插入',
12
+ noResult: '无结果',
13
+ default: '默认',
14
+ auto: '自动',
15
+ title: '标题',
16
+ paragraph: '正文',
17
+ link: '链接',
18
+ alignLeft: '左对齐',
19
+ alignCenter: '居中对齐',
20
+ alignRight: '右对齐',
21
+ alignJustify: '两端对齐',
22
+ unorderedList: '无序列表',
23
+ numberList: '有序列表',
24
+ codeBlock: '代码块',
25
+ mathBlock: '公式块',
26
+ table: '表格',
27
+ twoColumns: '2 列',
28
+ threeColumns: '3 列',
29
+ image: '图片',
30
+ iframe: 'iframe',
31
+ youtube: 'YouTube',
32
+ blockquote: '引用',
33
+ title1Info: '大标题',
34
+ title2Info: '较小标题',
35
+ title3Info: '中等标题',
36
+ unorderedListInfo: '无序列表',
37
+ numberListInfo: '1, 2, 3, 4',
38
+ codeBlockInfo: '带语法高亮的代码块',
39
+ mathBlockInfo: '公式块',
40
+ tableInfo: '插入表格',
41
+ twoColumnsInfo: '两列布局',
42
+ threeColumnsInfo: '三列布局',
43
+ imageInfo: '图片',
44
+ iframeInfo: '嵌入其他网站',
45
+ youtubeInfo: '嵌入 YouTube 视频',
46
+ blockquoteInfo: '引用块',
47
+ newLineInfo: '按 / 输入命令,或',
48
+ placeholder: '在此输入内容...',
49
+ insertCode: '插入代码'
50
+ };
@@ -0,0 +1,51 @@
1
+ declare const _default: {
2
+ target: string[];
3
+ lang: string;
4
+ country: string;
5
+ text: string;
6
+ block: string;
7
+ loading: string;
8
+ delete: string;
9
+ close: string;
10
+ cancel: string;
11
+ insert: string;
12
+ noResult: string;
13
+ default: string;
14
+ auto: string;
15
+ title: string;
16
+ paragraph: string;
17
+ link: string;
18
+ alignLeft: string;
19
+ alignCenter: string;
20
+ alignRight: string;
21
+ alignJustify: string;
22
+ unorderedList: string;
23
+ numberList: string;
24
+ codeBlock: string;
25
+ mathBlock: string;
26
+ table: string;
27
+ twoColumns: string;
28
+ threeColumns: string;
29
+ image: string;
30
+ iframe: string;
31
+ youtube: string;
32
+ blockquote: string;
33
+ title1Info: string;
34
+ title2Info: string;
35
+ title3Info: string;
36
+ unorderedListInfo: string;
37
+ numberListInfo: string;
38
+ codeBlockInfo: string;
39
+ mathBlockInfo: string;
40
+ tableInfo: string;
41
+ twoColumnsInfo: string;
42
+ threeColumnsInfo: string;
43
+ imageInfo: string;
44
+ iframeInfo: string;
45
+ youtubeInfo: string;
46
+ blockquoteInfo: string;
47
+ newLineInfo: string;
48
+ placeholder: string;
49
+ insertCode: string;
50
+ };
51
+ export default _default;
@@ -0,0 +1,50 @@
1
+ export default {
2
+ target: ['zh-TW', 'zh-HK', 'zh-MO', 'zh-Hant'],
3
+ lang: 'zh',
4
+ country: 'TW',
5
+ text: '文字',
6
+ block: '區塊',
7
+ loading: '載入中...',
8
+ delete: '刪除',
9
+ close: '關閉',
10
+ cancel: '取消',
11
+ insert: '插入',
12
+ noResult: '無結果',
13
+ default: '預設',
14
+ auto: '自動',
15
+ title: '標題',
16
+ paragraph: '段落',
17
+ link: '連結',
18
+ alignLeft: '靠左對齊',
19
+ alignCenter: '置中對齊',
20
+ alignRight: '靠右對齊',
21
+ alignJustify: '左右對齊',
22
+ unorderedList: '項目清單',
23
+ numberList: '編號清單',
24
+ codeBlock: '程式碼區塊',
25
+ mathBlock: '數學區塊',
26
+ table: '表格',
27
+ twoColumns: '2 欄',
28
+ threeColumns: '3 欄',
29
+ image: '圖片',
30
+ iframe: 'iframe',
31
+ youtube: 'YouTube',
32
+ blockquote: '引用',
33
+ title1Info: '大標題',
34
+ title2Info: '較小標題',
35
+ title3Info: '中等標題',
36
+ unorderedListInfo: '無序清單',
37
+ numberListInfo: '1, 2, 3, 4',
38
+ codeBlockInfo: '含語法高亮的程式碼區塊',
39
+ mathBlockInfo: '數學區塊',
40
+ tableInfo: '插入表格',
41
+ twoColumnsInfo: '雙欄版面',
42
+ threeColumnsInfo: '三欄版面',
43
+ imageInfo: '圖片',
44
+ iframeInfo: '嵌入其他網站',
45
+ youtubeInfo: '嵌入 YouTube 影片',
46
+ blockquoteInfo: '引用區塊',
47
+ newLineInfo: '按 / 輸入指令,或',
48
+ placeholder: '在此輸入內容...',
49
+ insertCode: '插入程式碼'
50
+ };
@@ -19,6 +19,15 @@ const minHeight = {
19
19
  attr: 160
20
20
  };
21
21
  const maxHeight = 1600;
22
+ const aspectRatioOptions = [
23
+ { label: 'Auto', value: null },
24
+ { label: '16:9', value: '16:9' },
25
+ { label: '3:2', value: '3:2' },
26
+ { label: '21:9', value: '21:9' },
27
+ { label: '1:1', value: '1:1' },
28
+ { label: '4:3', value: '4:3' },
29
+ { label: '9:16', value: '9:16' }
30
+ ];
22
31
  function isResizableType(value) {
23
32
  return typeSet.has(value);
24
33
  }
@@ -37,6 +46,69 @@ function parseNumericSize(value) {
37
46
  const parsed = Number.parseFloat(normalized);
38
47
  return Number.isFinite(parsed) ? parsed : null;
39
48
  }
49
+ function parseAspectRatio(value) {
50
+ if (typeof value === 'number')
51
+ return Number.isFinite(value) && value > 0 ? value : null;
52
+ if (typeof value !== 'string')
53
+ return null;
54
+ const trimmed = value.trim();
55
+ if (!trimmed)
56
+ return null;
57
+ const ratioMatch = /^([0-9]+(?:\.[0-9]+)?)\s*:\s*([0-9]+(?:\.[0-9]+)?)$/.exec(trimmed);
58
+ if (ratioMatch) {
59
+ const widthPart = Number.parseFloat(ratioMatch[1]);
60
+ const heightPart = Number.parseFloat(ratioMatch[2]);
61
+ if (!Number.isFinite(widthPart) || !Number.isFinite(heightPart))
62
+ return null;
63
+ if (widthPart <= 0 || heightPart <= 0)
64
+ return null;
65
+ return widthPart / heightPart;
66
+ }
67
+ const parsed = Number.parseFloat(trimmed);
68
+ if (!Number.isFinite(parsed) || parsed <= 0)
69
+ return null;
70
+ return parsed;
71
+ }
72
+ function formatDecimal(value, precision = 6) {
73
+ return String(Number(value.toFixed(precision)));
74
+ }
75
+ function normalizeAspectRatioAttr(value) {
76
+ if (typeof value === 'number') {
77
+ if (!Number.isFinite(value) || value <= 0)
78
+ return null;
79
+ return formatDecimal(value);
80
+ }
81
+ if (typeof value !== 'string')
82
+ return null;
83
+ const trimmed = value.trim();
84
+ if (!trimmed)
85
+ return null;
86
+ const ratioMatch = /^([0-9]+(?:\.[0-9]+)?)\s*:\s*([0-9]+(?:\.[0-9]+)?)$/.exec(trimmed);
87
+ if (ratioMatch) {
88
+ const widthPart = Number.parseFloat(ratioMatch[1]);
89
+ const heightPart = Number.parseFloat(ratioMatch[2]);
90
+ if (!Number.isFinite(widthPart) || !Number.isFinite(heightPart))
91
+ return null;
92
+ if (widthPart <= 0 || heightPart <= 0)
93
+ return null;
94
+ return `${formatDecimal(widthPart, 4)}:${formatDecimal(heightPart, 4)}`;
95
+ }
96
+ const parsed = Number.parseFloat(trimmed);
97
+ if (!Number.isFinite(parsed) || parsed <= 0)
98
+ return null;
99
+ return formatDecimal(parsed);
100
+ }
101
+ function sameAspectRatio(left, right) {
102
+ if (!left && !right)
103
+ return true;
104
+ if (!left || !right)
105
+ return false;
106
+ const leftRatio = parseAspectRatio(left);
107
+ const rightRatio = parseAspectRatio(right);
108
+ if (leftRatio === null || rightRatio === null)
109
+ return false;
110
+ return Math.abs(leftRatio - rightRatio) <= 0.001;
111
+ }
40
112
  function normalizeStringAttr(value) {
41
113
  if (typeof value !== 'string')
42
114
  return null;
@@ -113,6 +185,15 @@ function resolveStartHeight(kind, node, element) {
113
185
  return fromAttr;
114
186
  return defaultHeight[kind];
115
187
  }
188
+ function resolveElementWidth(node, element) {
189
+ const rect = element.getBoundingClientRect();
190
+ if (rect.width > 0)
191
+ return rect.width;
192
+ const fromAttr = parseNumericSize(node.attrs.width);
193
+ if (fromAttr !== null)
194
+ return fromAttr;
195
+ return 0;
196
+ }
116
197
  function resolveTargetElement(view, pos, resizeMeta, node) {
117
198
  const nodeDom = view.nodeDOM(pos);
118
199
  if (!(nodeDom instanceof HTMLElement))
@@ -153,7 +234,7 @@ function resolveTargetElement(view, pos, resizeMeta, node) {
153
234
  nodeDom.querySelector('iframe') ||
154
235
  nodeDom);
155
236
  }
156
- function buildResizeAttrs(kind, node, height, imageRatio) {
237
+ function buildResizeAttrs(kind, node, height, imageRatio, aspectRatio = normalizeAspectRatioAttr(node.attrs.aspectRatio)) {
157
238
  const attrs = { ...node.attrs };
158
239
  const roundedHeight = String(Math.round(height));
159
240
  if (kind === 'image') {
@@ -161,12 +242,26 @@ function buildResizeAttrs(kind, node, height, imageRatio) {
161
242
  // Keep image responsive by storing width only; fixed height causes ratio distortion on narrow layouts.
162
243
  return { ...attrs, width: roundedWidth, height: null };
163
244
  }
164
- if (kind === 'iframe' || kind === 'embed') {
165
- return { ...attrs, width: attrs.width || '100%', height: roundedHeight };
245
+ if (kind === 'iframe') {
246
+ if (aspectRatio) {
247
+ // A fixed height prevents CSS aspect-ratio from taking effect.
248
+ return { ...attrs, width: attrs.width || '100%', height: null, aspectRatio };
249
+ }
250
+ return { ...attrs, width: attrs.width || '100%', height: roundedHeight, aspectRatio: null };
251
+ }
252
+ if (kind === 'embed') {
253
+ if (aspectRatio) {
254
+ // Keep PDF/embed responsive with CSS aspect-ratio when a preset is selected.
255
+ return { ...attrs, width: attrs.width || '100%', height: null, aspectRatio };
256
+ }
257
+ return { ...attrs, width: attrs.width || '100%', height: roundedHeight, aspectRatio: null };
166
258
  }
167
- return { ...attrs, height: roundedHeight };
259
+ return { ...attrs, height: roundedHeight, aspectRatio: null };
168
260
  }
169
- function createResizeHandleDecoration(nodePos, widgetPos, resizeMeta) {
261
+ function canUseAspectRatioPreset(kind) {
262
+ return kind === 'iframe' || kind === 'embed';
263
+ }
264
+ function createResizeHandleDecoration(nodePos, widgetPos, resizeMeta, node) {
170
265
  return Decoration.widget(widgetPos, () => {
171
266
  const anchor = document.createElement('div');
172
267
  anchor.className = 'tiptap-media-resize-anchor';
@@ -175,8 +270,30 @@ function createResizeHandleDecoration(nodePos, widgetPos, resizeMeta) {
175
270
  button.className = 'tiptap-media-resize-handle';
176
271
  button.dataset.resizePos = String(nodePos);
177
272
  button.dataset.resizeKind = resizeMeta.kind;
178
- button.setAttribute('aria-label', 'Resize media height');
273
+ button.setAttribute('aria-label', 'Resize media height (click for aspect ratio)');
179
274
  anchor.append(button);
275
+ if (canUseAspectRatioPreset(resizeMeta.kind)) {
276
+ const selectedAspectRatio = normalizeAspectRatioAttr(node.attrs.aspectRatio);
277
+ const toolbar = document.createElement('div');
278
+ toolbar.className = 'tiptap-media-aspect-ratio-toolbar';
279
+ toolbar.setAttribute('role', 'toolbar');
280
+ toolbar.setAttribute('aria-label', 'Aspect ratio presets');
281
+ toolbar.dataset.resizePos = String(nodePos);
282
+ for (const option of aspectRatioOptions) {
283
+ const optionButton = document.createElement('button');
284
+ optionButton.type = 'button';
285
+ optionButton.className = 'tiptap-media-aspect-ratio-option';
286
+ optionButton.dataset.resizePos = String(nodePos);
287
+ optionButton.dataset.aspectRatio = option.value ?? 'auto';
288
+ optionButton.textContent = option.label;
289
+ const isActive = option.value
290
+ ? sameAspectRatio(option.value, selectedAspectRatio)
291
+ : !selectedAspectRatio;
292
+ optionButton.setAttribute('aria-pressed', isActive ? 'true' : 'false');
293
+ toolbar.append(optionButton);
294
+ }
295
+ anchor.append(toolbar);
296
+ }
180
297
  return anchor;
181
298
  }, { side: 1, key: `media-resize-${nodePos}-${resizeMeta.typeName}-${resizeMeta.kind}` });
182
299
  }
@@ -239,8 +356,17 @@ export default Extension.create({
239
356
  },
240
357
  height: {
241
358
  default: '600',
242
- parseHTML: (element) => normalizeNumericAttr(element.getAttribute('height') || element.style.height) || '600',
359
+ parseHTML: (element) => {
360
+ const aspectRatio = normalizeAspectRatioAttr(element.getAttribute('data-resize-aspect-ratio'));
361
+ if (aspectRatio)
362
+ return null;
363
+ return (normalizeNumericAttr(element.getAttribute('height') || element.style.height) ||
364
+ '600');
365
+ },
243
366
  renderHTML: (attributes) => {
367
+ const aspectRatio = normalizeAspectRatioAttr(attributes.aspectRatio);
368
+ if (aspectRatio)
369
+ return {};
244
370
  const height = normalizeNumericAttr(attributes.height) || '600';
245
371
  return { height };
246
372
  }
@@ -254,9 +380,7 @@ export default Extension.create({
254
380
  default: false,
255
381
  parseHTML: (element) => hasResizeHandler(element.getAttribute('data-resize-handler') ||
256
382
  element.getAttribute('resize-handler')),
257
- renderHTML: (attributes) => hasResizeHandler(attributes.resizeHandler)
258
- ? { 'data-resize-handler': 'true' }
259
- : {}
383
+ renderHTML: (attributes) => hasResizeHandler(attributes.resizeHandler) ? { 'data-resize-handler': 'true' } : {}
260
384
  },
261
385
  resizeTarget: {
262
386
  default: null,
@@ -281,6 +405,14 @@ export default Extension.create({
281
405
  const maxHeight = normalizeNumericAttr(attributes.maxHeight);
282
406
  return maxHeight ? { 'data-resize-max-height': maxHeight } : {};
283
407
  }
408
+ },
409
+ aspectRatio: {
410
+ default: null,
411
+ parseHTML: (element) => normalizeAspectRatioAttr(element.getAttribute('data-resize-aspect-ratio')),
412
+ renderHTML: (attributes) => {
413
+ const aspectRatio = normalizeAspectRatioAttr(attributes.aspectRatio);
414
+ return aspectRatio ? { 'data-resize-aspect-ratio': aspectRatio } : {};
415
+ }
284
416
  }
285
417
  }
286
418
  }
@@ -288,6 +420,15 @@ export default Extension.create({
288
420
  },
289
421
  addProseMirrorPlugins() {
290
422
  let removeDragListeners = null;
423
+ const closeOpenToolbars = (view, except = null) => {
424
+ view.dom
425
+ .querySelectorAll('.tiptap-media-resize-anchor.is-toolbar-open')
426
+ .forEach((anchor) => {
427
+ if (except && anchor === except)
428
+ return;
429
+ anchor.classList.remove('is-toolbar-open');
430
+ });
431
+ };
291
432
  return [
292
433
  new Plugin({
293
434
  key: pluginKey,
@@ -328,7 +469,7 @@ export default Extension.create({
328
469
  const resizeMeta = resolveResizeMeta(node);
329
470
  if (!resizeMeta || resizeMeta.kind === 'image' || node.isInline)
330
471
  return;
331
- decorations.push(createResizeHandleDecoration(pos, pos + node.nodeSize, resizeMeta));
472
+ decorations.push(createResizeHandleDecoration(pos, pos + node.nodeSize, resizeMeta, node));
332
473
  handled.add(pos);
333
474
  });
334
475
  }
@@ -336,7 +477,7 @@ export default Extension.create({
336
477
  const pos = state.selection.from;
337
478
  const resizeMeta = resolveResizeMeta(state.selection.node);
338
479
  if (resizeMeta && !handled.has(pos)) {
339
- decorations.push(createResizeHandleDecoration(pos, pos + state.selection.node.nodeSize, resizeMeta));
480
+ decorations.push(createResizeHandleDecoration(pos, pos + state.selection.node.nodeSize, resizeMeta, state.selection.node));
340
481
  }
341
482
  }
342
483
  if (!decorations.length)
@@ -349,9 +490,50 @@ export default Extension.create({
349
490
  return false;
350
491
  if (!(event.target instanceof HTMLElement))
351
492
  return false;
493
+ const ratioOption = event.target.closest('.tiptap-media-aspect-ratio-option');
494
+ if (ratioOption) {
495
+ event.preventDefault();
496
+ event.stopPropagation();
497
+ const pos = Number.parseInt(ratioOption.dataset.resizePos || '', 10);
498
+ if (!Number.isFinite(pos))
499
+ return true;
500
+ const node = view.state.doc.nodeAt(pos);
501
+ if (!node)
502
+ return true;
503
+ const resizeMeta = resolveResizeMeta(node);
504
+ if (!resizeMeta || !canUseAspectRatioPreset(resizeMeta.kind))
505
+ return true;
506
+ const target = resolveTargetElement(view, pos, resizeMeta, node);
507
+ if (!target)
508
+ return true;
509
+ const selectedRatio = ratioOption.dataset.aspectRatio || 'auto';
510
+ const normalizedAspectRatio = selectedRatio === 'auto' ? null : normalizeAspectRatioAttr(selectedRatio);
511
+ if (selectedRatio !== 'auto' && !normalizedAspectRatio)
512
+ return true;
513
+ let nextHeight = resolveStartHeight(resizeMeta.kind, node, target);
514
+ const ratioValue = parseAspectRatio(normalizedAspectRatio);
515
+ if (ratioValue !== null) {
516
+ const width = resolveElementWidth(node, target);
517
+ if (width > 0) {
518
+ nextHeight = clamp(width / ratioValue, resizeMeta.minHeight, resizeMeta.maxHeight);
519
+ }
520
+ }
521
+ const nextAttrs = buildResizeAttrs(resizeMeta.kind, node, nextHeight, 1, normalizedAspectRatio);
522
+ const nextWidth = 'width' in nextAttrs ? nextAttrs.width : node.attrs.width;
523
+ const nextAspectRatio = 'aspectRatio' in nextAttrs ? nextAttrs.aspectRatio : null;
524
+ if (nextAttrs.height !== node.attrs.height ||
525
+ nextWidth !== node.attrs.width ||
526
+ nextAspectRatio !== node.attrs.aspectRatio) {
527
+ view.dispatch(view.state.tr.setNodeMarkup(pos, node.type, nextAttrs));
528
+ }
529
+ closeOpenToolbars(view);
530
+ return true;
531
+ }
352
532
  const handle = event.target.closest('.tiptap-media-resize-handle');
353
- if (!handle)
533
+ if (!handle) {
534
+ closeOpenToolbars(view);
354
535
  return false;
536
+ }
355
537
  const pos = Number.parseInt(handle.dataset.resizePos || '', 10);
356
538
  if (!Number.isFinite(pos))
357
539
  return false;
@@ -369,6 +551,8 @@ export default Extension.create({
369
551
  return false;
370
552
  event.preventDefault();
371
553
  event.stopPropagation();
554
+ const anchor = handle.closest('.tiptap-media-resize-anchor');
555
+ const startX = event.clientX;
372
556
  const startY = event.clientY;
373
557
  const startHeight = resolveStartHeight(resizeMeta.kind, node, target);
374
558
  const imageRatio = resizeMeta.kind === 'image' ? resolveImageRatio(node, target) : 1;
@@ -377,21 +561,10 @@ export default Extension.create({
377
561
  let restoreTarget = null;
378
562
  let frame = 0;
379
563
  let pendingHeight = startHeight;
380
- if (shouldShowProxy && target.parentElement) {
381
- const targetElement = target;
382
- const originalDisplay = targetElement.style.display;
383
- resizeProxy = document.createElement('div');
384
- resizeProxy.className = 'tiptap-media-resize-proxy';
385
- resizeProxy.style.height = `${Math.round(startHeight)}px`;
386
- target.parentElement.insertBefore(resizeProxy, targetElement);
387
- targetElement.style.display = 'none';
388
- restoreTarget = () => {
389
- targetElement.style.display = originalDisplay;
390
- resizeProxy?.remove();
391
- resizeProxy = null;
392
- restoreTarget = null;
393
- };
394
- }
564
+ let isDragging = false;
565
+ let appliedDragCursor = false;
566
+ const previousCursor = document.body.style.cursor;
567
+ const previousSelect = document.body.style.userSelect;
395
568
  const dispatchHeight = (height) => {
396
569
  const current = view.state.doc.nodeAt(pos);
397
570
  if (!current || current.type.name !== resizeMeta.typeName)
@@ -399,19 +572,51 @@ export default Extension.create({
399
572
  const currentMeta = resolveResizeMeta(current);
400
573
  if (!currentMeta)
401
574
  return;
402
- const nextAttrs = buildResizeAttrs(currentMeta.kind, current, height, imageRatio);
403
- const nextWidth = 'width' in nextAttrs ? nextAttrs.width : current.attrs.width;
404
- if (nextAttrs.height === current.attrs.height && nextWidth === current.attrs.width)
575
+ // Manual drag should unlock fixed aspect ratio.
576
+ const aspectRatioForDrag = null;
577
+ const nextAttrs = buildResizeAttrs(currentMeta.kind, current, height, imageRatio, aspectRatioForDrag);
578
+ const nextWidth = 'width' in nextAttrs
579
+ ? nextAttrs.width
580
+ : current.attrs.width;
581
+ const nextAspectRatio = 'aspectRatio' in nextAttrs ? nextAttrs.aspectRatio : null;
582
+ if (nextAttrs.height === current.attrs.height &&
583
+ nextWidth === current.attrs.width &&
584
+ nextAspectRatio === current.attrs.aspectRatio)
405
585
  return;
406
- const tr = view.state.tr.setNodeMarkup(pos, current.type, nextAttrs);
407
- view.dispatch(tr);
586
+ view.dispatch(view.state.tr.setNodeMarkup(pos, current.type, nextAttrs));
587
+ };
588
+ const beginDrag = () => {
589
+ if (isDragging)
590
+ return;
591
+ isDragging = true;
592
+ closeOpenToolbars(view);
593
+ if (shouldShowProxy && target.parentElement) {
594
+ const targetElement = target;
595
+ const originalDisplay = targetElement.style.display;
596
+ resizeProxy = document.createElement('div');
597
+ resizeProxy.className = 'tiptap-media-resize-proxy';
598
+ resizeProxy.style.height = `${Math.round(startHeight)}px`;
599
+ target.parentElement.insertBefore(resizeProxy, targetElement);
600
+ targetElement.style.display = 'none';
601
+ restoreTarget = () => {
602
+ targetElement.style.display = originalDisplay;
603
+ resizeProxy?.remove();
604
+ resizeProxy = null;
605
+ restoreTarget = null;
606
+ };
607
+ }
608
+ document.body.style.cursor = 'ns-resize';
609
+ document.body.style.userSelect = 'none';
610
+ appliedDragCursor = true;
408
611
  };
409
- const previousCursor = document.body.style.cursor;
410
- const previousSelect = document.body.style.userSelect;
411
- document.body.style.cursor = 'ns-resize';
412
- document.body.style.userSelect = 'none';
413
612
  const onMove = (moveEvent) => {
414
- const nextHeight = clamp(startHeight + moveEvent.clientY - startY, resizeMeta.minHeight, resizeMeta.maxHeight);
613
+ const deltaX = moveEvent.clientX - startX;
614
+ const deltaY = moveEvent.clientY - startY;
615
+ if (!isDragging && Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4)
616
+ return;
617
+ if (!isDragging)
618
+ beginDrag();
619
+ const nextHeight = clamp(startHeight + deltaY, resizeMeta.minHeight, resizeMeta.maxHeight);
415
620
  pendingHeight = nextHeight;
416
621
  if (resizeProxy)
417
622
  resizeProxy.style.height = `${Math.round(nextHeight)}px`;
@@ -426,12 +631,21 @@ export default Extension.create({
426
631
  cancelAnimationFrame(frame);
427
632
  window.removeEventListener('mousemove', onMove);
428
633
  window.removeEventListener('mouseup', onUp);
429
- document.body.style.cursor = previousCursor;
430
- document.body.style.userSelect = previousSelect;
634
+ if (appliedDragCursor) {
635
+ document.body.style.cursor = previousCursor;
636
+ document.body.style.userSelect = previousSelect;
637
+ }
431
638
  restoreTarget?.();
432
639
  removeDragListeners = null;
433
640
  };
434
641
  const onUp = () => {
642
+ if (!isDragging) {
643
+ const shouldOpen = Boolean(anchor) && !(anchor?.classList.contains('is-toolbar-open') ?? false);
644
+ closeOpenToolbars(view, shouldOpen && anchor ? anchor : null);
645
+ anchor?.classList.toggle('is-toolbar-open', shouldOpen);
646
+ cleanup();
647
+ return;
648
+ }
435
649
  if (shouldShowProxy)
436
650
  dispatchHeight(pendingHeight);
437
651
  cleanup();
@@ -81,6 +81,7 @@
81
81
  'data-resize-target',
82
82
  'data-resize-min-height',
83
83
  'data-resize-max-height',
84
+ 'data-resize-aspect-ratio',
84
85
  'data-bubble-menu',
85
86
  'data-hide-bubble-menu'
86
87
  ];
@@ -343,10 +344,13 @@
343
344
  .editable :global(.tiptap-media-resize-anchor) {
344
345
  width: 100%;
345
346
  display: flex;
346
- justify-content: center;
347
+ flex-direction: column;
348
+ align-items: center;
347
349
  margin: 6px 0 2px;
348
350
  line-height: 0;
349
351
  pointer-events: none;
352
+ position: relative;
353
+ overflow: visible;
350
354
  }
351
355
 
352
356
  .editable :global(.tiptap-media-resize-handle) {
@@ -379,6 +383,56 @@
379
383
  transform: translateY(1px);
380
384
  }
381
385
 
386
+ .editable :global(.tiptap-media-aspect-ratio-toolbar) {
387
+ display: none;
388
+ align-items: center;
389
+ gap: 4px;
390
+ padding: 4px;
391
+ border: 1px solid var(--primary-light3, rgba(120, 120, 120, 0.4));
392
+ border-radius: 999px;
393
+ background: var(--surface, #fff);
394
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
395
+ pointer-events: auto;
396
+ line-height: 1;
397
+ position: absolute;
398
+ top: calc(100% + 6px);
399
+ left: 50%;
400
+ transform: translateX(-50%);
401
+ z-index: 4;
402
+ white-space: nowrap;
403
+ }
404
+
405
+ .editable
406
+ :global(.tiptap-media-resize-anchor.is-toolbar-open .tiptap-media-aspect-ratio-toolbar) {
407
+ display: flex;
408
+ }
409
+
410
+ .editable :global(.tiptap-media-aspect-ratio-option) {
411
+ appearance: none;
412
+ -webkit-appearance: none;
413
+ margin: 0;
414
+ padding: 2px 8px;
415
+ border: 0;
416
+ border-radius: 999px;
417
+ background: transparent;
418
+ color: var(--on-surface, #000);
419
+ font-size: 11px;
420
+ font-weight: 600;
421
+ cursor: pointer;
422
+ transition: background-color 0.15s ease;
423
+ }
424
+
425
+ .editable :global(.tiptap-media-aspect-ratio-option:hover),
426
+ .editable :global(.tiptap-media-aspect-ratio-option:focus-visible) {
427
+ background: var(--primary-light1, rgba(120, 120, 120, 0.14));
428
+ outline: none;
429
+ }
430
+
431
+ .editable :global(.tiptap-media-aspect-ratio-option[aria-pressed='true']) {
432
+ background: var(--primary-light4, rgba(120, 120, 120, 0.3));
433
+ color: var(--on-primary, #000);
434
+ }
435
+
382
436
  .editable :global(.tiptap-media-resize-proxy) {
383
437
  width: 100%;
384
438
  border-radius: 12px;
@@ -471,21 +525,58 @@
471
525
  border-radius: 12px;
472
526
  }
473
527
 
474
- & :global(.iframe-wrapper) {
475
- position: relative;
476
- padding-bottom: 12px;
528
+ & :global(.iframe-wrapper),
529
+ & :global(.embed-wrapper) {
477
530
  overflow: hidden;
478
531
  width: 100%;
479
- height: 600px;
480
532
  border-radius: 12px;
481
533
  }
482
534
 
483
- & :global(iframe) {
484
- position: absolute;
485
- top: 0;
486
- left: 0;
535
+ & :global(iframe),
536
+ & :global(embed) {
537
+ display: block;
487
538
  width: 100%;
488
- height: 100%;
539
+ max-width: 100%;
540
+ }
541
+
542
+ & :global([data-resize-aspect-ratio='16:9']) {
543
+ --tiptap-media-aspect-ratio: 16 / 9;
544
+ }
545
+
546
+ & :global([data-resize-aspect-ratio='3:2']) {
547
+ --tiptap-media-aspect-ratio: 3 / 2;
548
+ }
549
+
550
+ & :global([data-resize-aspect-ratio='21:9']) {
551
+ --tiptap-media-aspect-ratio: 21 / 9;
552
+ }
553
+
554
+ & :global([data-resize-aspect-ratio='1:1']) {
555
+ --tiptap-media-aspect-ratio: 1 / 1;
556
+ }
557
+
558
+ & :global([data-resize-aspect-ratio='4:3']) {
559
+ --tiptap-media-aspect-ratio: 4 / 3;
560
+ }
561
+
562
+ & :global([data-resize-aspect-ratio='9:16']) {
563
+ --tiptap-media-aspect-ratio: 9 / 16;
564
+ }
565
+
566
+ & :global(iframe[data-resize-aspect-ratio='16:9']),
567
+ & :global(iframe[data-resize-aspect-ratio='3:2']),
568
+ & :global(iframe[data-resize-aspect-ratio='21:9']),
569
+ & :global(iframe[data-resize-aspect-ratio='1:1']),
570
+ & :global(iframe[data-resize-aspect-ratio='4:3']),
571
+ & :global(iframe[data-resize-aspect-ratio='9:16']),
572
+ & :global(embed[data-resize-aspect-ratio='16:9']),
573
+ & :global(embed[data-resize-aspect-ratio='3:2']),
574
+ & :global(embed[data-resize-aspect-ratio='21:9']),
575
+ & :global(embed[data-resize-aspect-ratio='1:1']),
576
+ & :global(embed[data-resize-aspect-ratio='4:3']),
577
+ & :global(embed[data-resize-aspect-ratio='9:16']) {
578
+ aspect-ratio: var(--tiptap-media-aspect-ratio, auto);
579
+ height: auto;
489
580
  }
490
581
  }
491
582
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seorii/tiptap",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "svelte-kit sync && svelte-package",