@torpor/ui 0.3.6 → 0.4.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 (89) hide show
  1. package/dist/Accordion/Accordion.torp +26 -25
  2. package/dist/Accordion/AccordionItem.torp +3 -3
  3. package/dist/Accordion/AccordionTypes.ts +5 -5
  4. package/dist/Accordion/index.d.ts +2 -2
  5. package/dist/Calendar/CalendarTypes.ts +1 -1
  6. package/dist/Charts/BarChart.torp +159 -0
  7. package/dist/Charts/DataSeries.ts +5 -0
  8. package/dist/Charts/GridLines.torp +65 -0
  9. package/dist/Charts/LineChart.torp +196 -0
  10. package/dist/Charts/PieChart.torp +145 -0
  11. package/dist/Charts/ScatterChart.torp +215 -0
  12. package/dist/Charts/SparkLine.torp +71 -0
  13. package/dist/Charts/XAxis.torp +66 -0
  14. package/dist/Charts/YAxis.torp +47 -0
  15. package/dist/Charts/index.d.ts +220 -0
  16. package/dist/Charts/index.js +34 -0
  17. package/dist/Charts/index.js.map +1 -0
  18. package/dist/Contextual/Contextual.torp +8 -3
  19. package/dist/Contextual/ContextualTypes.ts +1 -1
  20. package/dist/Dialog/Dialog.torp +12 -16
  21. package/dist/Dialog/DialogButton.torp +3 -0
  22. package/dist/Dialog/DialogTypes.ts +2 -2
  23. package/dist/Dialog/index.d.ts +2 -0
  24. package/dist/Disclosure/Disclosure.torp +3 -3
  25. package/dist/Disclosure/DisclosureTypes.ts +1 -1
  26. package/dist/Drawer/Drawer.torp +2 -2
  27. package/dist/Drawer/DrawerTypes.ts +1 -1
  28. package/dist/Form/CheckBox.torp +1 -2
  29. package/dist/Form/Input.torp +1 -1
  30. package/dist/Form/MessageSummary.torp +38 -0
  31. package/dist/Form/TextArea.torp +3 -0
  32. package/dist/Form/index.d.ts +25 -1
  33. package/dist/Form/index.js +4 -0
  34. package/dist/ListBox/ListBox.torp +271 -0
  35. package/dist/ListBox/ListBoxItem.torp +107 -0
  36. package/dist/ListBox/ListBoxSeparator.torp +26 -0
  37. package/dist/ListBox/ListBoxTypes.ts +23 -0
  38. package/dist/ListBox/index.d.ts +97 -0
  39. package/dist/ListBox/index.js +14 -0
  40. package/dist/ListBox/index.js.map +1 -0
  41. package/dist/Menu/Menu.torp +19 -14
  42. package/dist/Menu/MenuTypes.ts +5 -4
  43. package/dist/MenuBar/MenuBarItem.torp +5 -8
  44. package/dist/MenuBar/MenuBarTypes.ts +2 -2
  45. package/dist/Modal/Modal.torp +7 -2
  46. package/dist/Modal/ModalTypes.ts +1 -1
  47. package/dist/NavMenu/NavMenu.torp +7 -0
  48. package/dist/NavMenu/NavMenuButton.torp +5 -2
  49. package/dist/NavMenu/NavMenuPopout.torp +1 -1
  50. package/dist/NavMenu/{NavMenuPopoutButton.torp → NavMenuPopoutTrigger.torp} +4 -4
  51. package/dist/NavMenu/NavMenuSeparator.torp +2 -1
  52. package/dist/NavMenu/NavMenuTypes.ts +4 -3
  53. package/dist/NavMenu/index.d.ts +22 -18
  54. package/dist/NavMenu/index.js +4 -4
  55. package/dist/Pagination/Pagination.torp +145 -0
  56. package/dist/Pagination/PaginationItem.torp +100 -0
  57. package/dist/Pagination/PaginationTypes.ts +25 -0
  58. package/dist/Pagination/buildPages.ts +57 -0
  59. package/dist/Pagination/index.d.ts +53 -0
  60. package/dist/Pagination/index.js +40 -0
  61. package/dist/Pagination/index.js.map +1 -0
  62. package/dist/Popover/Popover.torp +8 -3
  63. package/dist/Popover/PopoverTypes.ts +1 -1
  64. package/dist/SelectBox/SelectBox.torp +136 -0
  65. package/dist/SelectBox/SelectBoxContent.torp +81 -0
  66. package/dist/SelectBox/SelectBoxInput.torp +95 -0
  67. package/dist/SelectBox/SelectBoxTrigger.torp +41 -0
  68. package/dist/SelectBox/SelectBoxTypes.ts +18 -0
  69. package/dist/SelectBox/index.d.ts +127 -0
  70. package/dist/SelectBox/index.js +18 -0
  71. package/dist/SelectBox/index.js.map +1 -0
  72. package/dist/TabGroup/TabGroup.torp +3 -3
  73. package/dist/TabGroup/TabGroupTypes.ts +2 -2
  74. package/dist/ToolBar/ToolBar.torp +198 -0
  75. package/dist/ToolBar/ToolBarButton.torp +57 -0
  76. package/dist/ToolBar/ToolBarElement.torp +55 -0
  77. package/dist/ToolBar/ToolBarPopout.torp +97 -0
  78. package/dist/ToolBar/ToolBarPopoutContent.torp +92 -0
  79. package/dist/ToolBar/ToolBarPopoutTrigger.torp +138 -0
  80. package/dist/ToolBar/ToolBarSeparator.torp +25 -0
  81. package/dist/ToolBar/ToolBarTypes.ts +35 -0
  82. package/dist/ToolBar/index.d.ts +212 -0
  83. package/dist/ToolBar/index.js +30 -0
  84. package/dist/ToolBar/index.js.map +1 -0
  85. package/dist/metafile-esm.json +1 -1
  86. package/dist/utils/PopoutTypes.ts +35 -0
  87. package/dist/utils/chartColors.ts +17 -0
  88. package/dist/utils/chartUtils.ts +71 -0
  89. package/package.json +22 -2
