@redvars/peacock 3.3.0 → 3.3.1

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 (105) hide show
  1. package/dist/assets/components.css +1 -1
  2. package/dist/assets/components.css.map +1 -1
  3. package/dist/assets/styles.css +1 -1
  4. package/dist/assets/styles.css.map +1 -1
  5. package/dist/button-group-DA7xoziD.js +292 -0
  6. package/dist/button-group-DA7xoziD.js.map +1 -0
  7. package/dist/button-group.js +6 -107
  8. package/dist/button-group.js.map +1 -1
  9. package/dist/{button-BGFJfbT2.js → button-trIfcqC7.js} +2 -3
  10. package/dist/{button-BGFJfbT2.js.map → button-trIfcqC7.js.map} +1 -1
  11. package/dist/button.js +2 -3
  12. package/dist/button.js.map +1 -1
  13. package/dist/{class-map-DpeNtqCn.js → class-map-hJdvjl-W.js} +9 -3
  14. package/dist/class-map-hJdvjl-W.js.map +1 -0
  15. package/dist/code-editor.js +5 -5
  16. package/dist/code-editor.js.map +1 -1
  17. package/dist/code-highlighter.js +5 -23
  18. package/dist/code-highlighter.js.map +1 -1
  19. package/dist/custom-elements-jsdocs.json +4706 -2471
  20. package/dist/custom-elements.json +3444 -1007
  21. package/dist/index.js +4 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/peacock-loader.js +26 -496
  24. package/dist/peacock-loader.js.map +1 -1
  25. package/dist/src/accordion/accordion-item.d.ts +1 -0
  26. package/dist/src/breadcrumb/breadcrumb/breadcrumb.d.ts +2 -0
  27. package/dist/src/breadcrumb/breadcrumb-item/breadcrumb-item.d.ts +1 -0
  28. package/dist/src/button/button-group/button-group.d.ts +4 -0
  29. package/dist/src/code-editor/code-editor.d.ts +4 -3
  30. package/dist/src/code-highlighter/code-highlighter.d.ts +4 -7
  31. package/dist/src/index.d.ts +4 -0
  32. package/dist/src/menu/index.d.ts +3 -0
  33. package/dist/src/menu/menu/MenuSurfaceController.d.ts +18 -0
  34. package/dist/src/menu/menu/menu.d.ts +54 -12
  35. package/dist/src/menu/menu-item/menu-item.d.ts +12 -5
  36. package/dist/src/menu/sub-menu/sub-menu.d.ts +36 -0
  37. package/dist/src/pagination/index.d.ts +1 -0
  38. package/dist/src/pagination/pagination.d.ts +38 -0
  39. package/dist/src/popover/PopoverController.d.ts +4 -1
  40. package/dist/src/table/index.d.ts +1 -0
  41. package/dist/src/table/table.d.ts +110 -0
  42. package/dist/src/tabs/tab-group.d.ts +4 -0
  43. package/dist/src/tabs/tab-panel.d.ts +1 -0
  44. package/dist/src/tabs/tab.d.ts +1 -0
  45. package/dist/src/tabs/tabs.d.ts +2 -0
  46. package/dist/src/tooltip/tooltip.d.ts +1 -3
  47. package/dist/src/tree-view/index.d.ts +2 -0
  48. package/dist/src/tree-view/tree-node.d.ts +69 -0
  49. package/dist/src/tree-view/tree-view.d.ts +40 -0
  50. package/dist/src/tree-view/wc-tree-view.d.ts +6 -0
  51. package/dist/test/icon.test.d.ts +1 -1
  52. package/dist/test/menu.test.d.ts +1 -0
  53. package/dist/test/sub-menu.test.d.ts +1 -0
  54. package/dist/test/tree-view.test.d.ts +1 -0
  55. package/dist/{slider-Dk9CFWTG.js → tree-view-CLolVlU0.js} +3317 -1180
  56. package/dist/tree-view-CLolVlU0.js.map +1 -0
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +1 -1
  59. package/readme.md +40 -40
  60. package/src/accordion/accordion-item.ts +2 -1
  61. package/src/breadcrumb/breadcrumb/breadcrumb.ts +3 -0
  62. package/src/breadcrumb/breadcrumb-item/breadcrumb-item.ts +1 -0
  63. package/src/button/button-group/button-group.ts +6 -0
  64. package/src/code-editor/code-editor.ts +4 -3
  65. package/src/code-highlighter/code-highlighter.ts +4 -22
  66. package/src/divider/divider.scss +2 -2
  67. package/src/empty-state/empty-state.scss +1 -1
  68. package/src/empty-state/empty-state.ts +1 -1
  69. package/src/index.ts +6 -2
  70. package/src/menu/index.ts +3 -0
  71. package/src/menu/menu/MenuSurfaceController.ts +61 -0
  72. package/src/menu/{menu-list/menu-list.scss → menu/menu.scss} +19 -4
  73. package/src/menu/menu/menu.ts +389 -81
  74. package/src/menu/menu-item/menu-item.ts +115 -36
  75. package/src/menu/sub-menu/sub-menu.scss +7 -0
  76. package/src/menu/sub-menu/sub-menu.ts +243 -0
  77. package/src/pagination/index.ts +1 -0
  78. package/src/pagination/pagination.scss +59 -0
  79. package/src/pagination/pagination.ts +135 -0
  80. package/src/peacock-loader.ts +25 -11
  81. package/src/popover/PopoverController.ts +13 -7
  82. package/src/table/index.ts +1 -0
  83. package/src/table/table.scss +174 -0
  84. package/src/table/table.ts +475 -0
  85. package/src/tabs/tab-group.ts +12 -6
  86. package/src/tabs/tab-panel.ts +1 -0
  87. package/src/tabs/tab.ts +1 -0
  88. package/src/tabs/tabs.scss +6 -5
  89. package/src/tabs/tabs.ts +5 -3
  90. package/src/text/text.css-component.scss +6 -3
  91. package/src/tooltip/tooltip.scss +16 -13
  92. package/src/tooltip/tooltip.ts +7 -9
  93. package/src/tree-view/demo/index.html +57 -0
  94. package/src/tree-view/index.ts +2 -0
  95. package/src/tree-view/tree-node.scss +101 -0
  96. package/src/tree-view/tree-node.ts +268 -0
  97. package/src/tree-view/tree-view.scss +12 -0
  98. package/src/tree-view/tree-view.ts +182 -0
  99. package/src/tree-view/wc-tree-view.ts +9 -0
  100. package/dist/class-map-DpeNtqCn.js.map +0 -1
  101. package/dist/slider-Dk9CFWTG.js.map +0 -1
  102. package/dist/src/menu/menu-list/menu-list.d.ts +0 -22
  103. package/dist/state-8v48Exzh.js +0 -10
  104. package/dist/state-8v48Exzh.js.map +0 -1
  105. package/src/menu/menu-list/menu-list.ts +0 -48
