cclkit4svelte 2.0.7 → 3.1.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.
@@ -1,157 +1,148 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher } from 'svelte';
3
- import { CCLVividColor } from './const/config';
2
+ import { createEventDispatcher, onMount } from 'svelte';
4
3
  import type { ColorVar } from './const/config';
5
- import { vividFor } from './const/colorMap';
6
4
 
7
- const dispatch = createEventDispatcher<{ change: { page: number } }>();
8
-
9
- /** 現在のページ(1始まり) */
10
- export let page: number = 1;
11
- /** 総アイテム数(`pageCount`とどちらか一方を指定) */
12
- export let total: number | undefined = undefined;
13
- /** 1ページあたり件数(`total`指定時に使用) */
14
- export let perPage: number = 10;
15
- /** 総ページ数(`total`の代わりに明示指定可) */
16
- export let pageCount: number | undefined = undefined;
17
-
18
- /** 省略せず両端に常に表示するページ数 */
19
- export let boundaryCount: number = 1;
20
- /** 現在ページの前後に表示するページ数 */
21
- export let siblingCount: number = 1;
22
-
23
- /** 最初/最後ページボタンの表示 */
5
+ export let page: number = 1; // 1-based
6
+ export let total: number | undefined = undefined; // total items
7
+ export let perPage: number = 10; // items per page when using total
8
+ export let pageCount: number | undefined = undefined; // total pages (alternative to total)
9
+ export let boundaryCount: number = 1; // pages maintained at each boundary
10
+ export let siblingCount: number = 1; // pages around current page
24
11
  export let showFirstLast: boolean = true;
25
- /** 前へ/次へボタンの表示 */
26
12
  export let showPrevNext: boolean = true;
27
-
28
- /** 操作不可にする */
29
13
  export let disabled: boolean = false;
14
+ export let accentColor: ColorVar = '--soda-blue';
15
+ export let ariaLabel: string = 'Pagination';
30
16
 
31
- /** アクセントカラー(選択中・ホバー等) */
32
- export let accentColor: ColorVar = CCLVividColor.SODA_BLUE;
17
+ const dispatch = createEventDispatcher<{ change: { page: number } }>();
33
18
 
34
- /** `nav`のARIAラベル */
35
- export let ariaLabel: string = 'Pagination';
19
+ // normalize and clamp helpers
20
+ function getPageCount(): number {
21
+ const count = pageCount ?? (total && perPage ? Math.ceil(total / perPage) : 1);
22
+ return Math.max(1, count || 1);
23
+ }
36
24
 
37
- $: accentFg = vividFor(accentColor as string);
38
- $: styleInline =
39
- `--accent-color: var(${accentColor});` + (accentFg ? ` --accent-fg: var(${accentFg});` : '');
25
+ function clampPage(p: number): number {
26
+ const max = getPageCount();
27
+ return Math.min(Math.max(1, Math.trunc(p || 1)), max);
28
+ }
40
29
 
41
- $: totalPages = Math.max(
42
- 1,
43
- pageCount ?? (total != null ? Math.ceil(total / Math.max(1, perPage)) : 1)
44
- );
45
- $: currentPage = clamp(page, 1, totalPages);
46
- $: pages = getVisiblePages(totalPages, currentPage, siblingCount, boundaryCount);
30
+ $: page = clampPage(page);
47
31
 
