@digitalwalletcorp/vue-core 1.2.1 → 1.3.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.
Files changed (188) hide show
  1. package/README.md +79 -3
  2. package/lib/accordion-fieldset.d.cts +14 -4
  3. package/lib/accordion-fieldset.d.ts +14 -4
  4. package/lib/accordion-section.d.cts +20 -10
  5. package/lib/accordion-section.d.ts +20 -10
  6. package/lib/components-2zQ2SZuY.js +26 -0
  7. package/lib/components-HaiHcseF.cjs +1 -0
  8. package/lib/components.d.cts +10 -0
  9. package/lib/components.d.ts +10 -0
  10. package/lib/copy-button.d.cts +34 -0
  11. package/lib/copy-button.d.ts +34 -0
  12. package/lib/dark-mode-selector.d.cts +32 -0
  13. package/lib/dark-mode-selector.d.ts +32 -0
  14. package/lib/data-table.d.cts +30 -0
  15. package/lib/data-table.d.ts +30 -0
  16. package/lib/help-button.d.cts +39 -0
  17. package/lib/help-button.d.ts +39 -0
  18. package/lib/help-popper.d.cts +39 -0
  19. package/lib/help-popper.d.ts +39 -0
  20. package/lib/helpers/data-table-csv.d.cts +17 -0
  21. package/lib/helpers/data-table-csv.d.ts +17 -0
  22. package/lib/helpers/show-floating-component.d.cts +82 -0
  23. package/lib/helpers/show-floating-component.d.ts +82 -0
  24. package/lib/helpers/show-modal-dialog.d.cts +73 -0
  25. package/lib/helpers/show-modal-dialog.d.ts +73 -0
  26. package/lib/helpers/show-overlay-box.d.cts +53 -0
  27. package/lib/helpers/show-overlay-box.d.ts +53 -0
  28. package/lib/helpers/show-screen-loading.d.cts +20 -0
  29. package/lib/helpers/show-screen-loading.d.ts +20 -0
  30. package/lib/helpers/show-snackbar.d.cts +47 -0
  31. package/lib/helpers/show-snackbar.d.ts +47 -0
  32. package/lib/hourglass-loading.d.cts +1 -1
  33. package/lib/hourglass-loading.d.ts +1 -1
  34. package/lib/index.cjs +1 -1
  35. package/lib/index.d.cts +34 -0
  36. package/lib/index.d.ts +34 -0
  37. package/lib/index.js +445 -18
  38. package/lib/internal/anchored-popover.d.cts +35 -0
  39. package/lib/internal/anchored-popover.d.ts +35 -0
  40. package/lib/internal/anchored-position.d.cts +50 -0
  41. package/lib/internal/anchored-position.d.ts +50 -0
  42. package/lib/internal/data-table-controls.d.cts +42 -0
  43. package/lib/internal/data-table-controls.d.ts +42 -0
  44. package/lib/internal/data-table-value.d.cts +32 -0
  45. package/lib/internal/data-table-value.d.ts +32 -0
  46. package/lib/internal/floating-component.d.cts +52 -0
  47. package/lib/internal/floating-component.d.ts +52 -0
  48. package/lib/internal/icon-popper.d.cts +32 -0
  49. package/lib/internal/icon-popper.d.ts +32 -0
  50. package/lib/internal/mount-window.d.cts +39 -0
  51. package/lib/internal/mount-window.d.ts +39 -0
  52. package/lib/internal/movable-window.d.cts +55 -0
  53. package/lib/internal/movable-window.d.ts +55 -0
  54. package/lib/internal/opened-overlay-boxes.d.cts +2 -0
  55. package/lib/internal/opened-overlay-boxes.d.ts +2 -0
  56. package/lib/internal/pagination.d.cts +22 -0
  57. package/lib/internal/pagination.d.ts +22 -0
  58. package/lib/internal/snackbar-container.d.cts +20 -0
  59. package/lib/internal/snackbar-container.d.ts +20 -0
  60. package/lib/internal/snackbar-item.d.cts +17 -0
  61. package/lib/internal/snackbar-item.d.ts +17 -0
  62. package/lib/internal/tab-group-context.d.cts +4 -2
  63. package/lib/internal/tab-group-context.d.ts +4 -2
  64. package/lib/internal/use-movable-window.d.cts +44 -0
  65. package/lib/internal/use-movable-window.d.ts +44 -0
  66. package/lib/internal/window-order.d.cts +6 -0
  67. package/lib/internal/window-order.d.ts +6 -0
  68. package/lib/intersection-lazy-component.d.cts +32 -0
  69. package/lib/intersection-lazy-component.d.ts +32 -0
  70. package/lib/label-button.d.cts +2 -2
  71. package/lib/label-button.d.ts +2 -2
  72. package/lib/label-icon.d.cts +2 -2
  73. package/lib/label-icon.d.ts +2 -2
  74. package/lib/modal-dialog.d.cts +67 -0
  75. package/lib/modal-dialog.d.ts +67 -0
  76. package/lib/nuxt.cjs +1 -1
  77. package/lib/nuxt.js +1 -1
  78. package/lib/overlay-box.d.cts +52 -0
  79. package/lib/overlay-box.d.ts +52 -0
  80. package/lib/page-section.d.cts +2 -2
  81. package/lib/page-section.d.ts +2 -2
  82. package/lib/page-title.d.cts +2 -2
  83. package/lib/page-title.d.ts +2 -2
  84. package/lib/register.cjs +1 -1
  85. package/lib/register.js +1 -1
  86. package/lib/screen-loading.d.cts +7 -0
  87. package/lib/screen-loading.d.ts +7 -0
  88. package/lib/style.css +1 -1
  89. package/lib/tab-content.d.cts +5 -5
  90. package/lib/tab-content.d.ts +5 -5
  91. package/lib/tab-group.d.cts +7 -10
  92. package/lib/tab-group.d.ts +7 -10
  93. package/lib/types/data-table.d.cts +185 -0
  94. package/lib/types/data-table.d.ts +185 -0
  95. package/lib/types/modal-dialog.d.cts +13 -0
  96. package/lib/types/modal-dialog.d.ts +13 -0
  97. package/lib/types/movable-window.d.cts +16 -0
  98. package/lib/types/movable-window.d.ts +16 -0
  99. package/lib/warning-popper.d.cts +39 -0
  100. package/lib/warning-popper.d.ts +39 -0
  101. package/lib/warning-popper.vue_vue_type_style_index_0_lang-CxVzqsbn.cjs +1 -0
  102. package/lib/warning-popper.vue_vue_type_style_index_0_lang-zt-jCsvz.js +1833 -0
  103. package/package.json +40 -14
  104. package/src/accordion-fieldset.vue +24 -19
  105. package/src/accordion-section.vue +33 -28
  106. package/src/components.ts +10 -0
  107. package/src/copy-button.vue +89 -0
  108. package/src/dark-mode-selector.vue +212 -0
  109. package/src/data-table.vue +643 -0
  110. package/src/help-button.vue +128 -0
  111. package/src/help-popper.vue +70 -0
  112. package/src/helpers/data-table-csv.ts +55 -0
  113. package/src/helpers/show-floating-component.ts +169 -0
  114. package/src/helpers/show-modal-dialog.ts +121 -0
  115. package/src/helpers/show-overlay-box.ts +106 -0
  116. package/src/helpers/show-screen-loading.ts +37 -0
  117. package/src/helpers/show-snackbar.ts +153 -0
  118. package/src/hourglass-loading.vue +1 -1
  119. package/src/index.ts +34 -0
  120. package/src/internal/anchored-popover.vue +192 -0
  121. package/src/internal/anchored-position.ts +135 -0
  122. package/src/internal/data-table-controls.vue +203 -0
  123. package/src/internal/data-table-value.ts +99 -0
  124. package/src/internal/floating-component.vue +202 -0
  125. package/src/internal/icon-popper.vue +185 -0
  126. package/src/internal/mount-window.ts +110 -0
  127. package/src/internal/movable-window.vue +315 -0
  128. package/src/internal/opened-overlay-boxes.ts +2 -0
  129. package/src/internal/pagination.vue +325 -0
  130. package/src/internal/snackbar-container.vue +258 -0
  131. package/src/internal/snackbar-item.ts +20 -0
  132. package/src/internal/tab-group-context.ts +4 -2
  133. package/src/internal/use-movable-window.ts +208 -0
  134. package/src/internal/window-order.ts +11 -0
  135. package/src/intersection-lazy-component.vue +84 -0
  136. package/src/label-button.vue +9 -6
  137. package/src/label-icon.vue +2 -2
  138. package/src/modal-dialog.vue +287 -0
  139. package/src/overlay-box.vue +218 -0
  140. package/src/page-section.vue +7 -7
  141. package/src/page-title.vue +4 -4
  142. package/src/screen-loading.vue +90 -0
  143. package/src/tab-content.vue +16 -15
  144. package/src/tab-group.vue +34 -36
  145. package/src/types/data-table.ts +196 -0
  146. package/src/types/modal-dialog.ts +16 -0
  147. package/src/types/movable-window.ts +18 -0
  148. package/src/warning-popper.vue +70 -0
  149. package/lib/accordion-fieldset.cjs +0 -1
  150. package/lib/accordion-fieldset.js +0 -4
  151. package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-Cf4czDXz.js +0 -66
  152. package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-DoM5QjpJ.cjs +0 -1
  153. package/lib/accordion-section.cjs +0 -1
  154. package/lib/accordion-section.js +0 -4
  155. package/lib/accordion-section.vue_vue_type_style_index_0_lang-CFDdUEnn.cjs +0 -1
  156. package/lib/accordion-section.vue_vue_type_style_index_0_lang-Gvjjx02P.js +0 -78
  157. package/lib/components-C63HJoCn.cjs +0 -1
  158. package/lib/components-q4Cf97bO.js +0 -24
  159. package/lib/hourglass-loading.cjs +0 -1
  160. package/lib/hourglass-loading.js +0 -4
  161. package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-C72UVA9W.cjs +0 -1
  162. package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-DkUGIxQ2.js +0 -22
  163. package/lib/label-button.cjs +0 -1
  164. package/lib/label-button.js +0 -4
  165. package/lib/label-button.vue_vue_type_style_index_0_lang-D7ZgfZOL.cjs +0 -1
  166. package/lib/label-button.vue_vue_type_style_index_0_lang-DqRYddVt.js +0 -44
  167. package/lib/label-icon.cjs +0 -1
  168. package/lib/label-icon.js +0 -4
  169. package/lib/label-icon.vue_vue_type_style_index_0_lang-D9Cimu20.cjs +0 -1
  170. package/lib/label-icon.vue_vue_type_style_index_0_lang-me52HE2V.js +0 -38
  171. package/lib/page-section.cjs +0 -1
  172. package/lib/page-section.js +0 -4
  173. package/lib/page-section.vue_vue_type_style_index_0_lang-DEAkTLxd.cjs +0 -1
  174. package/lib/page-section.vue_vue_type_style_index_0_lang-DpVQe_rV.js +0 -54
  175. package/lib/page-title.cjs +0 -1
  176. package/lib/page-title.js +0 -4
  177. package/lib/page-title.vue_vue_type_style_index_0_lang-BqIsIks5.cjs +0 -1
  178. package/lib/page-title.vue_vue_type_style_index_0_lang-ChZot85K.js +0 -36
  179. package/lib/tab-content.cjs +0 -1
  180. package/lib/tab-content.js +0 -4
  181. package/lib/tab-content.vue_vue_type_style_index_0_lang-BeCi7Xkp.cjs +0 -1
  182. package/lib/tab-content.vue_vue_type_style_index_0_lang-CcQLHmIF.js +0 -44
  183. package/lib/tab-group.cjs +0 -1
  184. package/lib/tab-group.js +0 -4
  185. package/lib/tab-group.vue_vue_type_style_index_0_lang-C84mDEOC.js +0 -103
  186. package/lib/tab-group.vue_vue_type_style_index_0_lang-CMaufRJV.cjs +0 -1
  187. package/lib/use-shrinkable-CS2bmsJJ.js +0 -17
  188. package/lib/use-shrinkable-Cu9JQZll.cjs +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalwalletcorp/vue-core",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "Basic UI components for Vue 3",