@@ -1,5 +1,6 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
2
  import { ModalContextName, type ModalContext, type ModalState } from "./ModalTypes";
3
+ import { PopoutContextName, type PopoutContext } from "../utils/PopoutTypes";
3
4
 
4
5
  interface ModalProps {
5
6
  /** An ID for the root element */
@@ -60,6 +61,10 @@ export default function Modal($props: ModalProps | undefined) {
60
61
  };
61
62
  $context[ModalContextName] = context;
62
63
 
64
+ // Set the context to pass down to items
65
+ let popoutContext: PopoutContext = context;
66
+ $context[PopoutContextName] = popoutContext;
67
+
63
68
  // Dispatch the toggle event when the modal is opened or closed
64
69
  let firstToggle = true;
65
70
  $run(() => {
@@ -83,11 +88,11 @@ export default function Modal($props: ModalProps | undefined) {
83
88
  // Get the button's result
84
89
  switch (type) {
85
90
  case "confirm": {
86
- result = value || true;
91
+ result = value ?? true;
87
92
  break;
88
93
  }
89
94
  case "cancel": {
90
- result = value || false;
95
+ result = value ?? false;
91
96
  break;
92
97
  }
93
98
  default: {
@@ -1,4 +1,4 @@
1
- export const ModalContextName = "Modal";
1
+ export const ModalContextName: unique symbol = Symbol.for("torp.Modal");
2
2
 
3
3
  /** The shared context for modal components (Contextual, Prompt and Modal) */
4
4
  export interface ModalContext {
@@ -51,6 +51,7 @@ export default function NavMenu($props: NavMenuProps | undefined) {
51
51
  // Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
52
52
  if (popoutContext) {
53
53
  popoutContext.focusFirstElement = focusFirstElement;
54
+ popoutContext.focusLastElement = focusLastElement;
54
55
  }
55
56
 
56
57
  // Set the context to pass down to items
@@ -74,6 +75,12 @@ export default function NavMenu($props: NavMenuProps | undefined) {
74
75
  }
75
76
  }
76
77
 
78
+ function focusLastElement() {
79
+ if (itemStates.length > 0) {
80
+ itemStates.at(-1)!.setFocused();
81
+ }
82
+ }
83
+
77
84
  function registerItem(setFocused: () => void) {
78
85
  // Add the item's state to the array and return its index
79
86
  const newItemState: ItemState = {
@@ -10,6 +10,10 @@ interface NavMenuButtonProps {
10
10
  style?: StyleValue,
11
11
  /** Whether this button is disabled */
12
12
  disabled?: boolean,
13
+ /** Raised when the button is clicked */
14
+ onclick?: (e: MouseEvent) => void,
15
+ /** Raised when a key is pressed down */
16
+ onkeydown?: (e: KeyboardEvent) => void,
13
17
  }
14
18
 
15
19
  /**
@@ -43,9 +47,8 @@ export default function NavMenuButton($props: NavMenuButtonProps | undefined) {
43
47
  role="menuitem"
44
48
  aria-disabled={$props?.disabled ? "true" : undefined}
45
49
  data-disabled={$props?.disabled ? "true" : undefined}
46
- onkeydown
47
50
  onkeydown={handleItemKey}
48
- onclick
51
+ {$props?.onclick}
49
52
  onfocus={() => handleItemFocus(index)}
50
53
  &ref={button}
51
54
  >
@@ -25,7 +25,7 @@ interface NavMenuPopoutProps {
25
25
  *
26
26
  * ```
27
27
  * <NavMenuPopout>
28
- * <NavMenuPopoutButton />
28
+ * <NavMenuPopoutTrigger />
29
29
  * <NavMenuPopoutContent />
30
30
  * </NavMenuPopout>
31
31
  * ```
@@ -6,7 +6,7 @@ import {
6
6
  type NavMenuPopoutContext,
7
7
  } from "./NavMenuTypes";
8
8
 
9
- interface NavMenuPopoutButtonProps {
9
+ interface NavMenuPopoutTriggerProps {
10
10
  /** An ID for the root element */
11
11
  id?: string;
12
12
  /** Classes for the root element */
@@ -23,7 +23,7 @@ interface NavMenuPopoutButtonProps {
23
23
  * A button that is placed in a NavMenu and signals a response by a user.
24
24
  *
25
25
  */
26
- export default function NavMenuPopoutButton($props: NavMenuPopoutButtonProps | undefined) {
26
+ export default function NavMenuPopoutTrigger($props: NavMenuPopoutTriggerProps | undefined) {
27
27
  let defaultHoverDelay = 500;
28
28
 
29
29
  let button: HTMLButtonElement;
@@ -32,14 +32,14 @@ export default function NavMenuPopoutButton($props: NavMenuPopoutButtonProps | u
32
32
  // Get stuff out of the NavMenuContext
33
33
  const menuContext = $context[NavMenuContextName] as NavMenuContext;
34
34
  if (!menuContext) {
35
- throw new Error("NavMenuPopoutButton must be contained within a NavMenu");
35
+ throw new Error("NavMenuPopoutTrigger must be contained within a NavMenu");
36
36
  }
37
37
  const { registerItem, handleItemKey, handleItemFocus } = menuContext;
38
38
 
39
39
  // Get stuff out of the NavMenuPopoutContext
40
40
  const popoutContext = $context[NavMenuPopoutContextName] as NavMenuPopoutContext;
41
41
  if (!popoutContext) {
42
- throw new Error("NavMenuPopoutButton must be contained within a NavMenuPopout");
42
+ throw new Error("NavMenuPopoutTrigger must be contained within a NavMenuPopout");
43
43
  }
44
44
  let $state = popoutContext.state;
45
45
 
@@ -16,7 +16,8 @@ export default function NavMenuSeparator($props: NavMenuSeparatorProps | undefin
16
16
  @render {
17
17
  <div
18
18
  {$props?.id}
19
- class={`torp-nav-menu-separator ${$props?.class ?? ""}`.trim()} role="separator"
19
+ class={["torp-nav-menu-separator", $props?.class]}
20
+ role="separator"
20
21
  >
21
22
  <slot />
22
23
  </div>
@@ -1,6 +1,6 @@
1
- export const NavMenuContextName = "NavMenu";
2
- export const NavMenuPopoutContextName = "NavMenuPopout";
3
- export const NavMenuGroupContextName = "NavMenuGroup";
1
+ export const NavMenuContextName: unique symbol = Symbol.for("torp.NavMenu");
2
+ export const NavMenuPopoutContextName: unique symbol = Symbol.for("torp.NavMenuPopout");
3
+ export const NavMenuGroupContextName: unique symbol = Symbol.for("torp.NavMenuGroup");
4
4
 
5
5
  export interface NavMenuContext {
6
6
  /** Called from a NavMenuLink or NavMenuButton when its button is focused */
@@ -19,6 +19,7 @@ export interface NavMenuPopoutContext {
19
19
  };
20
20
  anchorElement?: HTMLElement;
21
21
  focusFirstElement?: () => void;
22
+ focusLastElement?: () => void;
22
23
  }
23
24
 
24
25
  export interface NavMenuGroupContext {
@@ -49,6 +49,10 @@ interface NavMenuButtonProps {
49
49
  style?: StyleValue,
50
50
  /** Whether this button is disabled */
51
51
  disabled?: boolean,
52
+ /** Raised when the button is clicked */
53
+ onclick?: (e: MouseEvent) => void,
54
+ /** Raised when a key is pressed down */
55
+ onkeydown?: (e: KeyboardEvent) => void,
52
56
  }
53
57
 
54
58
  /**
@@ -193,7 +197,7 @@ interface NavMenuPopoutProps {
193
197
  *
194
198
  * ```
195
199
  * <NavMenuPopout>
196
- * <NavMenuPopoutButton />
200
+ * <NavMenuPopoutTrigger />
197
201
  * <NavMenuPopoutContent />
198
202
  * </NavMenuPopout>
199
203
  * ```
@@ -208,54 +212,54 @@ declare function NavMenuPopout(
208
212
 
209
213
 
210
214
 
211
- interface NavMenuPopoutButtonProps {
215
+ interface NavMenuPopoutContentProps {
212
216
  /** An ID for the root element */
213
217
  id?: string;
214
218
  /** Classes for the root element */
215
219
  class?: ClassValue,
216
220
  /** Styles for the root element */
217
221
  style?: StyleValue,
218
- /** The delay before showing the popout, in milliseconds */
219
- hoverDelay?: number,
220
- /** Whether this button is disabled */
221
- disabled?: boolean,
222
+ /** Where the popout will be shown, relative to the popout button */
223
+ side?: "top" | "right" | "bottom" | "left",
224
+ /** How the popout will be aligned, relative to the popout button */
225
+ alignment?: "start" | "center" | "end",
222
226
  }
223
227
 
224
228
  /**
225
- * A button that is placed in a NavMenu and signals a response by a user.
229
+ * The content that is displayed for a NavMenuPopout when it is open.
226
230
  *
227
231
  */
228
- declare function NavMenuPopoutButton(
232
+ declare function NavMenuPopoutContent(
229
233
  $parent: ParentNode,
230
234
  $anchor: Node | null,
231
- $props: NavMenuPopoutButtonProps | undefined,
235
+ $props: NavMenuPopoutContentProps | undefined,
232
236
  $context?: Record<PropertyKey, any>,
233
237
  $slots?: Record<string, SlotRender>,
234
238
  ): void;
235
239
 
236
240
 
237
241
 
238
- interface NavMenuPopoutContentProps {
242
+ interface NavMenuPopoutTriggerProps {
239
243
  /** An ID for the root element */
240
244
  id?: string;
241
245
  /** Classes for the root element */
242
246
  class?: ClassValue,
243
247
  /** Styles for the root element */
244
248
  style?: StyleValue,
245
- /** Where the popout will be shown, relative to the popout button */
246
- side?: "top" | "right" | "bottom" | "left",
247
- /** How the popout will be aligned, relative to the popout button */
248
- alignment?: "start" | "center" | "end",
249
+ /** The delay before showing the popout, in milliseconds */
250
+ hoverDelay?: number,
251
+ /** Whether this button is disabled */
252
+ disabled?: boolean,
249
253
  }
250
254
 
251
255
  /**
252
- * The content that is displayed for a NavMenuPopout when it is open.
256
+ * A button that is placed in a NavMenu and signals a response by a user.
253
257
  *
254
258
  */
255
- declare function NavMenuPopoutContent(
259
+ declare function NavMenuPopoutTrigger(
256
260
  $parent: ParentNode,
257
261
  $anchor: Node | null,
258
- $props: NavMenuPopoutContentProps | undefined,
262
+ $props: NavMenuPopoutTriggerProps | undefined,
259
263
  $context?: Record<PropertyKey, any>,
260
264
  $slots?: Record<string, SlotRender>,
261
265
  ): void;
@@ -282,4 +286,4 @@ declare function NavMenuSeparator(
282
286
  $slots?: Record<string, SlotRender>,
283
287
  ): void;
284
288
 
285
- export { NavMenu, NavMenuButton, NavMenuElement, NavMenuGroup, NavMenuGroupHeader, NavMenuLink, NavMenuPopout, NavMenuPopoutButton, NavMenuPopoutContent, NavMenuSeparator };
289
+ export { NavMenu, NavMenuButton, NavMenuElement, NavMenuGroup, NavMenuGroupHeader, NavMenuLink, NavMenuPopout, NavMenuPopoutContent, NavMenuPopoutTrigger, NavMenuSeparator };
@@ -19,12 +19,12 @@ import NavMenuLink_default from "./NavMenuLink.torp";
19
19
  // src/NavMenu/NavMenuPopout.torp
20
20
  import NavMenuPopout_default from "./NavMenuPopout.torp";
21
21
 
22
- // src/NavMenu/NavMenuPopoutButton.torp
23
- import NavMenuPopoutButton_default from "./NavMenuPopoutButton.torp";
24
-
25
22
  // src/NavMenu/NavMenuPopoutContent.torp
26
23
  import NavMenuPopoutContent_default from "./NavMenuPopoutContent.torp";
27
24
 
25
+ // src/NavMenu/NavMenuPopoutTrigger.torp
26
+ import NavMenuPopoutTrigger_default from "./NavMenuPopoutTrigger.torp";
27
+
28
28
  // src/NavMenu/NavMenuSeparator.torp
29
29
  import NavMenuSeparator_default from "./NavMenuSeparator.torp";
30
30
  export {
@@ -35,8 +35,8 @@ export {
35
35
  NavMenuGroupHeader_default as NavMenuGroupHeader,
36
36
  NavMenuLink_default as NavMenuLink,
37
37
  NavMenuPopout_default as NavMenuPopout,
38
- NavMenuPopoutButton_default as NavMenuPopoutButton,
39
38
  NavMenuPopoutContent_default as NavMenuPopoutContent,
39
+ NavMenuPopoutTrigger_default as NavMenuPopoutTrigger,
40
40
  NavMenuSeparator_default as NavMenuSeparator
41
41
  };
42
42
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,145 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { PaginationContextName, type PaginationContext, type ItemState, type PageNumber } from "./PaginationTypes";
3
+
4
+ interface PaginationProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue,
9
+ /** Styles for the root element */
10
+ style?: StyleValue,
11
+ /** The total number of pages (or items if the page size is greater than 1) */
12
+ count: number,
13
+ /** The active page number */
14
+ number: number,
15
+ }
16
+
17
+ /**
18
+ * Buttons with page numbers that can be used to move between pages.
19
+ */
20
+ export default function Pagination($props: PaginationProps) {
21
+ // TODO: Should we (by default) create PaginationItems?
22
+
23
+ // TODO: Do we actually need ItemStates when we're creating the item collection ourselves??
24
+ // A collection containing the active state and some functions for each item
25
+ let itemStates: ItemState[] = [];
26
+
27
+ // Put stuff into a PaginationContext
28
+ const context: PaginationContext = {
29
+ registerItem,
30
+ removeItem,
31
+ toggleItem,
32
+ handleItemKey
33
+ };
34
+ $context[PaginationContextName] = context;
35
+
36
+ function registerItem(
37
+ target: PageNumber,
38
+ setActive: (value: boolean) => void,
39
+ setFocused: () => void
40
+ ) {
41
+ // Add the item's state to the array
42
+ const newItemState: ItemState = {
43
+ target,
44
+ active: false,
45
+ setActive,
46
+ setFocused
47
+ };
48
+ itemStates.push(newItemState);
49
+ }
50
+
51
+ function removeItem(target: PageNumber) {
52
+ // Remove the item
53
+ const index = itemStates.findIndex((i) => i.target === target);
54
+ if (index != -1) {
55
+ itemStates.splice(index, 1);
56
+ }
57
+ }
58
+
59
+ function toggleItem(target: PageNumber) {
60
+ switch (target) {
61
+ case "start": {
62
+ $props.number = 1;
63
+ break;
64
+ }
65
+ case "previous": {
66
+ $props.number = Math.max(1, $props.number - 1);
67
+ break;
68
+ }
69
+ case "next": {
70
+ $props.number = Math.min($props.count, $props.number + 1);
71
+ break;
72
+ }
73
+ case "end": {
74
+ $props.number = $props.count;
75
+ break;
76
+ }
77
+ case "startgap":
78
+ case "endgap": {
79
+ let input = prompt("Go to page:");
80
+ if (input) {
81
+ $props.number = parseInt(input);
82
+ }
83
+ break;
84
+ }
85
+ default: {
86
+ $props.number = target;
87
+ }
88
+ }
89
+
90
+ const item = itemStates.find((i) => i.target === target);
91
+ if (item) {
92
+ item.setFocused();
93
+ }
94
+ }
95
+
96
+ function handleFocus(e: FocusEvent & { currentTarget: EventTarget & HTMLElement }) {
97
+ // Focus the first button so that its key events can be triggered by the user
98
+ const el = e.currentTarget.getElementsByTagName("button")[0];
99
+ if (el) {
100
+ el.focus();
101
+ }
102
+ }
103
+
104
+ function handleItemKey(e: KeyboardEvent) {
105
+ // TODO: Actually just change focus??
106
+ switch (e.key) {
107
+ case "Left":
108
+ case "ArrowLeft": {
109
+ toggleItem("previous");
110
+ e.preventDefault();
111
+ break;
112
+ }
113
+ case "Right":
114
+ case "ArrowRight": {
115
+ toggleItem("next");
116
+ e.preventDefault();
117
+ break;
118
+ }
119
+ case "Home": {
120
+ toggleItem("start");
121
+ e.preventDefault();
122
+ break;
123
+ }
124
+ case "End": {
125
+ toggleItem("end");
126
+ e.preventDefault();
127
+ break;
128
+ }
129
+ }
130
+ }
131
+
132
+ @render {
133
+ @if ($props.count > 0) {
134
+ <div
135
+ {$props?.id}
136
+ class={["torp-pagination", $props?.class]}
137
+ {$props?.style}
138
+ tabindex="0"
139
+ onfocus={handleFocus}
140
+ >
141
+ <slot />
142
+ </div>
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,100 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { PaginationContextName, type PaginationContext, type PageNumber } from "./PaginationTypes";
3
+
4
+ interface PaginationItemProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue,
9
+ /** Styles for the root element */
10
+ style?: StyleValue,
11
+ /** The page for the button */
12
+ page: PageNumber;
13
+ /** Whether this is the active item */
14
+ active?: boolean;
15
+ /** Raised when this item is made active */
16
+ ontoggle?: (active: boolean) => void,
17
+ };
18
+
19
+ export default function PaginationItem($props: PaginationItemProps) {
20
+ let button: HTMLButtonElement;
21
+
22
+ // Get stuff out of the Pagination context
23
+ const context = $context[PaginationContextName] as PaginationContext;
24
+ if (!context) {
25
+ throw new Error("PaginationItem must be contained within a Pagination");
26
+ }
27
+ const { registerItem, /*removeItem,*/ toggleItem, handleItemKey } = context;
28
+
29
+ // Register this item with the parent Pagination, which will handle setting this item's focus on keyboard events
30
+ registerItem($props.page, setActive, setFocused);
31
+
32
+ // This function is called by the parent Pagination to set this item's active value
33
+ function setActive(value: boolean) {
34
+ if ($props.active !== value) {
35
+ $props.active = value;
36
+
37
+ /** Raised when this item is made active */
38
+ //dispatch("toggle", value);
39
+ if ($props?.ontoggle) {
40
+ $props.ontoggle(value);
41
+ }
42
+ }
43
+ }
44
+
45
+ // This function is called by the parent Pagination to focus this item's button
46
+ function setFocused() {
47
+ button.focus();
48
+ }
49
+
50
+ function handleClick() {
51
+ // Pass the value to the toggleItem method in the parent Pagination which will handle toggling
52
+ // the value for all items
53
+ toggleItem($props.page);
54
+ }
55
+
56
+ function handleKey(e: KeyboardEvent) {
57
+ // Pass the key press up to the parent Pagination which will handle setting the focused button
58
+ handleItemKey(e);
59
+ }
60
+
61
+ @render {
62
+ @if ($props.active) {
63
+ <span
64
+ {$props?.id}
65
+ class={["torp-pagination-item", $props?.class]}
66
+ {$props?.style}
67
+ data-state={$props.active ? "active" : "inactive"}
68
+ >
69
+ {$props.page}
70
+ </span>
71
+ } else {
72
+ <button
73
+ type="button"
74
+ {$props?.id}
75
+ class={["torp-pagination-item", $props?.class]}
76
+ data-type={$props.page}
77
+ data-state={$props.active ? "active" : "inactive"}
78
+ &ref={button}
79
+ onclick={handleClick}
80
+ onkeydown={handleKey}
81
+ >
82
+ <slot>
83
+ @if ($props.page === "start") {
84
+ <span>&lt;&lt;</span>
85
+ } else if ($props.page === "previous") {
86
+ <span>&lt;</span>
87
+ } else if ($props.page === "next") {
88
+ <span>&gt;</span>
89
+ } else if ($props.page === "end") {
90
+ <span>&gt;&gt;</span>
91
+ } else if ($props.page === "startgap" || $props.page === "endgap") {
92
+ <span>…</span>
93
+ } else {
94
+ <span>{$props.page}</span>
95
+ }
96
+ </slot>
97
+ </button>
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,25 @@
1
+ export const PaginationContextName: unique symbol = Symbol.for("torp.Pagination");
2
+
3
+ export interface PaginationContext {
4
+ // The registerItem function is called from each SeriesItem to register itself and its
5
+ // methods with this component
6
+ registerItem: (
7
+ target: PageNumber,
8
+ setActive: (value: boolean) => void,
9
+ setFocused: () => void,
10
+ ) => void;
11
+ // The removeItem function is called from a SeriesItem when it has been unloaded so that we
12
+ // can remove it from the itemStates array
13
+ removeItem: (target: PageNumber) => void;
14
+ toggleItem: (target: PageNumber) => void;
15
+ handleItemKey: (e: KeyboardEvent) => void;
16
+ }
17
+
18
+ export interface ItemState {
19
+ target: PageNumber;
20
+ active: boolean;
21
+ setActive: (value: boolean) => void;
22
+ setFocused: () => void;
23
+ }
24
+
25
+ export type PageNumber = "start" | "startgap" | "previous" | "next" | "end" | "endgap" | number;
@@ -0,0 +1,57 @@
1
+ import { type PageNumber } from "./PaginationTypes";
2
+
3
+ /**
4
+ * Builds the pages that should be displayed for pagination.
5
+ *
6
+ * @param count The total number of pages (or items if the page size is greater than 1)
7
+ * @param number The active page number
8
+ * @param pageSize The number of items on each page
9
+ * @param maxPages The maximum number of pages to display (gaps will be indicated with ellipses)
10
+ */
11
+ export default function buildPages(
12
+ count: number,
13
+ number: number,
14
+ pageSize: number = 1,
15
+ maxPages: number = 9,
16
+ ): PageNumber[] {
17
+ // Calculate the page count
18
+ const pageCount = Math.ceil(count / pageSize);
19
+
20
+ if (pageCount <= maxPages) {
21
+ // There"s no need for ellipses, just return the numbers
22
+ return Array.from({ length: pageCount })
23
+ .fill(0)
24
+ .map((_, i) => i + 1);
25
+ } else {
26
+ // Get the start and end around the current page number
27
+ // Use floor and ceil in case there is an even number of pages (e.g. if
28
+ // maxPages is 8, there will be 3 numbers at the start and 4 at the end)
29
+ let start = number - Math.floor((maxPages - 1) / 2);
30
+ let end = number + Math.ceil((maxPages - 1) / 2);
31
+ if (start < 1) {
32
+ end += Math.abs(start) + 1;
33
+ start = 1;
34
+ } else if (end > pageCount) {
35
+ start -= end - pageCount;
36
+ end = pageCount;
37
+ }
38
+
39
+ // Build the numbers
40
+ const numbers: PageNumber[] = Array.from({ length: end - start + 1 })
41
+ .fill(0)
42
+ .map((_, i) => start + i);
43
+
44
+ // Replace numbers at the start and end with ellipses if necessary
45
+ if (numbers[0] !== 1) {
46
+ numbers[0] = 1;
47
+ numbers[1] = "startgap";
48
+ }
49
+ const l = numbers.length;
50
+ if (numbers[l - 1] !== pageCount) {
51
+ numbers[l - 1] = pageCount;
52
+ numbers[l - 2] = "endgap";
53
+ }
54
+
55
+ return numbers;
56
+ }
57
+ }
@@ -0,0 +1,53 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface PaginationProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue,
9
+ /** Styles for the root element */
10
+ style?: StyleValue,
11
+ /** The total number of pages (or items if the page size is greater than 1) */
12
+ count: number,
13
+ /** The active page number */
14
+ number: number,
15
+ }
16
+
17
+ /**
18
+ * Buttons with page numbers that can be used to move between pages.
19
+ */
20
+ declare function Pagination(
21
+ $parent: ParentNode,
22
+ $anchor: Node | null,
23
+ $props: PaginationProps,
24
+ $context?: Record<PropertyKey, any>,
25
+ $slots?: Record<string, SlotRender>,
26
+ ): void;
27
+
28
+
29
+
30
+ interface PaginationItemProps {
31
+ /** An ID for the root element */
32
+ id?: string;
33
+ /** Classes for the root element */
34
+ class?: ClassValue,
35
+ /** Styles for the root element */
36
+ style?: StyleValue,
37
+ /** The page for the button */
38
+ page: PageNumber;
39
+ /** Whether this is the active item */
40
+ active?: boolean;
41
+ /** Raised when this item is made active */
42
+ ontoggle?: (active: boolean) => void,
43
+ }
44
+
45
+ declare function PaginationItem(
46
+ $parent: ParentNode,
47
+ $anchor: Node | null,
48
+ $props: PaginationItemProps,
49
+ $context?: Record<PropertyKey, any>,
50
+ $slots?: Record<string, SlotRender>,
51
+ ): void;
52
+
53
+ export { Pagination, PaginationItem };