48
- function clamp(n: number, min: number, max: number) {
49
- return Math.min(max, Math.max(min, n));
32
+ function goTo(p: number) {
33
+ if (disabled) return;
34
+ const next = clampPage(p);
35
+ if (next !== page) {
36
+ page = next;
37
+ dispatch('change', { page });
38
+ }
50
39
  }
51
40
 
52
- function range(start: number, end: number) {
53
- const arr: number[] = [];
54
- for (let i = start; i <= end; i++) arr.push(i);
55
- return arr;
41
+ type Item = number | 'ellipsis';
42
+
43
+ function range(start: number, end: number): number[] {
44
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
56
45
  }
57
46
 
58
- function getVisiblePages(
59
- total: number,
60
- current: number,
61
- sib: number,
62
- boundary: number
63
- ): (number | 'ellipsis-start' | 'ellipsis-end')[] {
64
- if (total <= 0) return [1];
47
+ function usePagination(): Item[] {
48
+ const totalPages = getPageCount();
65
49
 
66
- const startPages = range(1, Math.min(boundary, total));
67
- const endPages = range(Math.max(total - boundary + 1, boundary + 1), total);
50
+ // total pages small enough to show all
51
+ const totalNumbers = boundaryCount * 2 + siblingCount * 2 + 3; // first + last + current
52
+ const totalBlocks = totalNumbers + 2; // with two ellipses
53
+
54
+ if (totalPages <= totalBlocks) {
55
+ return range(1, totalPages);
56
+ }
57
+
58
+ const startPages = range(1, Math.min(boundaryCount, totalPages));
59
+ const endPages = range(
60
+ Math.max(totalPages - boundaryCount + 1, boundaryCount + 1),
61
+ totalPages
62
+ );
68
63
 
69
- // 兄弟範囲の開始/終了を計算
70
64
  const siblingsStart = Math.max(
71
- Math.min(current - sib, total - boundary - sib * 2 - 1),
72
- boundary + 2
65
+ Math.min(page - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1),
66
+ boundaryCount + 2
73
67
  );
74
68
  const siblingsEnd = Math.min(
75
- Math.max(current + sib, boundary + sib * 2 + 2),
76
- endPages.length > 0 ? endPages[0] - 2 : total - 1
69
+ Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2),
70
+ endPages[0] - 2
77
71
  );
78
72
 
79
- const pages: (number | 'ellipsis-start' | 'ellipsis-end')[] = [];
80
- pages.push(...startPages);
81
-
82
- if (siblingsStart > boundary + 2) {
83
- pages.push('ellipsis-start');
84
- } else if (boundary + 1 < total - boundary) {
85
- pages.push(boundary + 1);
73
+ const items: Item[] = [
74
+ ...startPages,
75
+ siblingsStart > boundaryCount + 2 ? 'ellipsis' : (boundaryCount + 1) as unknown as Item,
76
+ ...range(siblingsStart, siblingsEnd),
77
+ siblingsEnd < totalPages - boundaryCount - 1 ? 'ellipsis' : (totalPages - boundaryCount) as unknown as Item,
78
+ ...endPages
79
+ ];
80
+
81
+ // Clean up potential duplicate numbers from the conditional fallbacks above
82
+ const normalized: Item[] = [];
83
+ let prev: Item | undefined = undefined;
84
+ for (const it of items) {
85
+ if (it === 'ellipsis') {
86
+ if (prev !== 'ellipsis') normalized.push('ellipsis');
87
+ } else {
88
+ if (it !== prev) normalized.push(it);
89
+ }
90
+ prev = it;
86
91
  }
87
-
88
- pages.push(...range(siblingsStart, siblingsEnd));
89
-
90
- if (siblingsEnd < total - boundary - 1) {
91
- pages.push('ellipsis-end');
92
- } else if (total - boundary > boundary) {
93
- pages.push(total - boundary);
94
- }
95
-
96
- pages.push(...endPages);
97
- // ユニーク化と並び順維持
98
- const seen = new Set<string>();
99
- return pages.filter((p) => {
100
- const key = String(p);
101
- if (seen.has(key)) return false;
102
- seen.add(key);
103
- return true;
104
- });
92
+ return normalized;
105
93
  }
106
94
 
107
- function goTo(target: number) {
108
- if (disabled) return;
109
- const next = clamp(target, 1, totalPages);
110
- if (next !== currentPage) {
111
- dispatch('change', { page: next });
95
+ $: items = usePagination();
96
+
97
+ onMount(() => {
98
+ // Ensure initial page is clamped and consumers can react if needed
99
+ const normalized = clampPage(page);
100
+ if (normalized !== page) {
101
+ page = normalized;
102
+ dispatch('change', { page });
112
103
  }
113
- }
114
- //
104
+ });
115
105
  </script>
116
106
 
117
- <nav class="ccl-pagination" aria-label={ariaLabel} style={styleInline}>
118
- <ul class="list" role="list">
107
+ <nav class="ccl-pagination" aria-label={ariaLabel} style={`--ccl-pagination-accent: var(${accentColor});`}>
108
+ <ul class="ccl-pagination__list" role="list">
119
109
  {#if showFirstLast}
120
110
  <li>
121
111
  <button
122
- type="button"
123
- class="item control"
112
+ class="ccl-pagination__control"
124
113
  on:click={() => goTo(1)}
125
- aria-label="Go to first page"
126
- disabled={disabled || currentPage === 1}>«</button
127
- >
114
+ disabled={disabled || page === 1}
115
+ aria-label="go to first page"
116
+ type="button"
117
+ >«</button>
128
118
  </li>
129
119
  {/if}
130
120
  {#if showPrevNext}
131
121
  <li>
132
122
  <button
123
+ class="ccl-pagination__control"
124
+ on:click={() => goTo(page - 1)}
125
+ disabled={disabled || page === 1}
126
+ aria-label="go to previous page"
133
127
  type="button"
134
- class="item control"
135
- on:click={() => goTo(currentPage - 1)}
136
- aria-label="Go to previous page"
137
- disabled={disabled || currentPage === 1}>‹</button
138
- >
128
+ >‹</button>
139
129
  </li>
140
130
  {/if}
141
131
 
142
- {#each pages as p}
143
- {#if p === 'ellipsis-start' || p === 'ellipsis-end'}
144
- <li><span class="item ellipsis" aria-hidden="true">…</span></li>
132
+ {#each items as it}
133
+ {#if it === 'ellipsis'}
134
+ <li class="ccl-pagination__ellipsis" aria-hidden="true">…</li>
145
135
  {:else}
146
136
  <li>
147
137
  <button
138
+ class="ccl-pagination__page"
139
+ class:active={it === page}
140
+ aria-label={`go to page ${it}`}
141
+ aria-current={it === page ? 'page' : undefined}
142
+ disabled={disabled}
143
+ on:click={() => goTo(it)}
148
144
  type="button"
149
- class="item {p === currentPage ? 'active' : ''}"
150
- aria-current={p === currentPage ? 'page' : undefined}
151
- aria-label={`Go to page ${p}`}
152
- {disabled}
153
- on:click={() => goTo(p as number)}>{p}</button
154
- >
145
+ >{it}</button>
155
146
  </li>
156
147
  {/if}
157
148
  {/each}
@@ -159,99 +150,73 @@
159
150
  {#if showPrevNext}
160
151
  <li>
161
152
  <button
153
+ class="ccl-pagination__control"
154
+ on:click={() => goTo(page + 1)}
155
+ disabled={disabled || page === getPageCount()}
156
+ aria-label="go to next page"
162
157
  type="button"
163
- class="item control"
164
- on:click={() => goTo(currentPage + 1)}
165
- aria-label="Go to next page"
166
- disabled={disabled || currentPage === totalPages}>›</button
167
- >
158
+ >›</button>
168
159
  </li>
169
160
  {/if}
170
161
  {#if showFirstLast}
171
162
  <li>
172
163
  <button
164
+ class="ccl-pagination__control"
165
+ on:click={() => goTo(getPageCount())}
166
+ disabled={disabled || page === getPageCount()}
167
+ aria-label="go to last page"
173
168
  type="button"
174
- class="item control"
175
- on:click={() => goTo(totalPages)}
176
- aria-label="Go to last page"
177
- disabled={disabled || currentPage === totalPages}>»</button
178
- >
169
+ >»</button>
179
170
  </li>
180
171
  {/if}
181
172
  </ul>
182
- <div class="sr-only" aria-live="polite">Page {currentPage} of {totalPages}</div>
183
173
  </nav>
184
174
 
185
175
  <style>
186
176
  .ccl-pagination {
177
+ --ccl-pagination-accent: var(--soda-blue);
187
178
  display: inline-block;
188
- --accent-color: var(--soda-blue);
189
- --size: 36px;
190
179
  }
191
- .list {
192
- display: flex;
180
+ .ccl-pagination__list {
181
+ list-style: none;
182
+ display: inline-flex;
193
183
  gap: 6px;
194
184
  padding: 0;
195
185
  margin: 0;
196
- list-style: none;
197
186
  align-items: center;
198
187
  }
199
- .item {
200
- width: var(--size);
201
- min-width: var(--size);
202
- height: var(--size);
188
+ .ccl-pagination__page,
189
+ .ccl-pagination__control {
190
+ appearance: none;
191
+ border: 1px solid var(--wrap-grey, #ccc);
192
+ background: white;
193
+ color: inherit;
194
+ width: 36px;
195
+ height: 36px;
203
196
  padding: 0;
204
197
  border-radius: 50%;
205
- border: 2px solid var(--cloud-grey);
206
- background: white;
207
- color: var(--wrap-grey);
208
- font-size: 14px;
209
- line-height: 1;
198
+ cursor: pointer;
199
+ font: inherit;
210
200
  display: inline-flex;
211
201
  align-items: center;
212
202
  justify-content: center;
213
- cursor: pointer;
214
- transition:
215
- border-color 0.15s,
216
- color 0.15s,
217
- background 0.15s;
218
203
  }
219
- .item.control {
220
- font-size: 18px; /* larger chevrons */
204
+ .ccl-pagination__page.active {
205
+ background: var(--ccl-pagination-accent);
206
+ border-color: var(--ccl-pagination-accent);
207
+ color: white;
221
208
  }
222
- .item.ellipsis {
223
- width: auto;
224
- min-width: auto;
209
+ .ccl-pagination__ellipsis {
225
210
  padding: 0 4px;
226
- border-radius: 0;
227
- background: transparent;
228
- border: none;
229
- cursor: default;
230
- color: var(--wrap-grey);
231
- }
232
- .item:hover:not(:disabled) {
233
- border-color: var(--accent-color);
234
- color: var(--accent-color);
211
+ color: var(--wrap-grey, #888);
235
212
  }
236
- .item.active {
237
- background: var(--accent-color);
238
- border-color: var(--accent-color);
239
- color: var(--accent-fg, #fff);
240
- cursor: default;
241
- }
242
- .item:disabled {
213
+ .ccl-pagination__page:disabled,
214
+ .ccl-pagination__control:disabled {
243
215
  opacity: 0.5;
244
216
  cursor: not-allowed;
245
217
  }
246
- .sr-only {
247
- position: absolute;
248
- width: 1px;
249
- height: 1px;
250
- padding: 0;
251
- margin: -1px;
252
- overflow: hidden;
253
- clip: rect(0, 0, 0, 0);
254
- white-space: nowrap;
255
- border: 0;
218
+ .ccl-pagination__page:not(:disabled):hover,
219
+ .ccl-pagination__control:not(:disabled):hover {
220
+ border-color: var(--ccl-pagination-accent);
256
221
  }
257
222
  </style>
@@ -13,17 +13,17 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
13
13
  z_$$bindings?: Bindings;
14
14
  }
15
15
  declare const Pagination: $$__sveltets_2_IsomorphicComponent<{
16
- /** 現在のページ(1始まり) */ page?: number;
17
- /** 総アイテム数(`pageCount`とどちらか一方を指定) */ total?: number | undefined;
18
- /** 1ページあたり件数(`total`指定時に使用) */ perPage?: number;
19
- /** 総ページ数(`total`の代わりに明示指定可) */ pageCount?: number | undefined;
20
- /** 省略せず両端に常に表示するページ数 */ boundaryCount?: number;
21
- /** 現在ページの前後に表示するページ数 */ siblingCount?: number;
22
- /** 最初/最後ページボタンの表示 */ showFirstLast?: boolean;
23
- /** 前へ/次へボタンの表示 */ showPrevNext?: boolean;
24
- /** 操作不可にする */ disabled?: boolean;
25
- /** アクセントカラー(選択中・ホバー等) */ accentColor?: ColorVar;
26
- /** `nav`のARIAラベル */ ariaLabel?: string;
16
+ page?: number;
17
+ total?: number | undefined;
18
+ perPage?: number;
19
+ pageCount?: number | undefined;
20
+ boundaryCount?: number;
21
+ siblingCount?: number;
22
+ showFirstLast?: boolean;
23
+ showPrevNext?: boolean;
24
+ disabled?: boolean;
25
+ accentColor?: ColorVar;
26
+ ariaLabel?: string;
27
27
  }, {
28
28
  change: CustomEvent<{
29
29
  page: number;
@@ -0,0 +1,115 @@
1
+ <script context="module" lang="ts">
2
+ export type PrismaticAmbientGlowTone =
3
+ | '--peach-pink'
4
+ | '--sugar-blue'
5
+ | '--lemon-yellow'
6
+ | '--akebi-purple';
7
+
8
+ export type PrismaticAmbientGlowSize = 'small' | 'medium' | 'large';
9
+ export type PrismaticAmbientGlowOpacity = 'subtle' | 'standard' | 'strong';
10
+ export type PrismaticAmbientGlowBlur = 'soft' | 'medium' | 'diffuse';
11
+ export type PrismaticAmbientGlowPosition =
12
+ | 'top-left'
13
+ | 'top-right'
14
+ | 'center'
15
+ | 'bottom-left'
16
+ | 'bottom-right';
17
+
18
+ export type PrismaticAmbientGlowProps = {
19
+ tone?: PrismaticAmbientGlowTone;
20
+ size?: PrismaticAmbientGlowSize;
21
+ opacity?: PrismaticAmbientGlowOpacity;
22
+ blur?: PrismaticAmbientGlowBlur;
23
+ position?: PrismaticAmbientGlowPosition;
24
+ };
25
+ </script>
26
+
27
+ <script lang="ts">
28
+ import { CCLPastelColor } from './const/config';
29
+
30
+ const sizes: Record<PrismaticAmbientGlowSize, string> = {
31
+ small: '220px',
32
+ medium: '380px',
33
+ large: '620px'
34
+ };
35
+
36
+ const opacities: Record<PrismaticAmbientGlowOpacity, string> = {
37
+ subtle: '0.28',
38
+ standard: '0.48',
39
+ strong: '0.68'
40
+ };
41
+
42
+ const blurs: Record<PrismaticAmbientGlowBlur, string> = {
43
+ soft: '44px',
44
+ medium: '76px',
45
+ diffuse: '112px'
46
+ };
47
+
48
+ export let tone: PrismaticAmbientGlowTone = CCLPastelColor.PEACH_PINK;
49
+ export let size: PrismaticAmbientGlowSize = 'medium';
50
+ export let opacity: PrismaticAmbientGlowOpacity = 'standard';
51
+ export let blur: PrismaticAmbientGlowBlur = 'medium';
52
+ export let position: PrismaticAmbientGlowPosition = 'center';
53
+
54
+ $: glowSize = sizes[size];
55
+ $: glowOpacity = opacities[opacity];
56
+ $: glowBlur = blurs[blur];
57
+ </script>
58
+
59
+ <!-- The containing block should use position: relative and isolation: isolate. -->
60
+ <span
61
+ class="ambient-glow {position}"
62
+ aria-hidden="true"
63
+ data-testid="prismatic-ambient-glow"
64
+ data-tone={tone}
65
+ data-size={size}
66
+ data-opacity={opacity}
67
+ data-blur={blur}
68
+ data-position={position}
69
+ style="--glow-color: var({tone}); --glow-size: {glowSize}; --glow-opacity: {glowOpacity}; --glow-blur: {glowBlur};"
70
+ ></span>
71
+
72
+ <style>
73
+ .ambient-glow {
74
+ position: absolute;
75
+ z-index: -1;
76
+ display: block;
77
+ width: var(--glow-size);
78
+ height: var(--glow-size);
79
+ border-radius: 50%;
80
+ background: var(--glow-color);
81
+ filter: blur(var(--glow-blur));
82
+ opacity: var(--glow-opacity);
83
+ pointer-events: none;
84
+ }
85
+
86
+ .top-left {
87
+ top: 0;
88
+ left: 0;
89
+ transform: translate(-35%, -35%);
90
+ }
91
+
92
+ .top-right {
93
+ top: 0;
94
+ right: 0;
95
+ transform: translate(35%, -35%);
96
+ }
97
+
98
+ .center {
99
+ top: 50%;
100
+ left: 50%;
101
+ transform: translate(-50%, -50%);
102
+ }
103
+
104
+ .bottom-left {
105
+ bottom: 0;
106
+ left: 0;
107
+ transform: translate(-35%, 35%);
108
+ }
109
+
110
+ .bottom-right {
111
+ right: 0;
112
+ bottom: 0;
113
+ transform: translate(35%, 35%);
114
+ }
115
+ </style>
@@ -0,0 +1,36 @@
1
+ export type PrismaticAmbientGlowTone = '--peach-pink' | '--sugar-blue' | '--lemon-yellow' | '--akebi-purple';
2
+ export type PrismaticAmbientGlowSize = 'small' | 'medium' | 'large';
3
+ export type PrismaticAmbientGlowOpacity = 'subtle' | 'standard' | 'strong';
4
+ export type PrismaticAmbientGlowBlur = 'soft' | 'medium' | 'diffuse';
5
+ export type PrismaticAmbientGlowPosition = 'top-left' | 'top-right' | 'center' | 'bottom-left' | 'bottom-right';
6
+ export type PrismaticAmbientGlowProps = {
7
+ tone?: PrismaticAmbientGlowTone;
8
+ size?: PrismaticAmbientGlowSize;
9
+ opacity?: PrismaticAmbientGlowOpacity;
10
+ blur?: PrismaticAmbientGlowBlur;
11
+ position?: PrismaticAmbientGlowPosition;
12
+ };
13
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
14
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
15
+ $$bindings?: Bindings;
16
+ } & Exports;
17
+ (internal: unknown, props: Props & {
18
+ $$events?: Events;
19
+ $$slots?: Slots;
20
+ }): Exports & {
21
+ $set?: any;
22
+ $on?: any;
23
+ };
24
+ z_$$bindings?: Bindings;
25
+ }
26
+ declare const PrismaticAmbientGlow: $$__sveltets_2_IsomorphicComponent<{
27
+ tone?: PrismaticAmbientGlowTone;
28
+ size?: PrismaticAmbientGlowSize;
29
+ opacity?: PrismaticAmbientGlowOpacity;
30
+ blur?: PrismaticAmbientGlowBlur;
31
+ position?: PrismaticAmbientGlowPosition;
32
+ }, {
33
+ [evt: string]: CustomEvent<any>;
34
+ }, {}, {}, string>;
35
+ type PrismaticAmbientGlow = InstanceType<typeof PrismaticAmbientGlow>;
36
+ export default PrismaticAmbientGlow;