@lism-css/ui 0.2.3 → 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.
- package/dist/components/Accordion/astro/index.d.ts +10 -10
- package/dist/components/Accordion/getProps.d.ts +40 -19
- package/dist/components/Accordion/react/AccIcon.d.ts +1 -6
- package/dist/components/Accordion/react/Accordion.d.ts +23 -7
- package/dist/components/Accordion/react/index.d.ts +6 -4
- package/dist/components/Accordion/setAccordion.d.ts +4 -1
- package/dist/components/Accordion/setAccordion.js +39 -42
- package/dist/components/Callout/getProps.d.ts +1 -1
- package/dist/components/Details/astro/index.d.ts +13 -0
- package/dist/components/Details/getProps.d.ts +40 -0
- package/dist/components/Details/react/Details.d.ts +37 -0
- package/dist/components/Details/react/index.d.ts +9 -0
- package/dist/components/Modal/getProps.d.ts +2 -2
- package/dist/components/Modal/setModal.d.ts +3 -0
- package/dist/components/Modal/setModal.js +24 -29
- package/dist/components/Tabs/getProps.d.ts +1 -1
- package/dist/components/astro.d.ts +1 -0
- package/dist/components/react.d.ts +1 -0
- package/dist/helper/animation.d.ts +18 -0
- package/dist/helper/animation.js +12 -0
- package/dist/helper/uuid.d.ts +1 -0
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/Accordion/_style.css +60 -35
- package/src/components/Accordion/astro/Button.astro +13 -0
- package/src/components/Accordion/astro/Heading.astro +10 -0
- package/src/components/Accordion/astro/Icon.astro +15 -0
- package/src/components/Accordion/astro/Item.astro +31 -0
- package/src/components/Accordion/astro/Panel.astro +14 -0
- package/src/components/Accordion/astro/Root.astro +17 -0
- package/src/components/Accordion/astro/index.js +10 -7
- package/src/components/Accordion/getProps.js +61 -16
- package/src/components/Accordion/react/AccIcon.jsx +5 -4
- package/src/components/Accordion/react/Accordion.jsx +56 -28
- package/src/components/Accordion/react/index.js +6 -3
- package/src/components/Accordion/setAccordion.js +114 -88
- package/src/components/Callout/getProps.ts +1 -1
- package/src/components/Details/_style.css +48 -0
- package/src/components/Details/astro/Content.astro +17 -0
- package/src/components/Details/astro/Details.astro +18 -0
- package/src/components/Details/astro/Icon.astro +14 -0
- package/src/components/{Accordion/astro/AccLabel.astro → Details/astro/Summary.astro} +1 -2
- package/src/components/Details/astro/Title.astro +10 -0
- package/src/components/Details/astro/index.js +10 -0
- package/src/components/Details/getProps.js +23 -0
- package/src/components/Details/react/Details.jsx +66 -0
- package/src/components/Details/react/index.js +6 -0
- package/src/components/Modal/script.js +1 -0
- package/src/components/Modal/setModal.ts +64 -68
- package/src/components/Tabs/astro/Tabs.astro +3 -2
- package/src/components/astro.ts +1 -0
- package/src/components/react.ts +1 -0
- package/src/helper/animation.ts +36 -0
- package/src/{components/Tabs/astro/helper.js → helper/uuid.js} +1 -1
- package/dist/components/Tabs/astro/helper.d.ts +0 -1
- package/src/components/Accordion/astro/AccBody.astro +0 -13
- package/src/components/Accordion/astro/AccHeader.astro +0 -13
- package/src/components/Accordion/astro/AccHeaderLabel.astro +0 -12
- package/src/components/Accordion/astro/AccIcon.astro +0 -17
- package/src/components/Accordion/astro/Accordion.astro +0 -22
|
@@ -1,120 +1,146 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { waitFrame, waitAnimation, maybePauseAnimation } from '../../helper/animation';
|
|
2
|
+
|
|
3
|
+
// hidden を付け外しする時の値
|
|
4
|
+
let ACCORDION_HIDDEN_VALUE = 'until-found';
|
|
5
|
+
|
|
6
|
+
// アコーディオン要素から必要な要素を取得
|
|
7
|
+
const getAccordionElements = (accordionItem) => {
|
|
8
|
+
const heading = accordionItem.querySelector('.c--accordion_heading');
|
|
9
|
+
const panel = accordionItem.querySelector('.c--accordion_panel');
|
|
10
|
+
return {
|
|
11
|
+
heading,
|
|
12
|
+
button: heading.querySelector('button'),
|
|
13
|
+
panel,
|
|
14
|
+
content: panel.querySelector('.c--accordion_content'),
|
|
15
|
+
};
|
|
9
16
|
};
|
|
10
17
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
// 親に data-allow-multiple がついていなければ、展開中のアコーディオンを閉じる
|
|
19
|
+
// Memo: 自身の除外処理はしていない( 開く動作の前に呼び出す & もし自分が含まれていても連打可能なため )
|
|
20
|
+
const maybeCloseOpenedItems = (accordionItem) => {
|
|
21
|
+
const parent = accordionItem.parentNode;
|
|
22
|
+
if (!parent) return;
|
|
15
23
|
|
|
16
|
-
//
|
|
17
|
-
|
|
24
|
+
// 親の data-allow-multiple 属性の有無をチェック
|
|
25
|
+
if (parent.hasAttribute('data-allow-multiple')) return;
|
|
18
26
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
details.setAttribute('data-opened', ''); // 属性の追加
|
|
22
|
-
});
|
|
27
|
+
// 開いているアコーディオンを取得して閉じる
|
|
28
|
+
parent.querySelectorAll(':scope > [data-opened]').forEach((_a) => closeAccordion(_a));
|
|
23
29
|
};
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!details.open && !details.hasAttribute('data-opened')) return;
|
|
31
|
+
async function openAccordion(accordionItem) {
|
|
32
|
+
const { panel, content, button } = getAccordionElements(accordionItem);
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
// アニメーションがあれば一時停止
|
|
35
|
+
maybePauseAnimation(panel);
|
|
30
36
|
|
|
31
|
-
//
|
|
32
|
-
|
|
37
|
+
// hidden を削除
|
|
38
|
+
panel.removeAttribute('hidden');
|
|
33
39
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
};
|
|
40
|
+
// 1フレーム待機
|
|
41
|
+
await waitFrame();
|
|
37
42
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
let allowMultiple = false;
|
|
41
|
-
const parent = details.parentNode;
|
|
42
|
-
if (null != parent) {
|
|
43
|
-
const dataMultiple = parent.dataset.multiple;
|
|
44
|
-
allowMultiple = 'disallow' !== dataMultiple;
|
|
45
|
-
}
|
|
46
|
-
return allowMultiple;
|
|
47
|
-
};
|
|
43
|
+
// 次フレームで高さセット( 目標の高さ = content の高さ )
|
|
44
|
+
accordionItem.style.setProperty('--_panel-h', `${content.offsetHeight}px`);
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
open(details);
|
|
63
|
-
} else if (details.open) {
|
|
64
|
-
// 閉じる処理
|
|
65
|
-
close(details);
|
|
46
|
+
// さらに1フレーム待機
|
|
47
|
+
await waitFrame();
|
|
48
|
+
|
|
49
|
+
// アニメーション開始
|
|
50
|
+
accordionItem.setAttribute('data-opened', '');
|
|
51
|
+
button.setAttribute('aria-expanded', 'true');
|
|
52
|
+
|
|
53
|
+
// アニメーションを待つ
|
|
54
|
+
const status = await waitAnimation(panel);
|
|
55
|
+
|
|
56
|
+
// アニメーションが最後まで完了した時、--_panel-h削除(高さセットしたままだとリサイズできない)
|
|
57
|
+
if ('canceled' !== status) {
|
|
58
|
+
accordionItem.style.removeProperty('--_panel-h');
|
|
66
59
|
}
|
|
67
|
-
}
|
|
60
|
+
}
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const hasDataOpen = details.hasAttribute('data-opened');
|
|
62
|
+
async function closeAccordion(accordionItem) {
|
|
63
|
+
const { panel, button } = getAccordionElements(accordionItem);
|
|
72
64
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
// アニメーションがあれば一時停止
|
|
66
|
+
maybePauseAnimation(panel);
|
|
67
|
+
|
|
68
|
+
// 現在の高さを一旦セットする
|
|
69
|
+
accordionItem.style.setProperty('--_panel-h', `${panel.offsetHeight}px`);
|
|
70
|
+
|
|
71
|
+
// 1フレーム待機
|
|
72
|
+
await waitFrame();
|
|
73
|
+
|
|
74
|
+
// 次フレームで属性を削除
|
|
75
|
+
accordionItem.removeAttribute('data-opened');
|
|
76
|
+
button.setAttribute('aria-expanded', 'false');
|
|
77
|
+
|
|
78
|
+
// 変数削除して 0px へ向けてアニメーション開始
|
|
79
|
+
accordionItem.style.removeProperty('--_panel-h');
|
|
80
|
+
|
|
81
|
+
// アニメーションを待つ
|
|
82
|
+
const status = await waitAnimation(panel);
|
|
83
|
+
|
|
84
|
+
// アニメーションが最後まで完了したら、hidden付与
|
|
85
|
+
if ('canceled' !== status) {
|
|
86
|
+
panel.setAttribute('hidden', ACCORDION_HIDDEN_VALUE);
|
|
76
87
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// アコーディオンのトグル処理
|
|
91
|
+
function toggleAccordion(accordionItem) {
|
|
92
|
+
if (accordionItem.hasAttribute('data-opened')) {
|
|
93
|
+
// 自身を閉じる
|
|
94
|
+
closeAccordion(accordionItem);
|
|
95
|
+
} else {
|
|
96
|
+
// 親に data-allow-multiple がついていなければ、他のアコーディオンを閉じる
|
|
97
|
+
maybeCloseOpenedItems(accordionItem);
|
|
98
|
+
|
|
99
|
+
// 自身を開く
|
|
100
|
+
openAccordion(accordionItem);
|
|
80
101
|
}
|
|
81
|
-
}
|
|
102
|
+
}
|
|
82
103
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
/**
|
|
105
|
+
* 個別のアコーディオンにイベントをセット(React用にクリーンアップ関数を返す)
|
|
106
|
+
*/
|
|
107
|
+
export const setEvent = (accordionItem) => {
|
|
108
|
+
const { button, panel } = getAccordionElements(accordionItem);
|
|
86
109
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
// until-found のオン・オフが使い分けれるように、最初に初期値を取得
|
|
111
|
+
if (panel.hasAttribute('hidden')) {
|
|
112
|
+
ACCORDION_HIDDEN_VALUE = panel.getAttribute('hidden');
|
|
113
|
+
}
|
|
90
114
|
|
|
115
|
+
// clickイベント登録
|
|
91
116
|
const _clickEvent = (e) => {
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
onClick(details, allowMultiple);
|
|
95
|
-
};
|
|
96
|
-
const _toggleEvent = () => {
|
|
97
|
-
onToggle(details);
|
|
117
|
+
e.preventDefault(); // hidden="until-found" の自動付け外しを無効化
|
|
118
|
+
toggleAccordion(accordionItem);
|
|
98
119
|
};
|
|
99
120
|
|
|
100
|
-
//
|
|
101
|
-
|
|
121
|
+
// beforematchイベント登録 (ページ検索時などはこっちが発火する)
|
|
122
|
+
const _beforematchEvent = (e) => {
|
|
123
|
+
e.preventDefault(); // hidden="until-found" の自動付け外しを無効化
|
|
124
|
+
toggleAccordion(accordionItem);
|
|
125
|
+
};
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
button.addEventListener('click', _clickEvent);
|
|
128
|
+
panel.addEventListener('beforematch', _beforematchEvent);
|
|
105
129
|
|
|
106
|
-
// react
|
|
130
|
+
// react用: useEffect でアンマウントされた時にremoveEventListenerしないと2重でイベントが登録してしまう
|
|
107
131
|
return () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
details.removeEventListener('toggle', _toggleEvent);
|
|
132
|
+
button.removeEventListener('click', _clickEvent);
|
|
133
|
+
panel.removeEventListener('beforematch', _beforematchEvent);
|
|
111
134
|
};
|
|
112
135
|
};
|
|
113
136
|
|
|
137
|
+
/**
|
|
138
|
+
* ページ内の全アコーディオンにイベントをセット(Astro用)
|
|
139
|
+
*/
|
|
114
140
|
const setAccordion = () => {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
setEvent(
|
|
141
|
+
const accordionAll = document.querySelectorAll('.c--accordion_item');
|
|
142
|
+
accordionAll.forEach((accordionItem) => {
|
|
143
|
+
setEvent(accordionItem);
|
|
118
144
|
});
|
|
119
145
|
};
|
|
120
146
|
export default setAccordion;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* Details コンポーネント用スタイル */
|
|
2
|
+
/* ::details-content と grid-template-rows: 0fr → 1fr のトランジションでアニメーション */
|
|
3
|
+
@layer lism.modules {
|
|
4
|
+
.c--details {
|
|
5
|
+
--duration: var(--acc-duration, 0.25s); /* transition時間 */
|
|
6
|
+
--_icon-scale: 1.1;
|
|
7
|
+
--_icon-transform: rotate(0deg); /* icon操作用 */
|
|
8
|
+
--_content-gtr: 0fr; /* 開閉操作用 */
|
|
9
|
+
}
|
|
10
|
+
.c--details[open] {
|
|
11
|
+
--_icon-transform: scaleY(-1);
|
|
12
|
+
--_content-gtr: 1fr;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Safariで表示されるデフォルトの三角形アイコンを削除 */
|
|
16
|
+
.c--details_summary::-webkit-details-marker {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* 見出しタグでもフォントを揃える */
|
|
21
|
+
/* .c--details_title {} */
|
|
22
|
+
|
|
23
|
+
/* アイコン */
|
|
24
|
+
.c--details_icon {
|
|
25
|
+
--svg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="1em" height="1em" fill="currentColor"><path d="M216.49,104.49l-80,80a12,12,0,0,1-17,0l-80-80a12,12,0,0,1,17-17L128,159l71.51-71.52a12,12,0,0,1,17,17Z"></path></svg>');
|
|
26
|
+
display: inline-grid;
|
|
27
|
+
background-color: currentColor;
|
|
28
|
+
mask: var(--svg) no-repeat center center / contain;
|
|
29
|
+
scale: var(--_icon-scale);
|
|
30
|
+
|
|
31
|
+
transition: transform var(--duration);
|
|
32
|
+
transform: var(--_icon-transform);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ::details-content 擬似要素を使ったアニメーション */
|
|
36
|
+
.c--details::details-content {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-rows: var(--_content-gtr);
|
|
39
|
+
transition-duration: var(--duration);
|
|
40
|
+
transition-property: content-visibility, grid-template;
|
|
41
|
+
transition-behavior: allow-discrete;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* コンテンツ内部のラッパー(grid-template-rows: 0frのアニメーションに必須) */
|
|
45
|
+
.c--details_body {
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Content - コンテンツコンポーネント
|
|
4
|
+
* details要素の開閉されるコンテンツ部分のラッパー
|
|
5
|
+
* grid-template-rows: 0fr → 1fr のアニメーションに必要
|
|
6
|
+
*/
|
|
7
|
+
import { Lism } from 'lism-css/astro';
|
|
8
|
+
import { defaultProps } from '../getProps';
|
|
9
|
+
|
|
10
|
+
const props = Astro.props || {};
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<Lism {...defaultProps.body}>
|
|
14
|
+
<Lism {...defaultProps.content} {...props}>
|
|
15
|
+
<slot />
|
|
16
|
+
</Lism>
|
|
17
|
+
</Lism>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Details - ルートコンポーネント
|
|
4
|
+
* details要素をレンダリングし、CSSのみでアニメーションを実装
|
|
5
|
+
* スクリプト不要で::details-contentを使用
|
|
6
|
+
*/
|
|
7
|
+
import getLismProps from 'lism-css/lib/getLismProps';
|
|
8
|
+
import { getDetailsProps } from '../getProps';
|
|
9
|
+
|
|
10
|
+
// スタイルのインポート
|
|
11
|
+
import '../_style.css';
|
|
12
|
+
|
|
13
|
+
const props = Astro.props || {};
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<details {...getLismProps(getDetailsProps(props))}>
|
|
17
|
+
<slot />
|
|
18
|
+
</details>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Icon - アイコンコンポーネント
|
|
4
|
+
* 開閉状態を示すアイコン(開いた時に回転)
|
|
5
|
+
*/
|
|
6
|
+
import { Lism } from 'lism-css/astro';
|
|
7
|
+
import { defaultProps } from '../getProps';
|
|
8
|
+
|
|
9
|
+
const props = Astro.props || {};
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<Lism {...defaultProps.icon} {...props}>
|
|
13
|
+
<slot />
|
|
14
|
+
</Lism>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
// import type { LismProps } from 'lism-css/types';
|
|
3
2
|
import { Lism } from 'lism-css/astro';
|
|
4
3
|
import { defaultProps } from '../getProps';
|
|
5
4
|
|
|
6
5
|
const props = Astro.props || {};
|
|
7
6
|
---
|
|
8
7
|
|
|
9
|
-
<Lism {...defaultProps.
|
|
8
|
+
<Lism tag='summary' {...defaultProps.summary} {...props}>
|
|
10
9
|
<slot />
|
|
11
10
|
</Lism>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro版Detailsコンポーネントのエクスポート
|
|
3
|
+
*/
|
|
4
|
+
import Root from './Details.astro';
|
|
5
|
+
import Summary from './Summary.astro';
|
|
6
|
+
import Title from './Title.astro';
|
|
7
|
+
import Icon from './Icon.astro';
|
|
8
|
+
import Content from './Content.astro';
|
|
9
|
+
|
|
10
|
+
export default { Root, Summary, Title, Content, Icon };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import atts from 'lism-css/lib/helper/atts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detailsコンポーネントのルート要素用プロパティを生成
|
|
5
|
+
* @param {Object} props - コンポーネントのプロパティ
|
|
6
|
+
* @param {string} props.lismClass - 追加のLismクラス
|
|
7
|
+
* @returns {Object} 処理済みプロパティ
|
|
8
|
+
*/
|
|
9
|
+
export function getDetailsProps({ lismClass, ...props }) {
|
|
10
|
+
props.lismClass = atts(lismClass, 'c--details');
|
|
11
|
+
return props;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 各サブコンポーネント用のデフォルトプロパティ
|
|
16
|
+
*/
|
|
17
|
+
export const defaultProps = {
|
|
18
|
+
summary: { lismClass: 'c--details_summary', layout: 'flex', g: '10', ai: 'center' },
|
|
19
|
+
title: { lismClass: 'c--details_title', tag: 'span', fx: '1', setPlain: 1 },
|
|
20
|
+
icon: { lismClass: 'c--details_icon a--icon', tag: 'span', 'aria-hidden': 'true' },
|
|
21
|
+
body: { lismClass: 'c--details_body' },
|
|
22
|
+
content: { lismClass: 'c--details_content', layout: 'flow', flow: 's' },
|
|
23
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React版 Detailsコンポーネント
|
|
3
|
+
*/
|
|
4
|
+
import getLismProps from 'lism-css/lib/getLismProps';
|
|
5
|
+
import { Lism } from 'lism-css/react';
|
|
6
|
+
import { getDetailsProps, defaultProps } from '../getProps';
|
|
7
|
+
|
|
8
|
+
// スタイルのインポート
|
|
9
|
+
import '../_style.css';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Details - ルートコンポーネント
|
|
13
|
+
* details要素をレンダリング
|
|
14
|
+
*/
|
|
15
|
+
export function Details({ children, ...props }) {
|
|
16
|
+
const lismProps = getLismProps(getDetailsProps(props));
|
|
17
|
+
|
|
18
|
+
return <details {...lismProps}>{children}</details>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Summary - サマリーコンポーネント
|
|
23
|
+
* details要素のsummary部分
|
|
24
|
+
*/
|
|
25
|
+
export function Summary({ children, ...props }) {
|
|
26
|
+
return (
|
|
27
|
+
<Lism tag='summary' {...defaultProps.summary} {...props}>
|
|
28
|
+
{children}
|
|
29
|
+
</Lism>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Title - タイトルコンポーネント
|
|
35
|
+
*/
|
|
36
|
+
export function Title({ children, ...props }) {
|
|
37
|
+
return (
|
|
38
|
+
<Lism {...defaultProps.title} {...props}>
|
|
39
|
+
{children}
|
|
40
|
+
</Lism>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Icon - アイコンコンポーネント
|
|
46
|
+
*/
|
|
47
|
+
export function Icon({ children, ...props }) {
|
|
48
|
+
return (
|
|
49
|
+
<Lism {...defaultProps.icon} {...props}>
|
|
50
|
+
{children}
|
|
51
|
+
</Lism>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Content - コンテンツコンポーネント
|
|
57
|
+
*/
|
|
58
|
+
export function Content({ children, ...props }) {
|
|
59
|
+
return (
|
|
60
|
+
<Lism {...defaultProps.body}>
|
|
61
|
+
<Lism {...defaultProps.content} {...props}>
|
|
62
|
+
{children}
|
|
63
|
+
</Lism>
|
|
64
|
+
</Lism>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,105 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
let THE_TRIGGER: HTMLElement | null = null;
|
|
3
|
-
|
|
4
|
-
// モーダルのアニメーションが完了するのを待つ.
|
|
5
|
-
const waitAnimation = async (element: HTMLElement): Promise<void> => {
|
|
6
|
-
// (子要素も取得する場合は { subtree: true } を指定)
|
|
7
|
-
const animations = element.getAnimations();
|
|
8
|
-
|
|
9
|
-
if (animations.length > 0) {
|
|
10
|
-
// allSettled を使うことで、キャンセルされた場合もrejectせずに完了扱いになる
|
|
11
|
-
await Promise.allSettled(animations.map((a) => a.finished));
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
async function modalOpen(modal: HTMLDialogElement): Promise<void> {
|
|
16
|
-
// すでに open & data-is-open が付いていれば何もしない(連打防止)
|
|
17
|
-
if (modal.open && modal.dataset.isOpen) return;
|
|
18
|
-
|
|
19
|
-
// showModal() でモーダルを開く( open 属性の付与)
|
|
20
|
-
modal.showModal();
|
|
21
|
-
|
|
22
|
-
// 次フレームで data-is-open を付与(CSS側でフェードインアニメーション開始)
|
|
23
|
-
requestAnimationFrame(() => {
|
|
24
|
-
modal.dataset.isOpen = '1';
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async function modalClose(modal: HTMLDialogElement): Promise<void> {
|
|
28
|
-
// すでに閉じている場合は何もしない
|
|
29
|
-
if (undefined === modal.dataset.isOpen) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// data-open 属性を削除(CSS側でフェードアウトアニメーション開始)
|
|
34
|
-
delete modal.dataset.isOpen;
|
|
35
|
-
|
|
36
|
-
// アニメーション完了を待機
|
|
37
|
-
await waitAnimation(modal);
|
|
38
|
-
|
|
39
|
-
// アニメーション終了後、dialog を閉じる(open属性の削除)
|
|
40
|
-
modal.close();
|
|
41
|
-
}
|
|
1
|
+
import { waitAnimation } from '../../helper/animation';
|
|
42
2
|
|
|
3
|
+
/**
|
|
4
|
+
* モーダルの開閉イベントを設定する
|
|
5
|
+
*/
|
|
43
6
|
export function setEvent(modal: HTMLDialogElement): void {
|
|
44
7
|
// modalがない、またはidがない場合は処理を終了
|
|
45
8
|
if (!modal || !modal.id) return;
|
|
46
9
|
|
|
10
|
+
// オープンした時のトリガー要素を記憶する(data属性を戻すため)
|
|
11
|
+
let theTrigger: HTMLElement | null = null;
|
|
12
|
+
|
|
47
13
|
// モーダルを開くトリガーと閉じるトリガーを取得
|
|
48
|
-
const openTriggers
|
|
49
|
-
const closeTriggers
|
|
14
|
+
const openTriggers = document.querySelectorAll<HTMLElement>(`[data-modal-open="${modal.id}"]`);
|
|
15
|
+
const closeTriggers = modal.querySelectorAll<HTMLElement>(`[data-modal-close="${modal.id}"]`);
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
17
|
+
/**
|
|
18
|
+
* ダイアログを開く処理
|
|
19
|
+
* Point: showModal() してから、CSSアニメーション用の data-is-open属性を付与する
|
|
20
|
+
*/
|
|
21
|
+
const openDialog = () => {
|
|
22
|
+
// すでに open & data-is-open が付いていれば何もしない(連打防止)
|
|
23
|
+
if (modal.open && modal.hasAttribute('data-is-open')) return;
|
|
57
24
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (THE_TRIGGER) {
|
|
61
|
-
// THE_TRIGGER.focus(); // showModal()ではフォーカス戻す必要なし
|
|
25
|
+
// showModal() でモーダルを開く( open 属性の付与)
|
|
26
|
+
modal.showModal();
|
|
62
27
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
28
|
+
// 次フレームで data-is-open を付与(CSS側でフェードインアニメーション開始)
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
modal.dataset.isOpen = '1';
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* ダイアログを閉じる処理
|
|
36
|
+
* Point: data-is-open属性を削除してCSSアニメーションが終わってから、close() を実行する
|
|
37
|
+
*/
|
|
38
|
+
const closeDialog = async (): Promise<void> => {
|
|
39
|
+
// すでに閉じている場合は何もしない
|
|
40
|
+
if (!modal.hasAttribute('data-is-open')) {
|
|
41
|
+
return;
|
|
66
42
|
}
|
|
67
43
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
});
|
|
44
|
+
// data-is-open 属性を削除(CSS側でフェードアウトアニメーション開始)
|
|
45
|
+
modal.removeAttribute('data-is-open');
|
|
71
46
|
|
|
72
|
-
|
|
47
|
+
// アニメーション完了を待機
|
|
48
|
+
await waitAnimation(modal);
|
|
49
|
+
|
|
50
|
+
// アニメーション終了後、dialog を閉じる(open属性の削除)
|
|
51
|
+
modal.close();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// openボタンにイベント登録
|
|
73
55
|
openTriggers.forEach((trigger) => {
|
|
74
|
-
trigger?.addEventListener('click', (
|
|
56
|
+
trigger?.addEventListener('click', () => {
|
|
75
57
|
// button側にもdata属性付与
|
|
76
58
|
trigger.dataset.targetOpened = '1';
|
|
77
|
-
|
|
59
|
+
theTrigger = trigger; // close() 時にdata属性削除するために記憶
|
|
78
60
|
|
|
79
61
|
// モーダルを開く
|
|
80
|
-
|
|
62
|
+
openDialog();
|
|
81
63
|
});
|
|
82
64
|
});
|
|
83
65
|
|
|
84
|
-
//
|
|
66
|
+
// closeボタンにイベント登録
|
|
85
67
|
closeTriggers.forEach((trigger) => {
|
|
86
|
-
trigger?.addEventListener('click', (
|
|
87
|
-
|
|
68
|
+
trigger?.addEventListener('click', () => {
|
|
69
|
+
void closeDialog();
|
|
88
70
|
});
|
|
89
71
|
});
|
|
90
72
|
|
|
73
|
+
// 余白クリックで閉じる
|
|
74
|
+
modal.addEventListener('click', (e) => {
|
|
75
|
+
if (e.target === modal) {
|
|
76
|
+
void closeDialog();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// close() 完了時にトリガーのdata属性をリセット
|
|
81
|
+
modal.addEventListener('close', () => {
|
|
82
|
+
if (theTrigger) {
|
|
83
|
+
theTrigger.removeAttribute('data-target-opened');
|
|
84
|
+
theTrigger = null;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
91
88
|
/**
|
|
92
89
|
* ESCキーで閉じた時もアニメーションを実行する処理
|
|
93
90
|
*/
|
|
94
91
|
modal.addEventListener('cancel', (e) => {
|
|
95
92
|
e.preventDefault(); // デフォルトの即時 close() を防ぐ
|
|
96
|
-
|
|
93
|
+
void closeDialog(); // 自分で用意したクローズ処理
|
|
97
94
|
});
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
const setModal = () => {
|
|
101
98
|
const modals = document.querySelectorAll('.c--modal');
|
|
102
|
-
|
|
103
99
|
modals?.forEach((target) => {
|
|
104
100
|
setEvent(target as HTMLDialogElement);
|
|
105
101
|
});
|