@growgroup/visual-editor 0.7.1 → 0.8.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.
@@ -294,6 +294,80 @@ function isTextColorClass(cls: string): boolean {
294
294
  return false;
295
295
  }
296
296
 
297
+ // ------------------------------------------------------------
298
+ // 線(border-*)のクラスの見分け
299
+ // ------------------------------------------------------------
300
+ // border- で始まるクラスには、太さ(border-2 / border-l-2)・色(border-wf-ink)・
301
+ // スタイル(border-dashed)・表(border-collapse)が混ざっている。
302
+ // 以前は線色・線幅の変更で「border- で始まるもの」を全部消していたので、
303
+ // 左だけの線(border-l-2)や色・破線まで消えていた。tailwind-merge と同じ見分け方にする
304
+ // (太さ・スタイル・表の形でないものを色とみなす。テーマの色名を名簿で持たずに済む)
305
+
306
+ const BORDER_STYLE_CLASSES = new Set([
307
+ 'border-solid',
308
+ 'border-dashed',
309
+ 'border-dotted',
310
+ 'border-double',
311
+ 'border-hidden',
312
+ 'border-none',
313
+ ]);
314
+
315
+ const isBorderTableClass = (cls: string) => /^border-(collapse|separate|spacing(-|$))/.test(cls);
316
+
317
+ /**
318
+ * important の印を外す(`border-2!` / `!border-2`)。付いていても種類は同じなので、
319
+ * 判定の前に外して、消すときも同じ種類として消す(付いたまま残ると、新しい太さが !important に負ける)
320
+ */
321
+ const stripImportant = (cls: string) => cls.replace(/^!/, '').replace(/!$/, '');
322
+
323
+ /**
324
+ * 太さの値: 数字(border-2)、長さの任意値(border-[2px] / border-[length:var(--w)] /
325
+ * border-[min(2px,1vw)])、太さのキーワード(border-[thin] など。`border-[mediumblue]` は色なので厳密一致)、
326
+ * border-(length:--w)
327
+ */
328
+ const BORDER_WIDTH_VALUE = String.raw`(-\d+|-\[(?:thin|medium|thick)\]|-\[(?:\d|\.|calc\(|min\(|max\(|clamp\(|length:)[^\]]*\]|-\(length:[^)]*\))?`;
329
+
330
+ /** 辺の指定。`bs` / `be`(書字方向の始め・終わり)も Tailwind では辺の太さ */
331
+ const BORDER_SIDE_TOKENS = '(?:[trblxyse]|bs|be)';
332
+
333
+ /**
334
+ * 太さのクラス。side を渡すとその辺だけ(border-l / border-l-2 / border-l-[3px])、
335
+ * '' なら四辺まとめての指定だけ(border / border-2)。渡さなければ全部の辺と各辺
336
+ */
337
+ /** 辺を表す文字。`s` / `e` は書字方向、`bs` / `be` はブロック方向 */
338
+ export type SideLetter = 't' | 'r' | 'b' | 'l' | 'x' | 'y' | 's' | 'e' | 'bs' | 'be';
339
+
340
+ export function isBorderWidthClass(cls: string, side?: SideLetter | ''): boolean {
341
+ const sides = side === undefined ? `(-${BORDER_SIDE_TOKENS})?` : side ? `-${side}` : '';
342
+ return new RegExp(`^border${sides}${BORDER_WIDTH_VALUE}$`).test(stripImportant(cls));
343
+ }
344
+
345
+ /** スタイルのクラス(border-solid / border-dashed!) */
346
+ const isBorderStyleClass = (cls: string) => BORDER_STYLE_CLASSES.has(stripImportant(cls));
347
+
348
+ /** 太さ 0 の指定(border-0 / border-l-0 / border-[0px])。「線がある」の判定では数えない */
349
+ export function isZeroBorderWidthClass(cls: string): boolean {
350
+ return new RegExp(`^border(-${BORDER_SIDE_TOKENS})?-(0|\\[0(?:px|rem|em)?\\])$`).test(stripImportant(cls));
351
+ }
352
+
353
+ /** 色のクラス(border-wf-ink / border-l-wf-ink / border-[#fff] / border-[var(--x)] / border-wf-ink/50) */
354
+ function isBorderColorClass(cls: string): boolean {
355
+ const bare = stripImportant(cls);
356
+ return (
357
+ bare.startsWith('border-') &&
358
+ !BORDER_STYLE_CLASSES.has(bare) &&
359
+ !isBorderTableClass(bare) &&
360
+ !isBorderWidthClass(bare)
361
+ );
362
+ }
363
+
364
+ const BORDER_SIDE_WIDTH_PROPERTIES: Record<string, 't' | 'r' | 'b' | 'l'> = {
365
+ borderTopWidth: 't',
366
+ borderRightWidth: 'r',
367
+ borderBottomWidth: 'b',
368
+ borderLeftWidth: 'l',
369
+ };
370
+
297
371
  /**
298
372
  * 競合するクラスを要素から削除
299
373
  *
@@ -322,6 +396,26 @@ export function removeConflictingClasses(
322
396
  return;
323
397
  }
324
398
 
399
+ // 線の太さ・色・スタイルは、同じ種類のクラスだけを消す(太さを変えても色・破線は残す。色を変えても左だけの線は残す)。
400
+ // スタイルもここで消すのは、important 付き(border-dashed!)を名前の一致では拾えないため
401
+ if (
402
+ property === 'borderWidth' ||
403
+ property === 'borderColor' ||
404
+ property === 'borderStyle' ||
405
+ property in BORDER_SIDE_WIDTH_PROPERTIES
406
+ ) {
407
+ const side = BORDER_SIDE_WIDTH_PROPERTIES[property];
408
+ const conflicts =
409
+ property === 'borderColor'
410
+ ? isBorderColorClass
411
+ : property === 'borderStyle'
412
+ ? isBorderStyleClass
413
+ : (cls: string) => isBorderWidthClass(cls, side);
414
+ const currentClasses = getElementClassName(element).split(/\s+/).filter(Boolean);
415
+ setElementClassName(element, currentClasses.filter((cls) => !conflicts(cls)).join(' '));
416
+ return;
417
+ }
418
+
325
419
  if (!conflictPrefixes || conflictPrefixes.length === 0) return;
326
420
 
327
421
  const currentClasses = getElementClassName(element).split(/\s+/).filter(Boolean);
@@ -379,6 +473,17 @@ const LAYOUT_CRITICAL_PROPERTIES = new Set([
379
473
  'maxWidth',
380
474
  'minHeight',
381
475
  'maxHeight',
476
+ // 線のスタイル(なし / 実線 / 破線 / 点線)も同じ理由でインラインを残す。
477
+ // 紙面の CSS はファイルに書かれたクラスの分しか作られない(Vite の @source 走査)ので、
478
+ // 付けたばかりの border-none / border-solid にはルールが無く、紙面が変わらない。
479
+ // 右パネルは紙面の computed style を読み直すため、「なし」を選んでも「実線」に戻って見えていた
480
+ 'borderStyle',
481
+ // 線幅も同じ。border-0 / border-l-0 などは紙面の CSS に無いことが多い(構成ラフの殻で実測)
482
+ 'borderWidth',
483
+ 'borderTopWidth',
484
+ 'borderRightWidth',
485
+ 'borderBottomWidth',
486
+ 'borderLeftWidth',
382
487
  'display',
383
488
  'flexDirection',
384
489
  'flexWrap',
package/src/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * 利用側はこの値と node_modules の package.json を見比べて、食い違っていれば
7
7
  * 「再起動してください」と出せる(構成ラフのテンプレートが行っている)
8
8
  */
9
- export const EDITOR_VERSION = '0.7.1';
9
+ export const EDITOR_VERSION = '0.8.0';