@@ -1,5 +1,5 @@
1
- import { html, LitElement } from 'lit';
2
- import { property, query } from 'lit/decorators.js';
1
+ import { html, LitElement, nothing } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
4
  import styles from './menu-item.scss';
5
5
  import colorStyles from './menu-item-colors.scss';
@@ -8,6 +8,7 @@ import colorStyles from './menu-item-colors.scss';
8
8
  * @label Menu Item
9
9
  * @tag wc-menu-item
10
10
  * @rawTag menu-item
11
+ * @parentRawTag menu-list
11
12
  * @summary An item in a menu list.
12
13
  * @tags navigation
13
14
  *
@@ -21,19 +22,24 @@ export class MenuItem extends LitElement {
21
22
 
22
23
  @property({ type: String }) value = '';
23
24
 
24
- @property({ type: Boolean }) selected = false;
25
+ @property({ type: Boolean, reflect: true }) selected = false;
26
+
27
+ @property({ type: Boolean, attribute: 'keep-open' }) keepOpen = false;
28
+
29
+ @property({ type: Boolean, attribute: 'has-submenu' }) hasSubmenu = false;
30
+
31
+ @property({ type: Boolean, attribute: 'submenu-open' }) submenuOpen = false;
25
32
 
26
33
  /*
27
34
  * Hyperlink to navigate to on click.
28
35
  */
29
36
  @property({ reflect: true }) href?: string;
30
37
 
31
- /**
38
+ /**
32
39
  * Sets or retrieves the window or frame at which to target content.
33
40
  */
34
41
  @property() target: string = '_self';
35
42
 
36
-
37
43
  @property({ type: String, reflect: true }) variant: 'standard' | 'vibrant' =
38
44
  'standard';
39
45
 
@@ -45,65 +51,140 @@ export class MenuItem extends LitElement {
45
51
  if (!this.hasAttribute('role')) {
46
52
  this.setAttribute('role', 'menuitem');
47
53
  }
54
+
55
+ if (!this.hasAttribute('tabindex')) {
56
+ this.tabIndex = -1;
57
+ }
58
+
59
+ this.addEventListener('click', this._handleClick);
60
+ this.addEventListener('keydown', this._handleKeyDown);
61
+ }
62
+
63
+ disconnectedCallback() {
64
+ this.removeEventListener('click', this._handleClick);
65
+ this.removeEventListener('keydown', this._handleKeyDown);
66
+ super.disconnectedCallback();
67
+ }
68
+
69
+ private emitActivate(source: 'click' | 'keydown', key?: string) {
70
+ this.dispatchEvent(
71
+ new CustomEvent('menu-item-activate', {
72
+ bubbles: true,
73
+ composed: true,
74
+ detail: { item: this, source, key },
75
+ }),
76
+ );
77
+ }
78
+
79
+ private requestClose(source: 'click' | 'keydown', key?: string) {
80
+ this.dispatchEvent(
81
+ new CustomEvent('menu-item-request-close', {
82
+ bubbles: true,
83
+ composed: true,
84
+ detail: {
85
+ item: this,
86
+ source,
87
+ key,
88
+ reason: source === 'click' ? 'click-selection' : 'keydown',
89
+ },
90
+ }),
91
+ );
92
+ }
93
+
94
+ private requestSubmenuKey(key: string) {
95
+ this.dispatchEvent(
96
+ new CustomEvent('menu-item-submenu-keydown', {
97
+ bubbles: true,
98
+ composed: true,
99
+ detail: { item: this, key },
100
+ }),
101
+ );
48
102
  }
49
103
 
50
- // Handle keyboard activation (Enter/Space)
51
104
  private _handleKeyDown(e: KeyboardEvent) {
105
+ if (this.disabled) {
106
+ e.preventDefault();
107
+ return;
108
+ }
109
+
110
+ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
111
+ this.requestSubmenuKey(e.key);
112
+ return;
113
+ }
114
+
52
115
  if (e.key === 'Enter' || e.key === ' ') {
53
116
  e.preventDefault();
54
- this.click();
117
+ this.emitActivate('keydown', e.key);
118
+ if (!this.keepOpen) {
119
+ this.requestClose('keydown', e.key);
120
+ }
121
+ }
122
+ }
123
+
124
+ private _handleClick(e: MouseEvent) {
125
+ if (this.disabled) {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ return;
129
+ }
130
+
131
+ this.emitActivate('click');
132
+ if (!this.keepOpen) {
133
+ this.requestClose('click');
55
134
  }
56
135
  }