6
6
  "main": "lib/index.cjs",
@@ -37,23 +37,15 @@
37
37
  "./global": {
38
38
  "types": "./lib/global.d.ts"
39
39
  },
40
- "./style.css": "./lib/style.css",
41
- "./*": {
42
- "import": {
43
- "types": "./lib/*.d.ts",
44
- "default": "./lib/*.js"
45
- },
46
- "require": {
47
- "types": "./lib/*.d.cts",
48
- "default": "./lib/*.cjs"
49
- }
50
- }
40
+ "./style.css": "./lib/style.css"
51
41
  },
52
42
  "scripts": {
53
43
  "build": "vite build",
54
44
  "yarn:dedupe": "npx yarn-deduplicate --strategy highest yarn.lock",
55
45
  "install:dev": "rm -rf lib dest && find node_modules -mindepth 1 -delete && yarn install",
56
- "typecheck": "vue-tsc --noEmit -p tsconfig.json",
46
+ "tsc": "vue-tsc --noEmit -p tsconfig.json",
47
+ "lint": "eslint . && stylelint \"src/**/*.vue\" \"src/**/*.css\"",
48
+ "lint:fix": "eslint --fix . && stylelint --fix \"**/*.vue\" \"**/*.css\"",
57
49
  "version:patch": "npm version patch",
58
50
  "version:minor": "npm version minor",
59
51
  "version:major": "npm version major",
@@ -69,7 +61,7 @@
69
61
  "components",
70
62
  "ui"
71
63
  ],
