itmar-block-packages 1.7.0 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itmar-block-packages",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "We have put together a package of common React components used for WordPress custom blocks.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
package/src/BlockPlace.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  ToolbarGroup,
12
12
  ToolbarItem,
13
13
  RangeControl,
14
+ TextControl,
14
15
  RadioControl,
15
16
  ToggleControl,
16
17
  Modal,
@@ -59,6 +60,8 @@ export default function BlockPlace(props) {
59
60
  const center_cross =
60
61
  sel_pos.direction === "vertical" ? justifyCenter : middle;
61
62
  const end_cross = sel_pos.direction === "vertical" ? justifyRight : lower;
63
+ const stretch =
64
+ sel_pos.direction === "vertical" ? justifyStretch : vert_between;
62
65
  const between_icon =
63
66
  sel_pos.direction === "vertical" ? vert_between : justifyStretch;
64
67
  const around_icon =
@@ -82,12 +85,14 @@ export default function BlockPlace(props) {
82
85
  : __("lower alignment", "block-collections");
83
86
 
84
87
  const [isContainer, setIsContainer] = useState(false);
88
+ const [isFlexItem, setIsFlexItem] = useState(false);
85
89
  const [direction, setDirection] = useState("row");
86
90
  useEffect(() => {
87
91
  if (blockRef.current) {
88
92
  const element = blockRef.current;
89
93
  const parentElement = element.parentElement;
90
94
  const grandparentElement = parentElement?.parentElement;
95
+
91
96
  const computedStyle = getComputedStyle(grandparentElement);
92
97
  //親要素がFlex又はGridコンテナか
93
98
  if (
@@ -103,6 +108,7 @@ export default function BlockPlace(props) {
103
108
  computedStyle.display === "flex" ||
104
109
  computedStyle.display === "inline-flex"
105
110
  ) {
111
+ setIsFlexItem(true);
106
112
  if (
107
113
  computedStyle.flexDirection === "row" ||
108
114
  computedStyle.flexDirection === "row-reverse"
@@ -270,7 +276,7 @@ export default function BlockPlace(props) {
270
276
  props.onFlexChange("space-between", "inner_align")
271
277
  } //親コンポーネントに通知
272
278
  icon={between_icon}
273
- label={__("beteen stretch", "block-collections")}
279
+ label={__("stretch", "block-collections")}
274
280
  />
275
281
  )}
276
282
  </ToolbarItem>
@@ -335,6 +341,17 @@ export default function BlockPlace(props) {
335
341
  />
336
342
  )}
337
343
  </ToolbarItem>
344
+ <ToolbarItem>
345
+ {(itemProps) => (
346
+ <Button
347
+ {...itemProps}
348
+ isPressed={sel_pos.inner_items === "stretch"}
349
+ onClick={() => props.onFlexChange("stretch", "inner_items")} //親コンポーネントに通知
350
+ icon={stretch}
351
+ label={__("beteen stretch", "block-collections")}
352
+ />
353
+ )}
354
+ </ToolbarItem>
338
355
  </ToolbarGroup>
339
356
  )}
340
357
  {isContainer && (
@@ -413,6 +430,21 @@ export default function BlockPlace(props) {
413
430
  />
414
431
  )}
415
432
  </ToolbarItem>
433
+ <ToolbarItem>
434
+ {(itemProps) => (
435
+ <Button
436
+ {...itemProps}
437
+ isPressed={sel_pos.outer_vertical === "stretch"}
438
+ onClick={
439
+ direction === "row"
440
+ ? () => props.onVerticalChange("stretch")
441
+ : () => props.onAlignChange("stretch")
442
+ }
443
+ icon={direction === "row" ? vert_between : justifyStretch}
444
+ label={__("stretch", "block-collections")}
445
+ />
446
+ )}
447
+ </ToolbarItem>
416
448
  </ToolbarGroup>
417
449
  </>
418
450
  )}
@@ -427,6 +459,50 @@ export default function BlockPlace(props) {
427
459
  }
428
460
  />
429
461
 
462
+ {isFlexItem && (
463
+ <PanelRow className="position_row">
464
+ <TextControl
465
+ label={__("grow", "block-collections")}
466
+ labelPosition="left"
467
+ value={sel_pos.flex?.grow}
468
+ onChange={(newValue) => {
469
+ const flexObj = {
470
+ grow: newValue,
471
+ shrink: sel_pos.flex?.shrink,
472
+ basis: sel_pos.flex?.basis,
473
+ };
474
+ props.onFlexItemChange(flexObj);
475
+ }}
476
+ />
477
+ <TextControl
478
+ label={__("shrink", "block-collections")}
479
+ labelPosition="left"
480
+ value={sel_pos.flex?.shrink}
481
+ onChange={(newValue) => {
482
+ const flexObj = {
483
+ grow: sel_pos.flex?.grow,
484
+ shrink: newValue,
485
+ basis: sel_pos.flex?.basis,
486
+ };
487
+ props.onFlexItemChange(flexObj);
488
+ }}
489
+ />
490
+ <TextControl
491
+ label={__("basis", "block-collections")}
492
+ labelPosition="left"
493
+ value={sel_pos.flex?.basis}
494
+ onChange={(newValue) => {
495
+ const flexObj = {
496
+ grow: sel_pos.flex?.grow,
497
+ shrink: sel_pos.flex?.shrink,
498
+ basis: newValue,
499
+ };
500
+ props.onFlexItemChange(flexObj);
501
+ }}
502
+ />
503
+ </PanelRow>
504
+ )}
505
+
430
506
  <BlockHeight
431
507
  attributes={attributes}
432
508
  isMobile={isMobile}
@@ -603,7 +679,7 @@ export default function BlockPlace(props) {
603
679
  }
604
680
 
605
681
  export function BlockWidth(props) {
606
- const { attributes, isMobile, isSubmenu } = props;
682
+ const { attributes, isMobile, flexDirection, isSubmenu } = props;
607
683
  const { default_val, mobile_val } = attributes;
608
684
 
609
685
  //モバイルかデスクトップか
@@ -805,6 +881,16 @@ export function BlockHeight(props) {
805
881
  />
806
882
  )}
807
883
  </ToolbarItem>
884
+ <ToolbarItem>
885
+ {(itemProps) => (
886
+ <Button
887
+ {...itemProps}
888
+ isPressed={sel_pos.height_val === "auto"}
889
+ onClick={() => props.onHeightChange("auto")}
890
+ text="auto"
891
+ />
892
+ )}
893
+ </ToolbarItem>
808
894
  </ToolbarGroup>
809
895
  {sel_pos.height_val === "free" && (
810
896
  <UnitControl
package/src/blockStore.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { useSelect } from "@wordpress/data";
2
2
  import { store as blockEditorStore } from "@wordpress/block-editor";
3
+ import { createBlock } from "@wordpress/blocks";
3
4
 
4
5
  export const useTargetBlocks = (
5
6
  clientId,
@@ -52,3 +53,23 @@ export const flattenBlocks = (blocks) => {
52
53
  return acc;
53
54
  }, []);
54
55
  };
56
+
57
+ // 再帰的にブロック構造をシリアライズする関数
58
+ export const serializeBlockTree = (block) => {
59
+ return {
60
+ blockName: block.name,
61
+ attributes: block.attributes,
62
+ innerBlocks:
63
+ block.innerBlocks.length > 0
64
+ ? block.innerBlocks.map(serializeBlockTree)
65
+ : [],
66
+ };
67
+ };
68
+ // シリアライズしたブロックデータをもとの階層構造に戻す関数
69
+ export const createBlockTree = (blockData) => {
70
+ const inner = Array.isArray(blockData.innerBlocks)
71
+ ? blockData.innerBlocks.map(createBlockTree)
72
+ : [];
73
+
74
+ return createBlock(blockData.blockName, blockData.attributes, inner);
75
+ };
@@ -90,7 +90,9 @@ export const width_prm = (width, free_val) => {
90
90
  ? ` width: ${free_val}; `
91
91
  : width === "full"
92
92
  ? " width: 100%;"
93
- : " width: fit-content;";
93
+ : width === "fit"
94
+ ? " width: fit-content;"
95
+ : " width: auto;";
94
96
  return ret_width_prm;
95
97
  };
96
98
 
@@ -98,9 +100,11 @@ export const height_prm = (height, free_val) => {
98
100
  const ret_height_prm =
99
101
  height === "fit"
100
102
  ? " height: fit-content;"
103
+ : height === "full"
104
+ ? ` height: 100%; `
101
105
  : height === "free"
102
106
  ? ` height: ${free_val}; `
103
- : "height: 100%;";
107
+ : "height: auto;";
104
108
  return ret_height_prm;
105
109
  };
106
110
  //配置を返す
@@ -0,0 +1,179 @@
1
+ import { __ } from "@wordpress/i18n";
2
+ import {
3
+ PanelBody,
4
+ PanelRow,
5
+ RangeControl,
6
+ TextControl,
7
+ SelectControl,
8
+ } from "@wordpress/components";
9
+ import { format, getSettings } from "@wordpress/date";
10
+
11
+ //日付のフォーマット
12
+ const dateFormats = [
13
+ { label: "YYYY-MM-DD HH:mm:ss", value: "Y-m-d H:i:s" },
14
+ { label: "MM/DD/YYYY", value: "m/d/Y" },
15
+ { label: "DD/MM/YYYY", value: "d/m/Y" },
16
+ { label: "MMMM D, YYYY", value: "F j, Y" },
17
+ { label: "HH:mm:ss", value: "H:i:s" },
18
+ { label: "YYYY.M.D", value: "Y.n.j" },
19
+ { label: "Day, MMMM D, YYYY", value: "l, F j, Y" },
20
+ { label: "ddd, MMM D, YYYY", value: "D, M j, Y" },
21
+ { label: "YYYY年M月D日 (曜日)", value: "Y年n月j日 (l)" },
22
+ ];
23
+ //プレーンのフォーマット
24
+ const plaineFormats = [
25
+ {
26
+ key: "str_free",
27
+ label: __("Free String", "block-collections"),
28
+ value: "%s",
29
+ },
30
+ {
31
+ key: "num_comma",
32
+ label: __("Numbers (comma separated)", "block-collections"),
33
+ value: {
34
+ style: "decimal",
35
+ useGrouping: true, // カンマ区切り
36
+ },
37
+ },
38
+ {
39
+ key: "num_no_comma",
40
+ label: __("Numbers (no commas)", "block-collections"),
41
+ value: {
42
+ style: "decimal",
43
+ useGrouping: false,
44
+ },
45
+ },
46
+ {
47
+ key: "num_amount",
48
+ label: __("Amount", "block-collections"),
49
+ value: {
50
+ style: "currency",
51
+ currency: "JPY",
52
+ },
53
+ },
54
+ ];
55
+
56
+ export const FormatSelectControl = ({
57
+ titleType,
58
+ userFormat,
59
+ freeStrFormat,
60
+ decimal,
61
+ onFormatChange,
62
+ }) => {
63
+ const isPlaine = titleType === "plaine";
64
+ const isDate = titleType === "date";
65
+ const isUser = titleType === "user";
66
+
67
+ //SelectControlのオプションを生成
68
+ const options = isDate
69
+ ? dateFormats
70
+ : plaineFormats.map((f) => ({ label: f.label, value: f.key }));
71
+
72
+ return (
73
+ <PanelBody title={__("Display Format Setting", "block-collections")}>
74
+ {(isPlaine || isDate) && (
75
+ <>
76
+ <SelectControl
77
+ label={__("Select Format", "block-collections")}
78
+ value={userFormat}
79
+ options={options}
80
+ onChange={(newFormat) =>
81
+ onFormatChange({
82
+ userFormat: newFormat,
83
+ freeStrFormat,
84
+ decimal,
85
+ })
86
+ }
87
+ />
88
+
89
+ {userFormat?.startsWith("str_") && (
90
+ <TextControl
91
+ label={__("String Format", "block-collections")}
92
+ value={freeStrFormat}
93
+ onChange={(newFormat) =>
94
+ onFormatChange({
95
+ userFormat,
96
+ freeStrFormat: newFormat,
97
+ decimal,
98
+ })
99
+ }
100
+ />
101
+ )}
102
+ {userFormat?.startsWith("num_") && (
103
+ <PanelRow className="itmar_post_blocks_pannel">
104
+ <RangeControl
105
+ value={decimal}
106
+ label={__("Decimal Num", "query-blocks")}
107
+ max={5}
108
+ min={0}
109
+ onChange={(val) =>
110
+ onFormatChange({
111
+ userFormat,
112
+ freeStrFormat,
113
+ decimal: val,
114
+ })
115
+ }
116
+ />
117
+ </PanelRow>
118
+ )}
119
+ </>
120
+ )}
121
+
122
+ {isUser && (
123
+ <TextControl
124
+ label={__("User Format", "block-collections")}
125
+ value={freeStrFormat}
126
+ onChange={(newFormat) =>
127
+ onFormatChange({
128
+ userFormat: "str_free",
129
+ freeStrFormat: newFormat,
130
+ decimal,
131
+ })
132
+ }
133
+ />
134
+ )}
135
+ </PanelBody>
136
+ );
137
+ };
138
+
139
+ export const displayFormated = (
140
+ content,
141
+ userFormat,
142
+ freeStrFormat,
143
+ decimal
144
+ ) => {
145
+ // 内部で使用するロケール
146
+ const locale = getSettings().l10n?.locale || "en";
147
+
148
+ //日付にフォーマットがあれば、それで書式設定してリターン
149
+ const isDateFormat = dateFormats.find((f) => f.value === userFormat);
150
+ if (isDateFormat) {
151
+ const ret_val = format(userFormat, content, getSettings());
152
+ return ret_val;
153
+ }
154
+ //数値や文字列のフォーマット
155
+ const selectedFormat = plaineFormats.find((f) => f.key === userFormat)?.value;
156
+ if (typeof selectedFormat === "object") {
157
+ // Intl.NumberFormat オプション
158
+ try {
159
+ const numeric = parseFloat(content);
160
+ // `selectedFormat` を元に新しいフォーマット設定を生成(mutateしない)
161
+ const options = { ...selectedFormat };
162
+
163
+ if (typeof decimal === "number" && decimal > 0) {
164
+ options.minimumFractionDigits = decimal;
165
+ options.maximumFractionDigits = decimal;
166
+ }
167
+
168
+ const formatter = new Intl.NumberFormat(locale, options);
169
+ return formatter.format(numeric);
170
+ } catch (e) {
171
+ console.warn("Number format failed:", e);
172
+ return content;
173
+ }
174
+ } else if (typeof selectedFormat === "string") {
175
+ return freeStrFormat.replace("%s", content);
176
+ }
177
+ //フォーマットが見つからないときはそのまま返す
178
+ return content;
179
+ };
package/src/index.js CHANGED
@@ -48,7 +48,13 @@ export { default as ShadowStyle, ShadowElm } from "./ShadowStyle";
48
48
  export { default as PseudoElm, Arrow } from "./PseudoElm";
49
49
 
50
50
  //メディアライブラリから複数の画像を選択するコントロール
51
- export { SingleImageSelect, MultiImageSelect } from "./mediaUpload";
51
+ export {
52
+ SingleImageSelect,
53
+ MultiImageSelect,
54
+ getMediaType,
55
+ getImageAspectRatio,
56
+ getVideoAspectRatio,
57
+ } from "./mediaUpload";
52
58
 
53
59
  //ブロックのドラッガブルを設定するコントロール
54
60
  export { default as DraggableBox, useDraggingMove } from "./DraggableBox";
@@ -89,7 +95,12 @@ export {
89
95
  } from "./DateElm";
90
96
 
91
97
  //インナーブロック関連の関数
92
- export { flattenBlocks, useTargetBlocks } from "./blockStore";
98
+ export {
99
+ flattenBlocks,
100
+ useTargetBlocks,
101
+ serializeBlockTree,
102
+ createBlockTree,
103
+ } from "./blockStore";
93
104
 
94
105
  //バリデーションチェック関連の関数
95
106
  export { isValidUrlWithUrlApi } from "./validationCheck";
@@ -99,3 +110,6 @@ export { default as UpdateAllPostsBlockAttributes } from "./UpdateAllPostsBlockA
99
110
 
100
111
  //住所変換関連の関数
101
112
  export { fetchZipToAddress } from "./ZipAddress";
113
+
114
+ //書式設定用のコンポーネント
115
+ export { FormatSelectControl, displayFormated } from "./formatCreate";
@@ -113,3 +113,66 @@ export function MultiImageSelect(props) {
113
113
  </PanelBody>
114
114
  );
115
115
  }
116
+
117
+ //静止画か動画かを判定する関数
118
+ export function getMediaType(url) {
119
+ const imageExtensions = [
120
+ ".jpg",
121
+ ".jpeg",
122
+ ".png",
123
+ ".gif",
124
+ ".webp",
125
+ ".bmp",
126
+ ".svg",
127
+ ];
128
+ const videoExtensions = [".mp4", ".webm", ".ogg", ".mov", ".m4v"];
129
+
130
+ // クエリストリング(?以降)を除去
131
+ const cleanUrl = url.split("?")[0].toLowerCase();
132
+
133
+ const isImage = imageExtensions.some((ext) => cleanUrl.endsWith(ext));
134
+ const isVideo = videoExtensions.some((ext) => cleanUrl.endsWith(ext));
135
+
136
+ if (isImage) {
137
+ return "image";
138
+ } else if (isVideo) {
139
+ return "video";
140
+ } else {
141
+ // 拡張子で判別できない → HEADリクエストでContent-Type判定 or fallback
142
+ return undefined;
143
+ }
144
+ }
145
+ //静止画のアスペクト比を返す関数
146
+ export function getImageAspectRatio(url) {
147
+ return new Promise((resolve, reject) => {
148
+ const img = new Image();
149
+ img.onload = function () {
150
+ const aspectRatio = img.naturalWidth / img.naturalHeight;
151
+ resolve(aspectRatio);
152
+ };
153
+ img.onerror = function () {
154
+ reject(new Error("画像の読み込みに失敗しました: " + url));
155
+ };
156
+ img.src = url;
157
+ });
158
+ }
159
+ //動画のアスペクト比を返す関数
160
+ export function getVideoAspectRatio(url) {
161
+ return new Promise((resolve, reject) => {
162
+ const video = document.createElement("video");
163
+
164
+ video.preload = "metadata";
165
+ video.src = url;
166
+ video.muted = true; // 一部のブラウザで安全に動作させるため
167
+ video.playsInline = true;
168
+
169
+ video.onloadedmetadata = function () {
170
+ const aspectRatio = video.videoWidth / video.videoHeight;
171
+ resolve(aspectRatio);
172
+ };
173
+
174
+ video.onerror = function () {
175
+ reject(new Error("動画の読み込みに失敗しました: " + url));
176
+ };
177
+ });
178
+ }