57
136
 
58
- __isLink() {
137
+ __isLink() {
59
138
  return !!this.href;
60
139
  }
61
140
 
62
- @query('.menu-item') private readonly menuItemElement!: HTMLElement | null;
63
-
64
- override focus() {
65
- this.menuItemElement?.focus();
66
- }
67
-
68
- override blur() {
69
- this.menuItemElement?.blur();
70
- }
141
+ get focusTarget() {
142
+ return this;
143
+ }
71
144
 
72
145
  render() {
73
-
74
146
  const isLink = this.__isLink();
75
147
 
76
148
  const cssClasses = {
77
- 'menu-item': true,
78
- disabled: this.disabled,
79
- selected: this.selected,
80
- };
149
+ 'menu-item': true,
150
+ disabled: this.disabled,
151
+ selected: this.selected,
152
+ };
153
+
154
+ const controls = this.getAttribute('aria-controls');
81
155
 
82
156
  if (isLink) {
83
157
  return html`<a
84
158
  class=${classMap(cssClasses)}
85
159
  href=${this.href}
86
160
  target=${this.target}
161
+ aria-disabled=${String(this.disabled)}
162
+ aria-haspopup=${this.hasSubmenu ? 'menu' : nothing}
163
+ aria-controls=${this.hasSubmenu && controls ? controls : nothing}
164
+ aria-expanded=${this.hasSubmenu ? String(this.submenuOpen) : nothing}
87
165
  >
88
166
  ${this.renderContent()}
89
- </a>
90
- `;
167
+ </a> `;
91
168
  }
92
169
 
93
-
94
170
  return html`<div
95
- class=${classMap(cssClasses)}
96
- tabindex=${!this.disabled ? 0 : -1}
97
- @keydown="${this._handleKeyDown}"
98
- >
99
- ${this.renderContent()}
100
- </div>
101
- `;
171
+ class=${classMap(cssClasses)}
172
+ aria-disabled=${String(this.disabled)}
173
+ aria-haspopup=${this.hasSubmenu ? 'menu' : nothing}
174
+ aria-controls=${this.hasSubmenu && controls ? controls : nothing}
175
+ aria-expanded=${this.hasSubmenu ? String(this.submenuOpen) : nothing}
176
+ >
177
+ ${this.renderContent()}
178
+ </div>`;
102
179
  }
103
180
 
104
181
  renderContent() {
105
182
  return html`
106
- <wc-focus-ring class="focus-ring" .control=${this} element="menuItemElement"></wc-focus-ring>
183
+ <wc-focus-ring
184
+ class="focus-ring"
185
+ .control=${this}
186
+ element="focusTarget"
187
+ ></wc-focus-ring>
107
188
  <div class="background"></div>
108
189
  <wc-ripple class="ripple"></wc-ripple>
109
190
 
@@ -112,9 +193,7 @@ export class MenuItem extends LitElement {
112
193
  <div class="slot-container">
113
194
  <slot></slot>
114
195
  </div>
115
- <slot
116
- name="trailing-supporting-text"
117
- ></slot>
196
+ <slot name="trailing-supporting-text"></slot>
118
197
  </div>
119
198
  `;
120
199
  }
@@ -0,0 +1,7 @@
1
+ @use "../../../scss/mixin";
2
+
3
+ @include mixin.base-styles;
4
+
5
+ :host {
6
+ display: contents;
7
+ }
@@ -0,0 +1,243 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { property, queryAssignedElements } from 'lit/decorators.js';
3
+ import styles from './sub-menu.scss';
4
+ import { MenuItem } from '../menu-item/menu-item.js';
5
+ import { Menu } from '../menu/menu.js';
6
+
7
+ let subMenuIdCounter = 0;
8
+
9
+ /**
10
+ * @label Sub Menu
11
+ * @tag wc-sub-menu
12
+ * @rawTag sub-menu
13
+ * @summary Connects a menu item to a nested menu.
14
+ */
15
+ export class SubMenu extends LitElement {
16
+ static styles = [styles];
17
+
18
+ @property({ type: Number, attribute: 'hover-open-delay' })
19
+ hoverOpenDelay = 120;
20
+
21
+ @property({ type: Number, attribute: 'hover-close-delay' })
22
+ hoverCloseDelay = 180;
23
+
24
+ @property({ type: String, attribute: 'anchor-corner' })
25
+ anchorCorner = 'start-end';
26
+
27
+ @property({ type: String, attribute: 'menu-corner' })
28
+ menuCorner = 'start-start';
29
+
30
+ @queryAssignedElements({ slot: 'item' })
31
+ private readonly _items!: Element[];
32
+
33
+ @queryAssignedElements({ slot: 'menu' })
34
+ private readonly _menus!: Element[];
35
+
36
+ private _openTimeout?: number;
37
+
38
+ private _closeTimeout?: number;
39
+
40
+ private readonly _onChildMenuOpened = () => {
41
+ const { item } = this;
42
+ if (!item) {
43
+ return;
44
+ }
45
+
46
+ item.submenuOpen = true;
47
+ item.setAttribute('aria-expanded', 'true');
48
+ };
49
+
50
+ private readonly _onChildMenuClosed = () => {
51
+ const { item } = this;
52
+ if (!item) {
53
+ return;
54
+ }
55
+
56
+ item.submenuOpen = false;
57
+ item.setAttribute('aria-expanded', 'false');
58
+ };
59
+
60
+ get item(): MenuItem | null {
61
+ const [candidate] = this._items ?? [];
62
+ return candidate instanceof MenuItem ? candidate : null;
63
+ }
64
+
65
+ get menu(): Menu | null {
66
+ const [candidate] = this._menus ?? [];
67
+ return candidate instanceof Menu ? candidate : null;
68
+ }
69
+
70
+ connectedCallback() {
71
+ super.connectedCallback();
72
+ this.addEventListener('mouseenter', this._onMouseEnter);
73
+ this.addEventListener('mouseleave', this._onMouseLeave);
74
+ }
75
+
76
+ disconnectedCallback() {
77
+ const { menu } = this;
78
+ menu?.removeEventListener('opened', this._onChildMenuOpened);
79
+ menu?.removeEventListener('closed', this._onChildMenuClosed);
80
+ this.removeEventListener('mouseenter', this._onMouseEnter);
81
+ this.removeEventListener('mouseleave', this._onMouseLeave);
82
+ window.clearTimeout(this._openTimeout);
83
+ window.clearTimeout(this._closeTimeout);
84
+ super.disconnectedCallback();
85
+ }
86
+
87
+ async show() {
88
+ const { item, menu } = this;
89
+ if (!item || !menu) {
90
+ return;
91
+ }
92
+
93
+ menu.anchorElement = item;
94
+ menu.isSubmenu = true;
95
+ menu.show();
96
+
97
+ item.hasSubmenu = true;
98
+ item.submenuOpen = true;
99
+ item.setAttribute('aria-expanded', 'true');
100
+ }
101
+
102
+ async close() {
103
+ const { item, menu } = this;
104
+ if (!item || !menu) {
105
+ return;
106
+ }
107
+
108
+ menu.close({ kind: 'programmatic' });
109
+ item.submenuOpen = false;
110
+ item.setAttribute('aria-expanded', 'false');
111
+ }
112
+
113
+ render() {
114
+ return html`
115
+ <slot
116
+ name="item"
117
+ @slotchange=${this._onSlotChange}
118
+ @click=${this._onItemClick}
119
+ @keydown=${this._onItemKeyDown}
120
+ ></slot>
121
+ <slot
122
+ name="menu"
123
+ @slotchange=${this._onSlotChange}
124
+ @close-menu=${this._onCloseMenu}
125
+ @keydown=${this._onMenuKeyDown}
126
+ ></slot>
127
+ `;
128
+ }
129
+
130
+ private _onSlotChange = () => {
131
+ const { item, menu } = this;
132
+ if (!item || !menu) {
133
+ return;
134
+ }
135
+
136
+ if (!menu.id) {
137
+ subMenuIdCounter += 1;
138
+ menu.id = `wc-sub-menu-${subMenuIdCounter}`;
139
+ }
140
+
141
+ item.keepOpen = true;
142
+ item.hasSubmenu = true;
143
+ item.submenuOpen = menu.open;
144
+ item.setAttribute('aria-haspopup', 'menu');
145
+ item.setAttribute('aria-expanded', String(menu.open));
146
+ item.setAttribute('aria-controls', menu.id);
147
+
148
+ menu.removeEventListener('opened', this._onChildMenuOpened);
149
+ menu.removeEventListener('closed', this._onChildMenuClosed);
150
+ menu.addEventListener('opened', this._onChildMenuOpened);
151
+ menu.addEventListener('closed', this._onChildMenuClosed);
152
+
153
+ menu.isSubmenu = true;
154
+ menu.anchorElement = item;
155
+ menu.placement =
156
+ getComputedStyle(this).direction === 'rtl' ? 'left-start' : 'right-start';
157
+ menu.offset = 4;
158
+ };
159
+
160
+ private _onItemClick = () => {
161
+ if (this.menu?.open) {
162
+ this.close();
163
+ return;
164
+ }
165
+
166
+ this.show();
167
+ };
168
+
169
+ private _onItemKeyDown = async (event: KeyboardEvent) => {
170
+ const isRtl = getComputedStyle(this).direction === 'rtl';
171
+ const arrowEnter = isRtl ? 'ArrowLeft' : 'ArrowRight';
172
+
173
+ const shouldOpen =
174
+ event.key === arrowEnter || event.key === 'Enter' || event.key === ' ';
175
+
176
+ if (!shouldOpen) {
177
+ return;
178
+ }
179
+
180
+ event.preventDefault();
181
+ if (event.key === arrowEnter) {
182
+ event.stopPropagation();
183
+ }
184
+
185
+ await this.show();
186
+ const firstItem = this.menu?.items.find(menuItem => !menuItem.disabled);
187
+ if (firstItem) {
188
+ firstItem.tabIndex = 0;
189
+ firstItem.focus();
190
+ }
191
+ };
192
+
193
+ private _onMenuKeyDown = async (event: KeyboardEvent) => {
194
+ const isRtl = getComputedStyle(this).direction === 'rtl';
195
+ const arrowExit = isRtl ? 'ArrowRight' : 'ArrowLeft';
196
+
197
+ if (event.key !== 'Escape' && event.key !== arrowExit) {
198
+ return;
199
+ }
200
+
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+
204
+ await this.close();
205
+
206
+ const { item } = this;
207
+ if (item) {
208
+ item.tabIndex = 0;
209
+ item.focus();
210
+ }
211
+ };
212
+
213
+ private _onCloseMenu = async (
214
+ event: CustomEvent<{ reason?: { kind?: string; key?: string } }>,
215
+ ) => {
216
+ const { reason } = event.detail ?? {};
217
+ const { key } = reason ?? {};
218
+ if (reason?.kind === 'keydown' && key === 'Escape') {
219
+ event.stopPropagation();
220
+ await this.close();
221
+ this.item?.focus();
222
+ }
223
+ };
224
+
225
+ private _onMouseEnter = () => {
226
+ window.clearTimeout(this._closeTimeout);
227
+ this._openTimeout = window.setTimeout(() => {
228
+ this.show();
229
+ }, this.hoverOpenDelay);
230
+ };
231
+
232
+ private _onMouseLeave = (event: MouseEvent) => {
233
+ const { relatedTarget: related } = event;
234
+ if (related instanceof Node && this.contains(related)) {
235
+ return;
236
+ }
237
+
238
+ window.clearTimeout(this._openTimeout);
239
+ this._closeTimeout = window.setTimeout(() => {
240
+ this.close();
241
+ }, this.hoverCloseDelay);
242
+ };
243
+ }
@@ -0,0 +1 @@
1
+ export { Pagination } from './pagination.js';
@@ -0,0 +1,59 @@
1
+ @use '../../scss/mixin';
2
+
3
+ @include mixin.base-styles;
4
+
5
+ :host {
6
+ display: block;
7
+ }
8
+
9
+ .pagination {
10
+ background: var(--color-surface, #fff);
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ .page-sizes-select {
15
+ margin-inline-start: var(--spacing-100, 0.5rem);
16
+ }
17
+
18
+ .page-size-label {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: var(--spacing-100, 0.5rem);
22
+ @include mixin.get-typography-not-important('body-medium');
23
+ color: var(--color-on-surface-variant);
24
+ white-space: nowrap;
25
+ }
26
+
27
+ .page-size-select {
28
+ border: 1px solid var(--color-outline-variant);
29
+ background: var(--color-surface, #fff);
30
+ color: var(--color-on-surface);
31
+ padding: var(--spacing-050, 0.25rem) var(--spacing-100, 0.5rem);
32
+ cursor: pointer;
33
+ outline: none;
34
+ height: 2.5rem;
35
+
36
+ @include mixin.get-typography-not-important('body-medium');
37
+
38
+ &:focus {
39
+ outline: 2px solid var(--color-primary);
40
+ }
41
+ }
42
+
43
+ .pagination-item-count {
44
+ margin-inline-start: var(--spacing-150, 0.75rem);
45
+ flex: 1;
46
+ display: flex;
47
+ align-items: center;
48
+ }
49
+
50
+ .pagination-text {
51
+ @include mixin.get-typography-not-important('body-medium');
52
+ color: var(--color-on-surface-variant);
53
+ }
54
+
55
+ .arrows {
56
+ --border-radius: 0;
57
+ --button-height: calc(2.5rem - 2px);
58
+ }
59
+ }
@@ -0,0 +1,135 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
+ import styles from './pagination.scss';
4
+
5
+ const DEFAULT_PAGE_SIZES = [10, 25, 50, 100];
6
+
7
+ /**
8
+ * @label Pagination
9
+ * @tag wc-pagination
10
+ * @rawTag pagination
11
+ * @summary A pagination control with page size selector, item count display, and previous/next navigation.
12
+ * @overview
13
+ * <p>The pagination component provides controls for navigating through paged data sets.</p>
14
+ *
15
+ * @fires {CustomEvent} page - Dispatched when the page or page size changes. Detail: `{ page, pageSize }`.
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <wc-pagination page="1" page-size="10" total-items="100"></wc-pagination>
20
+ * ```
21
+ * @tags navigation, data
22
+ */
23
+ export class Pagination extends LitElement {
24
+ static styles = [styles];
25
+
26
+ /**
27
+ * The current page number (1-based). Defaults to `1`.
28
+ */
29
+ @property({ type: Number })
30
+ page: number = 1;
31
+
32
+ /**
33
+ * The number of rows per page. Defaults to `10`.
34
+ */
35
+ @property({ type: Number, attribute: 'page-size' })
36
+ pageSize: number = 10;
37
+
38
+ /**
39
+ * Total number of items.
40
+ */
41
+ @property({ type: Number, attribute: 'total-items' })
42
+ totalItems: number = 0;
43
+
44
+ /**
45
+ * Supported page size options.
46
+ */
47
+ @property({ type: Array, attribute: 'page-sizes' })
48
+ pageSizes: number[] = DEFAULT_PAGE_SIZES;
49
+
50
+ private dispatchPageEvent() {
51
+ this.dispatchEvent(
52
+ new CustomEvent('page', {
53
+ detail: { page: this.page, pageSize: this.pageSize },
54
+ bubbles: true,
55
+ composed: true,
56
+ }),
57
+ );
58
+ }
59
+
60
+ render() {
61
+ const startItem = this.pageSize * (this.page - 1);
62
+ const endItem = Math.min(this.pageSize * this.page, this.totalItems);
63
+ const isFirstPage = this.page === 1;
64
+ const isLastPage = this.pageSize * this.page >= this.totalItems;
65
+
66
+ return html`
67
+ <div class="pagination">
68
+ <div class="page-sizes-select">
69
+ <label class="page-size-label">
70
+ Items per page:
71
+ <select
72
+ class="page-size-select"
73
+ .value=${String(this.pageSize)}
74
+ @change=${(e: Event) => {
75
+ this.pageSize = parseInt(
76
+ (e.target as HTMLSelectElement).value,
77
+ 10,
78
+ );
79
+ this.page = 1;
80
+ this.dispatchPageEvent();
81
+ }}
82
+ >
83
+ ${this.pageSizes.map(
84
+ size => html`
85
+ <option value=${size} ?selected=${this.pageSize === size}>
86
+ ${size}
87
+ </option>
88
+ `,
89
+ )}
90
+ </select>
91
+ </label>
92
+ </div>
93
+ <div class="pagination-item-count">
94
+ <span class="pagination-text">
95
+ ${startItem} - ${endItem} of ${this.totalItems} items
96
+ </span>
97
+ </div>
98
+ <div class="pagination-right">
99
+ <div class="table-footer-right-content">
100
+ <div class="table-footer-right-content-pagination">
101
+ <wc-button
102
+ class="arrows"
103
+ color="secondary"
104
+ variant="text"
105
+ ?disabled=${isFirstPage}
106
+ @click=${() => {
107
+ if (!isFirstPage) {
108
+ this.page -= 1;
109
+ this.dispatchPageEvent();
110
+ }
111
+ }}
112
+ >
113
+ <wc-icon slot="icon" name="arrow--left"></wc-icon>
114
+ </wc-button>
115
+ <wc-button
116
+ color="secondary"
117
+ variant="text"
118
+ class="arrows"
119
+ ?disabled=${isLastPage}
120
+ @click=${() => {
121
+ if (!isLastPage) {
122
+ this.page += 1;
123
+ this.dispatchPageEvent();
124
+ }
125
+ }}
126
+ >
127
+ <wc-icon slot="icon" name="arrow--right"></wc-icon>
128
+ </wc-button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ `;
134
+ }
135
+ }