72
- "author": "satoshi kotaki",
64
+ "author": "Digital Wallet Corporation",
73
65
  "license": "MIT",
74
66
  "repository": {
75
67
  "type": "git",
@@ -85,23 +77,57 @@
85
77
  ],
86
78
  "peerDependencies": {
87
79
  "@digitalwalletcorp/vue-svg-icons": "^1.15.3",
80
+ "@nuxt/kit": "^3.17.5",
88
81
  "vue": "^3.5.29"
89
82
  },
83
+ "peerDependenciesMeta": {
84
+ "@nuxt/kit": {
85
+ "optional": true
86
+ }
87
+ },
90
88
  "devDependencies": {
91
89
  "@digitalwalletcorp/vue-svg-icons": "^1.15.3",
92
90
  "@nuxt/kit": "^3.17.5",
91
+ "@stylistic/eslint-plugin": "5.10.0",
92
+ "@stylistic/stylelint-plugin": "5.3.1",
93
93
  "@types/node": "24.13.6",
94
94
  "@vitejs/plugin-vue": "^6.0.0",
95
95
  "@vitest/coverage-v8": "3.2.4",
96
+ "@vue/compiler-core": "3.5.43",
96
97
  "@vue/test-utils": "2.4.6",
98
+ "eslint": "10.11.0",
99
+ "eslint-plugin-vue": "10.11.1",
100
+ "globals": "17.13.0",
97
101
  "happy-dom": "20.14.5",
98
102
  "nuxt": "^3.17.5",
103
+ "postcss-html": "2.0.0",
104
+ "stylelint": "17.16.0",
105
+ "stylelint-config-recommended-vue": "2.0.0",
106
+ "stylelint-config-standard": "40.0.0",
99
107
  "typescript": "6.0.3",
108
+ "typescript-eslint": "8.71.0",
100
109
  "vite": "^7.0.0",
101
110
  "vite-plugin-dts": "5.1.0",
102
111
  "vite-tsconfig-paths": "6.1.1",
103
112
  "vitest": "3.2.4",
104
113
  "vue": "^3.5.29",
114
+ "vue-eslint-parser": "10.4.1",
105
115
  "vue-tsc": "3.1.5"
116
+ },
117
+ "resolutions": {
118
+ "eslint": "10.11.0",
119
+ "eslint-plugin-vue": "10.11.1",
120
+ "typescript-eslint": "8.71.0",
121
+ "@stylistic/eslint-plugin": "5.10.0",
122
+ "globals": "17.13.0",
123
+ "vue-eslint-parser": "10.4.1",
124
+ "stylelint": "17.16.0",
125
+ "@stylistic/stylelint-plugin": "5.3.1",
126
+ "stylelint-config-standard": "40.0.0",
127
+ "stylelint-config-recommended-vue": "2.0.0",
128
+ "stylelint-config-recommended": "18.0.0",
129
+ "stylelint-config-html": "2.0.0",
130
+ "postcss-html": "2.0.0",
131
+ "postcss": "8.5.28"
106
132
  }
107
133
  }
@@ -10,10 +10,10 @@
10
10
  <button
11
11
  type="button"
12
12
  class="dwui-accordion-toggle"
13
- :aria-expanded="pageContext.expanded"
13
+ :aria-expanded="expanded"
14
14
  @click="onClickToggle"
15
15
  >
16
- <SvgTriangleButton :direction="pageContext.expanded ? 'up' : 'down'" />
16
+ <SvgTriangleButton :direction="expanded ? 'up' : 'down'" />
17
17
  <LabelIcon v-if="props.legend != null || slots.legend != null || slots.icon != null">
18
18
  <!-- 渡されたスロットだけを中継する。空のラベルを渡すとアイコンの後ろに間隔が空くため -->
19
19
  <template
@@ -33,7 +33,7 @@
33
33
  </legend>
34
34
  <div
35
35
  ref="refContents"
36
- :class="[ 'dwui-accordion-contents', shrinkable ? pageContext.expanded ? 'dwui-expanded' : 'dwui-shrunk' : '' ]"
36
+ :class="[ 'dwui-accordion-contents', shrinkable ? expanded ? 'dwui-expanded' : 'dwui-shrunk' : '' ]"
37
37
  >
38
38
  <slot />
39
39
  </div>
@@ -41,22 +41,22 @@
41
41
  </template>
42
42
 
43
43
  <script setup lang="ts">
44
- import { onMounted, reactive, ref } from 'vue';
44
+ import { computed, ref } from 'vue';
45
45
  import { SvgTriangleButton } from '@digitalwalletcorp/vue-svg-icons';
46
46
  import LabelIcon from '@/label-icon.vue';
47
47
  import { useShrinkable } from '@/internal/use-shrinkable';
48
48
 
49
- interface Props {
49
+ export interface Props {
50
50
  /** 凡例の文字。HTMLを含める場合は#legendスロットを使う */
51
51
  legend?: string;
52
52
  /** trueの場合、fieldsetを無効にする */
53
53
  disabled?: boolean;
54
- /** trueの場合、開いた状態で表示する デフォルト false */
54
+ /** trueの場合、開いた状態で表示する デフォルト false。v-model:expanded で束縛しないときの初期値 */
55
55
  initialExpand?: boolean;
56
56
  }
57
57
  const props = defineProps<Props>();
58
58
 
59
- interface Slots {
59
+ export interface Slots {
60
60
  /** 凡例の文字の前に表示するアイコン */
61
61
  icon?(): unknown;
62
62
  /** 凡例。legendより優先する */
@@ -66,18 +66,23 @@ interface Slots {
66
66
  const slots = defineSlots<Slots>();
67
67
 
68
68
  const refContents = ref<HTMLElement>();
69
- const pageContext = reactive({
70
- expanded: true
69
+ // 真偽値の prop は未指定だと false に変換されるため、未指定を undefined のまま受けるよう既定値を明示する
70
+ /** 親が v-model:expanded で束縛したときの開閉状態。束縛しなければ undefined */
71
+ const model = defineModel<boolean | undefined>('expanded', { default: undefined });
72
+ /** 束縛しないときの開閉状態 */
73
+ const localExpanded = ref(!!props.initialExpand);
74
+ const expanded = computed({
75
+ get: (): boolean => model.value ?? localExpanded.value,
76
+ set: (value: boolean): void => {
77
+ localExpanded.value = value;
78
+ model.value = value;
79
+ }
71
80
  });
72
81
  const shrinkable = useShrinkable(refContents);
73
82
 
74
83
  const onClickToggle = () => {
75
- pageContext.expanded = !pageContext.expanded;
84
+ expanded.value = !expanded.value;
76
85
  };
77
-
78
- onMounted(() => {
79
- pageContext.expanded = !!props.initialExpand;
80
- });
81
86
  </script>
82
87
 
83
88
  <style>
@@ -89,8 +94,8 @@ onMounted(() => {
89
94
  :where(.dwui-accordion-toggle) {
90
95
  display: inline-flex;
91
96
  align-items: center;
92
- gap: 4px;
93
- margin-left: 4px;
97
+ gap: 0.33em;
98
+ margin-left: 0.33em;
94
99
  padding: 0;
95
100
  border: none;
96
101
  background: none;
@@ -104,14 +109,14 @@ onMounted(() => {
104
109
  }
105
110
 
106
111
  :where(.dwui-accordion-toggle:focus-visible) {
107
- outline: var(--dwui-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
108
- outline-offset: var(--dwui-offset-focus-ring, 2px);
112
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
113
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
109
114
  }
110
115
 
111
116
  :where(.dwui-accordion-contents) {
112
117
  position: relative;
113
118
  width: fit-content;
114
- padding: 2px;
119
+ padding: 0.17em;
115
120
  overflow: hidden;
116
121
  opacity: 1;
117
122
  }
@@ -7,7 +7,7 @@
7
7
  ref="refContents"
8
8
  class="dwui-accordion-section-contents"
9
9
  :class="[
10
- shrinkable ? pageContext.expanded ? 'dwui-expanded' : 'dwui-shrunk' : '',
10
+ shrinkable ? expanded ? 'dwui-expanded' : 'dwui-shrunk' : '',
11
11
  { 'dwui-no-gradient': props.noGradient }
12
12
  ]"
13
13
  >
@@ -21,7 +21,7 @@
21
21
  type="button"
22
22
  class="dwui-accordion-section-toggle"
23
23
  :disabled="props.disabled"
24
- :aria-expanded="pageContext.expanded"
24
+ :aria-expanded="expanded"
25
25
  @click.stop="onClickToggle"
26
26
  >
27
27
  <LabelIcon v-if="toggleIcon() != null || toggleLabel != null">
@@ -43,7 +43,7 @@
43
43
  </LabelIcon>
44
44
  <SvgTriangleButton
45
45
  v-else
46
- :direction="pageContext.expanded ? 'up' : 'down'"
46
+ :direction="expanded ? 'up' : 'down'"
47
47
  />
48
48
  </button>
49
49
  </div>
@@ -51,7 +51,7 @@
51
51
  </template>
52
52
 
53
53
  <script setup lang="ts">
54
- import { computed, onMounted, reactive, ref } from 'vue';
54
+ import { computed, ref } from 'vue';
55
55
  import type { Component, Slot } from 'vue';
56
56
  import { SvgTriangleButton } from '@digitalwalletcorp/vue-svg-icons';
57
57
  import LabelIcon from '@/label-icon.vue';
@@ -60,40 +60,49 @@ import type { LabelPreset } from '@/types/label-preset';
60
60
 
61
61
  // 開閉ボタンのアイコンとラベルは別々に決める。どちらも、スロット/ラベルの指定 > プリセット の順で使う
62
62
  // 何も指定が無い場合だけ、既定の三角を表示する
63
- interface Props {
63
+ export interface Props {
64
64
  /** trueの場合、開閉ボタンを無効にする */
65
65
  disabled?: boolean;
66
- /** trueの場合、開いた状態で表示する デフォルト false */
66
+ /** trueの場合、開いた状態で表示する デフォルト false。v-model:expanded で束縛しないときの初期値 */
67
67
  initialExpand?: boolean;
68
68
  /** trueの場合、縮めたときに中身の下側をぼかさない */
69
69
  noGradient?: boolean;
70
70
  /** 縮めているときの開閉ボタン(開く)のラベル */
71
- openLabel?: string;
71
+ expandLabel?: string;
72
72
  /** 開いているときの開閉ボタン(閉じる)のラベル */
73
- closeLabel?: string;
73
+ collapseLabel?: string;
74
74
  /** 縮めているときの開閉ボタン(開く)のアイコンとラベル */
75
- openPreset?: LabelPreset;
75
+ expandPreset?: LabelPreset;
76
76
  /** 開いているときの開閉ボタン(閉じる)のアイコンとラベル */
77
- closePreset?: LabelPreset;
77
+ collapsePreset?: LabelPreset;
78
78
  }
79
79
  const props = defineProps<Props>();
80
80
 
81
- interface Slots {
81
+ export interface Slots {
82
82
  /** 縮めているときの開閉ボタン(開く)のアイコン */
83
- 'open-icon'?(): unknown;
83
+ 'expand-icon'?(): unknown;
84
84
  /** 開いているときの開閉ボタン(閉じる)のアイコン */
85
- 'close-icon'?(): unknown;
85
+ 'collapse-icon'?(): unknown;
86
86
  default?(): unknown;
87
87
  }
88
88
  const slots = defineSlots<Slots>();
89
89
 
90
90
  const refContents = ref<HTMLElement>();
91
- const pageContext = reactive({
92
- expanded: true
91
+ // 真偽値の prop は未指定だと false に変換されるため、未指定を undefined のまま受けるよう既定値を明示する
92
+ /** 親が v-model:expanded で束縛したときの開閉状態。束縛しなければ undefined */
93
+ const model = defineModel<boolean | undefined>('expanded', { default: undefined });
94
+ /** 束縛しないときの開閉状態 */
95
+ const localExpanded = ref(!!props.initialExpand);
96
+ const expanded = computed({
97
+ get: (): boolean => model.value ?? localExpanded.value,
98
+ set: (value: boolean): void => {
99
+ localExpanded.value = value;
100
+ model.value = value;
101
+ }
93
102
  });
94
103
  const shrinkable = useShrinkable(refContents);
95
104
 
96
- const togglePreset = computed((): LabelPreset | undefined => pageContext.expanded ? props.closePreset : props.openPreset);
105
+ const togglePreset = computed((): LabelPreset | undefined => expanded.value ? props.collapsePreset : props.expandPreset);
97
106
 
98
107
  /**
99
108
  * 開閉ボタンに表示するアイコン。スロットは関数のままコンポーネントとして描画する
@@ -102,7 +111,7 @@ const togglePreset = computed((): LabelPreset | undefined => pageContext.expande
102
111
  * @returns {Slot | Component | undefined}
103
112
  */
104
113
  const toggleIcon = (): Slot | Component | undefined => {
105
- const slot = pageContext.expanded ? slots['close-icon'] : slots['open-icon'];
114
+ const slot = expanded.value ? slots['collapse-icon'] : slots['expand-icon'];
106
115
  return (slot as Slot | undefined) ?? togglePreset.value?.icon;
107
116
  };
108
117
 
@@ -112,12 +121,12 @@ const toggleIcon = (): Slot | Component | undefined => {
112
121
  * @returns {Record<string, unknown> | undefined}
113
122
  */
114
123
  const toggleIconProps = (): Record<string, unknown> | undefined => {
115
- const slot = pageContext.expanded ? slots['close-icon'] : slots['open-icon'];
124
+ const slot = expanded.value ? slots['collapse-icon'] : slots['expand-icon'];
116
125
  return slot == null ? togglePreset.value?.iconProps : undefined;
117
126
  };
118
127
 
119
128
  const toggleLabel = computed((): string | undefined => {
120
- const label = pageContext.expanded ? props.closeLabel : props.openLabel;
129
+ const label = expanded.value ? props.collapseLabel : props.expandLabel;
121
130
  if (label != null) {
122
131
  return label;
123
132
  }
@@ -126,12 +135,8 @@ const toggleLabel = computed((): string | undefined => {
126
135
  });
127
136
 
128
137
  const onClickToggle = () => {
129
- pageContext.expanded = !pageContext.expanded;
138
+ expanded.value = !expanded.value;
130
139
  };
131
-
132
- onMounted(() => {
133
- pageContext.expanded = !!props.initialExpand;
134
- });
135
140
  </script>
136
141
 
137
142
  <style>
@@ -189,16 +194,16 @@ onMounted(() => {
189
194
  }
190
195
 
191
196
  :where(.dwui-accordion-section-toggle:focus-visible) {
192
- outline: var(--dwui-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
193
- outline-offset: var(--dwui-offset-focus-ring, 2px);
197
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
198
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
194
199
  }
195
200
 
196
201
  :where(.dwui-accordion-section-toggle[aria-expanded='true']) {
197
- margin-top: 8px;
202
+ margin-top: 0.67em;
198
203
  }
199
204
 
200
205
  /* 縮めたときはボタンを中身のぼかしに少し重ね、中身とボタンの間を詰める */
201
206
  :where(.dwui-accordion-section-toggle[aria-expanded='false']) {
202
- margin-top: -10px;
207
+ margin-top: -0.83em;
203
208
  }
204
209
  </style>
package/src/components.ts CHANGED
@@ -1,10 +1,20 @@
1
1
  // 公開するコンポーネントをここに列挙する。index/register/nuxtはここから読む
2
2
  export { default as AccordionFieldset } from '@/accordion-fieldset.vue';
3
3
  export { default as AccordionSection } from '@/accordion-section.vue';
4
+ export { default as CopyButton } from '@/copy-button.vue';
5
+ export { default as DarkModeSelector } from '@/dark-mode-selector.vue';
6
+ export { default as DataTable } from '@/data-table.vue';
7
+ export { default as HelpButton } from '@/help-button.vue';
8
+ export { default as HelpPopper } from '@/help-popper.vue';
4
9
  export { default as HourglassLoading } from '@/hourglass-loading.vue';
10
+ export { default as IntersectionLazyComponent } from '@/intersection-lazy-component.vue';
5
11
  export { default as LabelButton } from '@/label-button.vue';
6
12
  export { default as LabelIcon } from '@/label-icon.vue';
13
+ export { default as ModalDialog } from '@/modal-dialog.vue';
14
+ export { default as OverlayBox } from '@/overlay-box.vue';
7
15
  export { default as PageSection } from '@/page-section.vue';
8
16
  export { default as PageTitle } from '@/page-title.vue';
17
+ export { default as ScreenLoading } from '@/screen-loading.vue';
9
18
  export { default as TabContent } from '@/tab-content.vue';
10
19
  export { default as TabGroup } from '@/tab-group.vue';
20
+ export { default as WarningPopper } from '@/warning-popper.vue';
@@ -0,0 +1,89 @@
1
+ <!--
2
+ 値をクリップボードにコピーするボタン
3
+ -->
4
+ <template>
5
+ <button
6
+ type="button"
7
+ class="dwui-copy-button"
8
+ :aria-label="slots.default ? undefined : props.label"
9
+ @click="onClickCopy"
10
+ >
11
+ <slot>
12
+ <SvgMemo />
13
+ </slot>
14
+ </button>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { SvgMemo } from '@digitalwalletcorp/vue-svg-icons';
19
+
20
+ export interface Props {
21
+ /** コピーする値。関数を渡すとクリック時に呼び出して値を得る */
22
+ value: string | number | (() => string | number);
23
+ /** ボタンの読み上げ名。スロットで文字を渡したときは使わない */
24
+ label?: string;
25
+ }
26
+ const props = withDefaults(defineProps<Props>(), {
27
+ label: 'Copy'
28
+ });
29
+
30
+ export interface Emits {
31
+ /** コピーに成功したときに、コピーした文字列を発行する */
32
+ (e: 'emit:copy', value: string): void;
33
+ /** コピーに失敗したときに、原因のエラーを発行する */
34
+ (e: 'emit:error', error: unknown): void;
35
+ }
36
+ const emit = defineEmits<Emits>();
37
+
38
+ export interface Slots {
39
+ /** ボタンの中身。未指定ならメモのアイコン */
40
+ default?(): unknown;
41
+ }
42
+ const slots = defineSlots<Slots>();
43
+
44
+ const onClickCopy = async () => {
45
+ const value = String(typeof props.value === 'function' ? props.value() : props.value);
46
+ try {
47
+ await navigator.clipboard.writeText(value);
48
+ } catch (error) {
49
+ emit('emit:error', error);
50
+ return;
51
+ }
52
+ emit('emit:copy', value);
53
+ };
54
+ </script>
55
+
56
+ <style>
57
+ :where(.dwui-copy-button) {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ padding: 0.17em;
62
+ border: none;
63
+ border-radius: 4px;
64
+ background-color: transparent;
65
+ background-image: none;
66
+ color: inherit;
67
+ font: inherit;
68
+ line-height: 0;
69
+ cursor: pointer;
70
+ }
71
+
72
+ :where(.dwui-copy-button:not(:disabled):hover) {
73
+ background: var(--dwui-background-copy-button-hover, light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.12)));
74
+ }
75
+
76
+ :where(.dwui-copy-button:not(:disabled):active) {
77
+ transform: translate(1px, 1px);
78
+ }
79
+
80
+ :where(.dwui-copy-button:disabled) {
81
+ opacity: 0.4;
82
+ cursor: not-allowed;
83
+ }
84
+
85
+ :where(.dwui-copy-button:focus-visible) {
86
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
87
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
88
+ }
89
+ </style>
@@ -0,0 +1,212 @@
1
+ <!--
2
+ ライトモードとダークモードを切り替えるスイッチ
3
+ 選んだモードを <html> の class(light / dark)に反映し、localStorage に保存する。
4
+ 保存値が無ければ OS の設定に従う。OS の設定が変わったとき、およびモードを切り替えたときは同一サイトのすべてのタブ、ウィンドウが追随する。
5
+ SSRではライトの状態で描画されるため、一瞬ちらつきが発生することがある。
6
+ これを回避するには dark-mode-selector.md に記載されている回避ロジックを実装する。
7
+ -->
8
+ <template>
9
+ <button
10
+ type="button"
11
+ role="switch"
12
+ class="dwui-dark-mode-selector"
13
+ :aria-label="props.label"
14
+ :aria-checked="isDark"
15
+ @click="onClickToggle"
16
+ >
17
+ <span class="dwui-dark-mode-selector-track">
18
+ <span class="dwui-dark-mode-selector-icon-light"><slot name="icon-light"><SvgHighBrightness /></slot></span>
19
+ <span class="dwui-dark-mode-selector-icon-dark"><slot name="icon-dark"><SvgMoonPhases :moon-age="3.85" /></slot></span>
20
+ </span>
21
+ <span class="dwui-dark-mode-selector-handle" />
22
+ </button>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { onBeforeUnmount, onMounted, ref } from 'vue';
27
+ import { SvgHighBrightness, SvgMoonPhases } from '@digitalwalletcorp/vue-svg-icons';
28
+
29
+ type ColorScheme = 'light' | 'dark';
30
+
31
+ export interface Props {
32
+ /** 選んだモードを保存する localStorage のキー */
33
+ storageKey?: string;
34
+ /** スイッチの読み上げ名 */
35
+ label?: string;
36
+ }
37
+ const props = withDefaults(defineProps<Props>(), {
38
+ storageKey: 'dwui-color-scheme',
39
+ label: 'Dark mode'
40
+ });
41
+
42
+ export interface Emits {
43
+ /** マウント時に決まったモードと、その後モードが変わるたびに、新しいモードを発行する */
44
+ (e: 'emit:change', value: ColorScheme): void;
45
+ }
46
+ const emit = defineEmits<Emits>();
47
+
48
+ export interface Slots {
49
+ 'icon-light'?(): unknown;
50
+ 'icon-dark'?(): unknown;
51
+ }
52
+ defineSlots<Slots>();
53
+
54
+ const isDark = ref<boolean>(false);
55
+
56
+ let applied: ColorScheme | null = null;
57
+ let mediaQuery: MediaQueryList | null = null;
58
+
59
+ const isColorScheme = (value: unknown): value is ColorScheme => value === 'light' || value === 'dark';
60
+
61
+ /** OS の設定から得られるモード */
62
+ const osScheme = (): ColorScheme => mediaQuery?.matches ? 'dark' : 'light';
63
+
64
+ /** 保存されているモード。無い場合や localStorage が使えない場合は null */
65
+ const storedScheme = (): ColorScheme | null => {
66
+ try {
67
+ const value = localStorage.getItem(props.storageKey);
68
+ return isColorScheme(value) ? value : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ };
73
+
74
+ /** モードを <html> の class に反映する。変わったときだけ change を発行する */
75
+ const applyScheme = (scheme: ColorScheme): void => {
76
+ const classList = document.documentElement.classList;
77
+ classList.remove(scheme === 'dark' ? 'light' : 'dark');
78
+ classList.add(scheme);
79
+ isDark.value = scheme === 'dark';
80
+ if (applied !== scheme) {
81
+ applied = scheme;
82
+ emit('emit:change', scheme);
83
+ }
84
+ };
85
+
86
+ /** モードを保存して反映する */
87
+ const selectScheme = (scheme: ColorScheme): void => {
88
+ try {
89
+ localStorage.setItem(props.storageKey, scheme);
90
+ } catch {
91
+ // 保存できなくても表示の切り替えは行う
92
+ }
93
+ applyScheme(scheme);
94
+ };
95
+
96
+ const onClickToggle = (): void => {
97
+ selectScheme(isDark.value ? 'light' : 'dark');
98
+ };
99
+
100
+ const onChangeOsScheme = (event: MediaQueryListEvent): void => {
101
+ selectScheme(event.matches ? 'dark' : 'light');
102
+ };
103
+
104
+ // 別のタブで切り替えたとき。保存値が消されたときは OS の設定に戻る
105
+ const onStorage = (event: StorageEvent): void => {
106
+ if (event.key !== props.storageKey) {
107
+ return;
108
+ }
109
+ applyScheme(isColorScheme(event.newValue) ? event.newValue : osScheme());
110
+ };
111
+
112
+ onMounted(() => {
113
+ mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
114
+ applyScheme(storedScheme() ?? osScheme());
115
+ mediaQuery.addEventListener('change', onChangeOsScheme);
116
+ window.addEventListener('storage', onStorage);
117
+ });
118
+
119
+ onBeforeUnmount(() => {
120
+ mediaQuery?.removeEventListener('change', onChangeOsScheme);
121
+ window.removeEventListener('storage', onStorage);
122
+ });
123
+ </script>
124
+
125
+ <style>
126
+ :where(.dwui-dark-mode-selector) {
127
+ position: relative;
128
+ display: inline-block;
129
+ vertical-align: top;
130
+ width: 3.85em;
131
+ height: 1.65em;
132
+ padding: 0;
133
+ border: none;
134
+ border-radius: 0.85em;
135
+ background-color: transparent;
136
+ background-image: none;
137
+ color: inherit;
138
+ font: inherit;
139
+ line-height: 0;
140
+ cursor: pointer;
141
+ }
142
+
143
+ :where(.dwui-dark-mode-selector-track) {
144
+ position: relative;
145
+ display: block;
146
+ height: inherit;
147
+ border-radius: inherit;
148
+ background: var(--dwui-background-dark-mode-selector, azure);
149
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15);
150
+ font-size: 0.85em;
151
+ transition: background 0.15s ease-out;
152
+ }
153
+
154
+ :where(.dwui-dark-mode-selector[aria-checked='true'] > .dwui-dark-mode-selector-track) {
155
+ background: var(--dwui-background-dark-mode-selector-checked, darkslategray);
156
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2);
157
+ }
158
+
159
+ :where(.dwui-dark-mode-selector-icon-light, .dwui-dark-mode-selector-icon-dark) {
160
+ position: absolute;
161
+ top: 50%;
162
+ transform: translateY(-50%);
163
+ display: inline-flex;
164
+ align-items: center;
165
+ line-height: 1;
166
+ transition: opacity 0.15s ease-out;
167
+ }
168
+
169
+ :where(.dwui-dark-mode-selector-icon-light) {
170
+ right: 0.6em;
171
+ }
172
+
173
+ :where(.dwui-dark-mode-selector-icon-dark) {
174
+ left: 0.6em;
175
+ opacity: 0;
176
+ }
177
+
178
+ :where(.dwui-dark-mode-selector[aria-checked='true'] .dwui-dark-mode-selector-icon-light) {
179
+ opacity: 0;
180
+ }
181
+
182
+ :where(.dwui-dark-mode-selector[aria-checked='true'] .dwui-dark-mode-selector-icon-dark) {
183
+ opacity: 1;
184
+ }
185
+
186
+ :where(.dwui-dark-mode-selector-handle) {
187
+ position: absolute;
188
+ top: 0.075em;
189
+ left: 0;
190
+ width: 1.5em;
191
+ height: 1.5em;
192
+ border-radius: 0.75em;
193
+ background: var(--dwui-background-dark-mode-selector-handle, linear-gradient(0.25turn, #888484 0%, #b4abab 50%, #888484 100%));
194
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
195
+ transition: left 0.15s ease-out;
196
+ }
197
+
198
+ :where(.dwui-dark-mode-selector[aria-checked='true'] > .dwui-dark-mode-selector-handle) {
199
+ left: calc(3.85em - 1.5em - 0.075em);
200
+ box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2);
201
+ }
202
+
203
+ :where(.dwui-dark-mode-selector:disabled) {
204
+ opacity: 0.5;
205
+ cursor: not-allowed;
206
+ }
207
+
208
+ :where(.dwui-dark-mode-selector:focus-visible) {
209
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
210
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
211
+ }
212
+ </style>