@umbra.ui/core 0.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.
Files changed (272) hide show
  1. package/dist/components/controls/Dropdown/types.d.ts +5 -0
  2. package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
  3. package/dist/components/controls/Dropdown/types.js +1 -0
  4. package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
  5. package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
  6. package/dist/components/controls/SegmentedControl/types.js +1 -0
  7. package/dist/components/dialogs/Alert/types.d.ts +7 -0
  8. package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
  9. package/dist/components/dialogs/Alert/types.js +1 -0
  10. package/dist/components/dialogs/Toast/types.d.ts +34 -0
  11. package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
  12. package/dist/components/dialogs/Toast/types.js +10 -0
  13. package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
  14. package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
  15. package/dist/components/dialogs/Toast/useToast.js +90 -0
  16. package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
  17. package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
  18. package/dist/components/indicators/Tooltip/tooltip.js +33 -0
  19. package/dist/components/indicators/Tooltip/types.d.ts +14 -0
  20. package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
  21. package/dist/components/indicators/Tooltip/types.js +1 -0
  22. package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
  23. package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
  24. package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
  25. package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
  26. package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
  27. package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
  28. package/dist/components/inputs/Tags/types.d.ts +93 -0
  29. package/dist/components/inputs/Tags/types.d.ts.map +1 -0
  30. package/dist/components/inputs/Tags/types.js +216 -0
  31. package/dist/components/inputs/search/types.d.ts +9 -0
  32. package/dist/components/inputs/search/types.d.ts.map +1 -0
  33. package/dist/components/inputs/search/types.js +1 -0
  34. package/dist/components/navigation/adaptive/types.d.ts +16 -0
  35. package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
  36. package/dist/components/navigation/adaptive/types.js +1 -0
  37. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
  38. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
  39. package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
  40. package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
  41. package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
  42. package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
  43. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
  44. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
  45. package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
  46. package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
  47. package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
  48. package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
  49. package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
  50. package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
  51. package/dist/components/navigation/adaptive/useViewResize.js +146 -0
  52. package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
  53. package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
  54. package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
  55. package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
  56. package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
  57. package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
  58. package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
  59. package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
  60. package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
  61. package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
  62. package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
  63. package/dist/components/navigation/tabcontroller/types.js +1 -0
  64. package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
  65. package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
  66. package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
  67. package/dist/components/navigation/types.d.ts +8 -0
  68. package/dist/components/navigation/types.d.ts.map +1 -0
  69. package/dist/components/navigation/types.js +1 -0
  70. package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
  71. package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
  72. package/dist/components/pickers/CollectionPicker/types.js +1 -0
  73. package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
  74. package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
  75. package/dist/components/pickers/ColorPicker/colors.js +266 -0
  76. package/dist/components/pickers/FilePicker/types.d.ts +10 -0
  77. package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
  78. package/dist/components/pickers/FilePicker/types.js +1 -0
  79. package/dist/index.d.ts +91 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +196 -0
  82. package/dist/theme.d.ts +73 -0
  83. package/dist/theme.d.ts.map +1 -0
  84. package/dist/theme.js +279 -0
  85. package/dist/themes/blank.d.ts +7 -0
  86. package/dist/themes/blank.d.ts.map +1 -0
  87. package/dist/themes/blank.js +543 -0
  88. package/dist/themes/crimson-dark.d.ts +4 -0
  89. package/dist/themes/crimson-dark.d.ts.map +1 -0
  90. package/dist/themes/crimson-dark.js +552 -0
  91. package/dist/themes/cyan-light.d.ts +4 -0
  92. package/dist/themes/cyan-light.d.ts.map +1 -0
  93. package/dist/themes/cyan-light.js +552 -0
  94. package/dist/themes/dark.d.ts +4 -0
  95. package/dist/themes/dark.d.ts.map +1 -0
  96. package/dist/themes/dark.js +551 -0
  97. package/dist/themes/gold-dark.d.ts +4 -0
  98. package/dist/themes/gold-dark.d.ts.map +1 -0
  99. package/dist/themes/gold-dark.js +552 -0
  100. package/dist/themes/grass-dark.d.ts +4 -0
  101. package/dist/themes/grass-dark.d.ts.map +1 -0
  102. package/dist/themes/grass-dark.js +552 -0
  103. package/dist/themes/indigo.d.ts +4 -0
  104. package/dist/themes/indigo.d.ts.map +1 -0
  105. package/dist/themes/indigo.js +552 -0
  106. package/dist/themes/light.d.ts +4 -0
  107. package/dist/themes/light.d.ts.map +1 -0
  108. package/dist/themes/light.js +551 -0
  109. package/dist/themes/orange-dark.d.ts +4 -0
  110. package/dist/themes/orange-dark.d.ts.map +1 -0
  111. package/dist/themes/orange-dark.js +551 -0
  112. package/dist/themes/orange-light.d.ts +4 -0
  113. package/dist/themes/orange-light.d.ts.map +1 -0
  114. package/dist/themes/orange-light.js +551 -0
  115. package/package.json +62 -0
  116. package/src/components/controls/Button/Button.vue +417 -0
  117. package/src/components/controls/Button/README.md +348 -0
  118. package/src/components/controls/Button/theme.css +200 -0
  119. package/src/components/controls/Checkbox/Checkbox.vue +164 -0
  120. package/src/components/controls/Checkbox/README.md +441 -0
  121. package/src/components/controls/Checkbox/theme.css +36 -0
  122. package/src/components/controls/Dropdown/Dropdown.vue +476 -0
  123. package/src/components/controls/Dropdown/README.md +370 -0
  124. package/src/components/controls/Dropdown/theme.css +50 -0
  125. package/src/components/controls/Dropdown/types.ts +6 -0
  126. package/src/components/controls/IconButton/IconButton.vue +267 -0
  127. package/src/components/controls/IconButton/README.md +502 -0
  128. package/src/components/controls/IconButton/theme.css +89 -0
  129. package/src/components/controls/Radio/README.md +591 -0
  130. package/src/components/controls/Radio/Radio.vue +89 -0
  131. package/src/components/controls/Radio/theme.css +14 -0
  132. package/src/components/controls/RangeSlider/README.md +608 -0
  133. package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
  134. package/src/components/controls/RangeSlider/theme.css +80 -0
  135. package/src/components/controls/SegmentedControl/README.md +587 -0
  136. package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
  137. package/src/components/controls/SegmentedControl/theme.css +60 -0
  138. package/src/components/controls/SegmentedControl/types.ts +5 -0
  139. package/src/components/controls/Slider/README.md +627 -0
  140. package/src/components/controls/Slider/Slider.vue +260 -0
  141. package/src/components/controls/Slider/theme.css +74 -0
  142. package/src/components/controls/Stepper/README.md +601 -0
  143. package/src/components/controls/Stepper/Stepper.vue +103 -0
  144. package/src/components/controls/Stepper/theme.css +53 -0
  145. package/src/components/controls/Switch/README.md +667 -0
  146. package/src/components/controls/Switch/Switch.vue +127 -0
  147. package/src/components/controls/Switch/theme.css +42 -0
  148. package/src/components/dialogs/Alert/Alert.vue +218 -0
  149. package/src/components/dialogs/Alert/README.md +450 -0
  150. package/src/components/dialogs/Alert/theme.css +44 -0
  151. package/src/components/dialogs/Alert/types.ts +11 -0
  152. package/src/components/dialogs/Toast/README.md +522 -0
  153. package/src/components/dialogs/Toast/Toast.vue +296 -0
  154. package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
  155. package/src/components/dialogs/Toast/theme.css +44 -0
  156. package/src/components/dialogs/Toast/types.ts +46 -0
  157. package/src/components/dialogs/Toast/useToast.ts +127 -0
  158. package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
  159. package/src/components/indicators/ProgressBar/README.md +744 -0
  160. package/src/components/indicators/ProgressBar/theme.css +36 -0
  161. package/src/components/indicators/Tooltip/README.md +723 -0
  162. package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
  163. package/src/components/indicators/Tooltip/theme.css +18 -0
  164. package/src/components/indicators/Tooltip/tooltip.ts +48 -0
  165. package/src/components/indicators/Tooltip/types.ts +15 -0
  166. package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
  167. package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
  168. package/src/components/inputs/AutogrowTextView/README.md +643 -0
  169. package/src/components/inputs/AutogrowTextView/theme.css +28 -0
  170. package/src/components/inputs/InputCard/InputCard.vue +600 -0
  171. package/src/components/inputs/InputCard/README.md +636 -0
  172. package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
  173. package/src/components/inputs/InputEmail/README.md +764 -0
  174. package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
  175. package/src/components/inputs/InputNumber/README.md +749 -0
  176. package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
  177. package/src/components/inputs/InputPhone/README.md +636 -0
  178. package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
  179. package/src/components/inputs/InputSecure/README.md +771 -0
  180. package/src/components/inputs/InputText/InputText.vue +225 -0
  181. package/src/components/inputs/InputText/README.md +844 -0
  182. package/src/components/inputs/OTP/OTP.vue +349 -0
  183. package/src/components/inputs/OTP/README.md +736 -0
  184. package/src/components/inputs/OTP/theme.css +50 -0
  185. package/src/components/inputs/StringCapture/README.md +718 -0
  186. package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
  187. package/src/components/inputs/StringCapture/theme.css +86 -0
  188. package/src/components/inputs/Tags/README.md +897 -0
  189. package/src/components/inputs/Tags/TagBar.vue +793 -0
  190. package/src/components/inputs/Tags/TagCreation.vue +219 -0
  191. package/src/components/inputs/Tags/TagPicker.vue +380 -0
  192. package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
  193. package/src/components/inputs/Tags/theme.css +121 -0
  194. package/src/components/inputs/Tags/types.ts +346 -0
  195. package/src/components/inputs/search/README.md +759 -0
  196. package/src/components/inputs/search/SearchBar.vue +394 -0
  197. package/src/components/inputs/search/SearchResults.vue +310 -0
  198. package/src/components/inputs/search/theme.css +187 -0
  199. package/src/components/inputs/search/types.ts +8 -0
  200. package/src/components/inputs/theme.css +102 -0
  201. package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
  202. package/src/components/menus/ActionMenu/README.md +825 -0
  203. package/src/components/menus/ActionMenu/theme.css +93 -0
  204. package/src/components/models/Popover/Popover.vue +551 -0
  205. package/src/components/models/Popover/README.md +885 -0
  206. package/src/components/models/Popover/theme.css +52 -0
  207. package/src/components/models/Sheet/README.md +1159 -0
  208. package/src/components/models/Sheet/Sheet.vue +465 -0
  209. package/src/components/models/Sheet/theme.css +72 -0
  210. package/src/components/models/Sidebar/README.md +1228 -0
  211. package/src/components/models/Sidebar/Sidebar.vue +480 -0
  212. package/src/components/models/Sidebar/theme.css +90 -0
  213. package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
  214. package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
  215. package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
  216. package/src/components/navigation/adaptive/README.md +768 -0
  217. package/src/components/navigation/adaptive/types.ts +19 -0
  218. package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
  219. package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
  220. package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
  221. package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
  222. package/src/components/navigation/adaptive/useViewResize.ts +211 -0
  223. package/src/components/navigation/navstack/NavigationStack.vue +180 -0
  224. package/src/components/navigation/navstack/README.md +994 -0
  225. package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
  226. package/src/components/navigation/slideover/README.md +1275 -0
  227. package/src/components/navigation/slideover/SlideoverController.vue +287 -0
  228. package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
  229. package/src/components/navigation/splitview/README.md +1115 -0
  230. package/src/components/navigation/splitview/SplitViewController.vue +176 -0
  231. package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
  232. package/src/components/navigation/tabcontroller/README.md +919 -0
  233. package/src/components/navigation/tabcontroller/TabController.vue +307 -0
  234. package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
  235. package/src/components/navigation/tabcontroller/types.ts +24 -0
  236. package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
  237. package/src/components/navigation/theme.css +91 -0
  238. package/src/components/navigation/types.ts +7 -0
  239. package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
  240. package/src/components/pickers/CollectionPicker/README.md +1115 -0
  241. package/src/components/pickers/CollectionPicker/theme.css +14 -0
  242. package/src/components/pickers/CollectionPicker/types.ts +11 -0
  243. package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
  244. package/src/components/pickers/ColorPicker/README.md +1439 -0
  245. package/src/components/pickers/ColorPicker/colors.ts +299 -0
  246. package/src/components/pickers/ColorPicker/theme.css +32 -0
  247. package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
  248. package/src/components/pickers/DatePicker/README.md +1195 -0
  249. package/src/components/pickers/DatePicker/theme.css +22 -0
  250. package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
  251. package/src/components/pickers/FilePicker/README.md +1542 -0
  252. package/src/components/pickers/FilePicker/theme.css +48 -0
  253. package/src/components/pickers/FilePicker/types.ts +10 -0
  254. package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
  255. package/src/components/pickers/IconPicker/README.md +1161 -0
  256. package/src/components/pickers/IconPicker/theme.css +28 -0
  257. package/src/components/pickers/theme.css +82 -0
  258. package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
  259. package/src/components/views/MarkdownViewer/README.md +833 -0
  260. package/src/components/views/MarkdownViewer/theme.css +130 -0
  261. package/src/index.ts +263 -0
  262. package/src/theme.ts +378 -0
  263. package/src/themes/crimson-dark.ts +556 -0
  264. package/src/themes/cyan-light.ts +556 -0
  265. package/src/themes/dark.ts +557 -0
  266. package/src/themes/gold-dark.ts +556 -0
  267. package/src/themes/grass-dark.ts +556 -0
  268. package/src/themes/indigo.ts +556 -0
  269. package/src/themes/light.ts +557 -0
  270. package/src/themes/orange-dark.ts +557 -0
  271. package/src/themes/orange-light.ts +557 -0
  272. package/src/vue.d.ts +45 -0
@@ -0,0 +1,721 @@
1
+ import { nextTick, ref, type Ref } from "vue";
2
+ import { Flip } from "gsap/Flip";
3
+ import gsap from "gsap";
4
+ import { InternalView } from "./types";
5
+
6
+ gsap.registerPlugin(Flip);
7
+
8
+ const animationInProgress = ref<boolean>(false);
9
+
10
+ /*
11
+ # FLIP Order of Operations
12
+ Step 1: Get State
13
+ Step 3: Manipulate DOM
14
+ Step 4: Animate
15
+ Step 5: Update Data (vue reactive state)
16
+ */
17
+
18
+ export const useViewAnimation = (
19
+ views: Ref<InternalView[]>,
20
+ instanceKey: string,
21
+ containerDimensions: Ref<{ width: number; height: number }>,
22
+ padding: string,
23
+ gap: string
24
+ ) => {
25
+ /*
26
+ * Basic animation function
27
+ */
28
+
29
+ const animate = async (
30
+ targets: HTMLElement[],
31
+ manipulateDOMFn: () => void,
32
+ updateDataFn: () => void
33
+ ) => {
34
+ if (animationInProgress.value) {
35
+ console.warn("Animation already in progress");
36
+ return;
37
+ }
38
+ animationInProgress.value = true;
39
+ // Step 1: Get State
40
+ const state = Flip.getState(targets);
41
+
42
+ // Step 2: Manipulate DOM
43
+ manipulateDOMFn();
44
+
45
+ // Step 3: Animate
46
+ return Flip.from(state, {
47
+ duration: 0.3,
48
+ ease: "power1.inOut",
49
+ absolute: true,
50
+ onComplete: () => {
51
+ animationInProgress.value = false;
52
+ },
53
+ }).then(() => {
54
+ // Step 5: Update Data
55
+ updateDataFn();
56
+ });
57
+ };
58
+
59
+ /*
60
+ Utility Functions
61
+ */
62
+
63
+ // Helper functions to get unique element IDs
64
+ const getOffscreenLeadingId = () => `${instanceKey}-offscreen-leading`;
65
+ const getOffscreenTrailingId = () => `${instanceKey}-offscreen-trailing`;
66
+ const getOnscreenId = () => `${instanceKey}-onscreen`;
67
+ const getViewId = (view: InternalView) => `${instanceKey}-view-${view.id}`;
68
+ const getDarkenId = () => `${instanceKey}-darken`;
69
+ const getOverlayId = () => `${instanceKey}-overlay`;
70
+
71
+ const getViewElements = (): HTMLElement[] => {
72
+ return views.value
73
+ .map((view) => document.getElementById(getViewId(view)))
74
+ .filter((element): element is HTMLElement => element !== null);
75
+ };
76
+
77
+ const getAllViewsBefore = (id: string): InternalView[] => {
78
+ const targetIndex = views.value.findIndex((view) => view.id === id);
79
+ return views.value.slice(0, targetIndex);
80
+ };
81
+
82
+ const getAllViewsBeforeIndex = (index: number): InternalView[] => {
83
+ return views.value.slice(0, index);
84
+ };
85
+
86
+ const getAllViewsAfterIndex = (index: number): InternalView[] => {
87
+ return views.value.slice(index + 1);
88
+ };
89
+
90
+ const getAllViewsAfter = (id: string): InternalView[] => {
91
+ const targetIndex = views.value.findIndex((view) => view.id === id);
92
+
93
+ if (targetIndex === -1) {
94
+ return []; // View not found
95
+ }
96
+
97
+ return views.value.slice(targetIndex + 1);
98
+ };
99
+
100
+ const getAllViewsAfterWithLocation = (
101
+ id: string,
102
+ location: string
103
+ ): InternalView[] => {
104
+ const viewsAfter = getAllViewsAfter(id);
105
+ return viewsAfter.filter((view) => view.location === location);
106
+ };
107
+
108
+ const getAllViewElementsAfterWithLocation = (
109
+ id: string,
110
+ location: string
111
+ ): HTMLElement[] => {
112
+ const viewsAfter = getAllViewsAfterWithLocation(id, location);
113
+
114
+ // Map to HTMLElements and filter out null values
115
+ return viewsAfter
116
+ .map((view) => document.getElementById(getViewId(view)))
117
+ .filter((element): element is HTMLElement => element !== null);
118
+ };
119
+
120
+ const getAllViewElementsBefore = (id: string): HTMLElement[] => {
121
+ const targetIndex = views.value.findIndex((view) => view.id === id);
122
+
123
+ if (targetIndex === -1) {
124
+ return []; // View not found
125
+ }
126
+
127
+ // Get all views before the target index
128
+ const precedingViews = views.value.slice(0, targetIndex);
129
+
130
+ // Map to HTMLElements and filter out null values
131
+ return precedingViews
132
+ .map((view) => document.getElementById(getViewId(view)))
133
+ .filter((element): element is HTMLElement => element !== null);
134
+ };
135
+
136
+ const getSingleViewBefore = (id: string): InternalView | null => {
137
+ const targetIndex = views.value.findIndex((view) => view.id === id);
138
+
139
+ if (targetIndex === -1 || targetIndex === 0) {
140
+ return null; // View not found or is first view
141
+ }
142
+
143
+ return views.value[targetIndex - 1];
144
+ };
145
+
146
+ const getSingleViewAfter = (id: string): InternalView | null => {
147
+ const targetIndex = views.value.findIndex((view) => view.id === id);
148
+ if (targetIndex === -1 || targetIndex === views.value.length - 1) {
149
+ return null; // View not found or is last view
150
+ }
151
+ return views.value[targetIndex + 1];
152
+ };
153
+
154
+ const getSingleViewElementBefore = (id: string): HTMLElement | null => {
155
+ const precedingView = getSingleViewBefore(id);
156
+
157
+ if (!precedingView) {
158
+ return null;
159
+ }
160
+
161
+ return document.getElementById(getViewId(precedingView));
162
+ };
163
+
164
+ const isOnscreen = (view: InternalView) => {
165
+ return view.location === "onscreen";
166
+ };
167
+
168
+ const hasPrecedingViews = (id: string, location: string) => {
169
+ const targetIndex = views.value.findIndex((view) => view.id === id);
170
+ if (targetIndex === -1) {
171
+ return false; // View not found
172
+ }
173
+ // Check if ANY preceding view has the specified location
174
+ return views.value
175
+ .slice(0, targetIndex)
176
+ .some((view) => view.location === location);
177
+ };
178
+
179
+ const calculateContentWidth = (
180
+ containerWidth: number,
181
+ paddingString: string
182
+ ): number => {
183
+ // Handle empty or invalid padding
184
+ if (!paddingString || paddingString === "0") {
185
+ return containerWidth;
186
+ }
187
+
188
+ // Split padding string by spaces and filter out empty strings
189
+ const values = paddingString
190
+ .trim()
191
+ .split(/\s+/)
192
+ .map((v) => parseFloat(v));
193
+
194
+ // Determine horizontal padding based on number of values
195
+ let horizontalPadding = 0;
196
+
197
+ switch (values.length) {
198
+ case 1:
199
+ // padding: 10px (all sides)
200
+ horizontalPadding = values[0] * 2;
201
+ break;
202
+ case 2:
203
+ // padding: 10px 20px (vertical horizontal)
204
+ horizontalPadding = values[1] * 2;
205
+ break;
206
+ case 3:
207
+ // padding: 10px 20px 15px (top horizontal bottom)
208
+ horizontalPadding = values[1] * 2;
209
+ break;
210
+ case 4:
211
+ // padding: 10px 20px 15px 25px (top right bottom left)
212
+ horizontalPadding = values[1] + values[3];
213
+ break;
214
+ }
215
+
216
+ return containerWidth - horizontalPadding;
217
+ };
218
+
219
+ const getFirstViewInOnscreen = () => {
220
+ return views.value.find((v) => v.location === "onscreen");
221
+ };
222
+
223
+ const getFirstViewInOverlay = () => {
224
+ return views.value.find((v) => v.location === "overlay");
225
+ };
226
+
227
+ /*
228
+ * Internal Functions: Splitview
229
+ For Splitview:
230
+ - show presents view(s) onscreen from the left.
231
+ - hide dismisses view(s) to offscreenfrom the left.
232
+ - push presents a view from the left.
233
+ - pop dismisses a view to the left.
234
+ */
235
+
236
+ type NavigationType = "push" | "pop" | "show" | "hide";
237
+
238
+ const splitViewShow = async (id: string) => {
239
+ const view = views.value.find((v) => v.id === id);
240
+ if (view?.location === "onscreen") return;
241
+ await splitViewNavigate(id, "show");
242
+ };
243
+
244
+ const splitViewHide = async (id: string) => {
245
+ // Prevent hiding the last view (would result in nothing being shown)
246
+ const viewIndex = views.value.findIndex((view) => view.id === id);
247
+ if (viewIndex === views.value.length - 1) {
248
+ return;
249
+ }
250
+
251
+ await splitViewNavigate(id, "hide");
252
+ };
253
+
254
+ const splitViewPush = async (id: string | null = null) => {
255
+ // if id is provided, push from that id
256
+ if (id) {
257
+ await splitViewNavigate(id, "push");
258
+ return;
259
+ } else {
260
+ const firstInOnscreen = getFirstViewInOnscreen()?.id;
261
+ if (!firstInOnscreen) {
262
+ console.error("No view to push from");
263
+ return;
264
+ }
265
+ await splitViewNavigate(firstInOnscreen, "push");
266
+ return;
267
+ }
268
+ };
269
+
270
+ const splitViewPop = async (id: string | null = null) => {
271
+ // if id is provided, pop from that id
272
+ if (id) {
273
+ await splitViewNavigate(id, "pop");
274
+ return;
275
+ }
276
+ // if id is not provided, pop from the view to the right of the first view in onscreen (yes, I know it's a little weird)
277
+ else {
278
+ const firstInOnscreen = getFirstViewInOnscreen()?.id;
279
+ if (!firstInOnscreen) return;
280
+ const nextInOnscreen = getSingleViewAfter(firstInOnscreen)?.id;
281
+ if (!nextInOnscreen) {
282
+ console.warn("nothing left to pop from");
283
+ return;
284
+ }
285
+ await splitViewNavigate(nextInOnscreen, "pop");
286
+ return;
287
+ }
288
+ };
289
+
290
+ const splitViewNavigate = async (id: string, type: NavigationType) => {
291
+ const elements = getViewElements();
292
+
293
+ // Get the appropriate view elements based on type
294
+ let viewElements: (HTMLElement | null | undefined)[] = [];
295
+
296
+ switch (type) {
297
+ case "push":
298
+ viewElements = [getSingleViewElementBefore(id)];
299
+ break;
300
+ case "pop":
301
+ viewElements = getAllViewElementsBefore(id);
302
+ break;
303
+ case "show":
304
+ // Get the view with the given id AND all views after it that have location "leading"
305
+ const currentView = document.getElementById(
306
+ getViewId(views.value.find((v) => v.id === id)!)
307
+ );
308
+ const viewsAfterWithLeading = getAllViewElementsAfterWithLocation(
309
+ id,
310
+ "leading"
311
+ );
312
+ viewElements = [currentView, ...viewsAfterWithLeading];
313
+ break;
314
+ case "hide":
315
+ // Get the current view AND all views before it
316
+ const hideCurrentView = document.getElementById(
317
+ getViewId(views.value.find((v) => v.id === id)!)
318
+ );
319
+ viewElements = [hideCurrentView, ...getAllViewElementsBefore(id)];
320
+ break;
321
+ }
322
+
323
+ // Early return for push if no element found
324
+ if (type === "push" && !viewElements[0]) return;
325
+
326
+ const manipulateDOM = () => {
327
+ switch (type) {
328
+ case "push": {
329
+ const onscreen = document.getElementById(getOnscreenId());
330
+ if (!onscreen) return;
331
+
332
+ const viewElement = viewElements[0];
333
+ if (viewElement) {
334
+ viewElement.parentNode?.removeChild(viewElement);
335
+ viewElement.style.order = "";
336
+ onscreen.insertBefore(viewElement, onscreen.children[0] || null);
337
+ }
338
+ break;
339
+ }
340
+
341
+ case "pop": {
342
+ const offscreenLeading = document.getElementById(
343
+ getOffscreenLeadingId()
344
+ );
345
+ if (!offscreenLeading) return;
346
+
347
+ viewElements.forEach((view) => {
348
+ if (view) {
349
+ view.parentNode?.removeChild(view);
350
+ offscreenLeading.appendChild(view);
351
+ }
352
+ });
353
+ break;
354
+ }
355
+
356
+ case "show": {
357
+ const onscreen = document.getElementById(getOnscreenId());
358
+ if (!onscreen) return;
359
+
360
+ // Show the view with the given id and all views after it that have location "leading"
361
+ // Add them at the beginning of onscreen in reverse order to maintain correct sequence
362
+ viewElements.reverse().forEach((view) => {
363
+ if (view) {
364
+ view.parentNode?.removeChild(view);
365
+ view.style.order = "";
366
+ onscreen.insertBefore(view, onscreen.children[0] || null);
367
+ }
368
+ });
369
+ break;
370
+ }
371
+
372
+ case "hide": {
373
+ const offscreenLeading = document.getElementById(
374
+ getOffscreenLeadingId()
375
+ );
376
+ if (!offscreenLeading) return;
377
+
378
+ // Hide the current view and all views before it
379
+ viewElements.forEach((view) => {
380
+ if (view) {
381
+ view.parentNode?.removeChild(view);
382
+ offscreenLeading.appendChild(view);
383
+ }
384
+ });
385
+ break;
386
+ }
387
+ }
388
+ };
389
+
390
+ const updateData = async () => {
391
+ switch (type) {
392
+ case "push": {
393
+ const prevIndex = views.value.findIndex(
394
+ (view) => view.id === getSingleViewBefore(id)?.id
395
+ );
396
+ views.value[prevIndex].location = "onscreen";
397
+ break;
398
+ }
399
+
400
+ case "pop": {
401
+ const beforeViews = getAllViewsBefore(id);
402
+ beforeViews.forEach((view) => {
403
+ view.location = "leading";
404
+ });
405
+ break;
406
+ }
407
+
408
+ case "show": {
409
+ // Update the view with the given id to be onscreen
410
+ const currentViewIndex = views.value.findIndex((v) => v.id === id);
411
+ if (currentViewIndex !== -1) {
412
+ views.value[currentViewIndex].location = "onscreen";
413
+ }
414
+
415
+ // Update all views after the given id that have location "leading" to be onscreen
416
+ const viewsAfterWithLeading = getAllViewsAfterWithLocation(
417
+ id,
418
+ "leading"
419
+ );
420
+ viewsAfterWithLeading.forEach((view) => {
421
+ view.location = "onscreen";
422
+ });
423
+ break;
424
+ }
425
+
426
+ case "hide": {
427
+ // Update the current view and all views before it to be leading/offscreen
428
+ const currentViewIndex = views.value.findIndex((v) => v.id === id);
429
+ if (currentViewIndex !== -1) {
430
+ views.value[currentViewIndex].location = "leading";
431
+ }
432
+
433
+ const beforeViews = getAllViewsBefore(id);
434
+ beforeViews.forEach((view) => {
435
+ view.location = "leading";
436
+ });
437
+ break;
438
+ }
439
+ }
440
+ };
441
+
442
+ await animate(elements, manipulateDOM, updateData);
443
+ };
444
+
445
+ /*
446
+ * Internal Functions: Slideover
447
+ For Slideover:
448
+ - show presents the overlay and the view(s) from the left.
449
+ - hide dismisses the overlay and its views.
450
+ - push presents another view to the overlay from the left.
451
+ - pop dismisses a view from the overlay to the left.
452
+ */
453
+
454
+ const slideoverShow = async (id: string) => {
455
+ // Get the view with the given id AND all views after it that have location "leading"
456
+ const currentView = views.value.find((v) => v.id === id);
457
+ if (currentView?.location === "onscreen") {
458
+ console.warn("This view is already onscreen.");
459
+ return;
460
+ }
461
+ const viewsAfterWithLeading = getAllViewsAfterWithLocation(id, "leading");
462
+ const viewsToShow = [currentView, ...viewsAfterWithLeading];
463
+
464
+ for (const view of viewsToShow) {
465
+ if (view) {
466
+ view.location = "overlay";
467
+ }
468
+ }
469
+
470
+ await nextTick();
471
+
472
+ const overlay = document.getElementById(getOverlayId());
473
+ if (!overlay) return;
474
+ isOverlayOpen.value = true;
475
+ overlay.style.pointerEvents = "auto";
476
+ overlay.style.transform = "translateX(0)";
477
+ };
478
+
479
+ const slideoverHide = async () => {
480
+ const overlay = document.getElementById(getOverlayId());
481
+ if (!overlay) return;
482
+ isOverlayOpen.value = false;
483
+ overlay.style.pointerEvents = "none";
484
+ overlay.style.transform = "translateX(calc((110% + 10px) * -1))";
485
+
486
+ setTimeout(() => {
487
+ for (const view of views.value) {
488
+ if (!view) return;
489
+ if (view.location === "overlay") {
490
+ view.location = "leading";
491
+ }
492
+ }
493
+ }, 330);
494
+ };
495
+
496
+ const slideoverPush = async (id: string | null = null) => {
497
+ let viewId = id;
498
+ if (!viewId) {
499
+ const firstInOnscreen = getFirstViewInOnscreen()?.id;
500
+ if (!firstInOnscreen) return;
501
+ const prevView = getSingleViewBefore(firstInOnscreen);
502
+ if (!prevView) {
503
+ console.warn("no more views to push to");
504
+ return;
505
+ }
506
+ viewId = prevView.id;
507
+ }
508
+ // exit and show the overlay if it's not open
509
+ if (!isOverlayOpen.value) {
510
+ slideoverShow(viewId);
511
+ return;
512
+ }
513
+
514
+ // get the elements
515
+ const overlay = document.getElementById(getOverlayId());
516
+ if (!overlay) return;
517
+ const leadingViews = views.value
518
+ .filter((view) => view.location === "leading")
519
+ .map((view) => document.getElementById(getViewId(view)))
520
+ .filter((el): el is HTMLElement => el !== null);
521
+ const overlayViews = views.value
522
+ .filter((view) => view.location === "overlay")
523
+ .map((view) => document.getElementById(getViewId(view)))
524
+ .filter((el): el is HTMLElement => el !== null);
525
+ const targets = [...leadingViews, ...overlayViews, overlay];
526
+
527
+ // manupulate the DOM
528
+ const manipulateDOM = () => {
529
+ const view = getSingleViewElementBefore(viewId);
530
+ if (!view) return;
531
+ view.parentNode?.removeChild(view);
532
+ view.style.order = "";
533
+ overlay.insertBefore(view, overlay.children[0] || null);
534
+ };
535
+
536
+ // update the data
537
+ const updateData = () => {
538
+ const data = getSingleViewBefore(viewId);
539
+ if (!data) return;
540
+ data.location = "overlay";
541
+ };
542
+
543
+ animate(targets, manipulateDOM, updateData);
544
+ };
545
+
546
+ const slideoverPop = async (id: string | null = null) => {
547
+ // exit if the overlay is not open
548
+ if (!isOverlayOpen.value) {
549
+ console.warn("No views to pop from");
550
+ return;
551
+ }
552
+ // if id is provided, pop from that id
553
+ let viewId = id;
554
+ // if id is not provided, pop from the view to the right of the first view in overlay
555
+ if (!viewId) {
556
+ const firstInOverlay = getFirstViewInOverlay()?.id;
557
+ if (!firstInOverlay) return;
558
+ const nextView = getSingleViewAfter(firstInOverlay);
559
+ if (!nextView) {
560
+ console.warn("nothing left to pop from");
561
+ return;
562
+ }
563
+ // if the next view is not in overlay, dismiss the overlay (since we are at the end)
564
+ if (nextView.location !== "overlay") {
565
+ console.warn("No more views in overlay... dismissing");
566
+ slideoverHide();
567
+ return;
568
+ }
569
+ viewId = nextView.id;
570
+ }
571
+ const leading = document.getElementById(getOffscreenLeadingId());
572
+ const overlay = document.getElementById(getOverlayId());
573
+ if (!leading || !overlay) {
574
+ console.error("Elements are null");
575
+ return;
576
+ }
577
+ const elements = getViewElements();
578
+ const before = getAllViewElementsBefore(viewId);
579
+
580
+ const targets = [...elements, overlay];
581
+
582
+ const manipulateDOM = () => {
583
+ const offscreenLeading = document.getElementById(getOffscreenLeadingId());
584
+ if (!offscreenLeading) return;
585
+ leading.style.zIndex = "10";
586
+ before.forEach((view) => {
587
+ view.parentNode?.removeChild(view);
588
+ offscreenLeading.appendChild(view);
589
+ });
590
+ };
591
+
592
+ const updateData = () => {
593
+ const beforeViews = getAllViewsBefore(viewId);
594
+ beforeViews.forEach((view) => {
595
+ view.location = "leading";
596
+ });
597
+ leading.style.zIndex = "";
598
+ };
599
+ await animate(targets, manipulateDOM, updateData);
600
+ };
601
+
602
+ /*
603
+ * Internal Functions: Navstack
604
+ For Navstack:
605
+ - show presents any view fullscreen from either the left or the right depending on location in the stack
606
+ - push presents next view fullscreen from the right, and dismisses current view to the left.
607
+ - pop dismisses current view to the right, and pushes preceeding view fullscreen from the left.
608
+ */
609
+
610
+ const navstackShow = async (id: string) => {
611
+ const index = views.value.findIndex((view) => view.id === id);
612
+ navstackNavigateTo(index);
613
+ };
614
+
615
+ const navstackPush = async () => {
616
+ const currentIndex = views.value.findIndex(
617
+ (view) => view.location === "onscreen"
618
+ );
619
+ navstackNavigateTo(currentIndex + 1);
620
+ };
621
+
622
+ const navstackPop = async (id: string) => {
623
+ const currentIndex = views.value.findIndex(
624
+ (view) => view.location === "onscreen"
625
+ );
626
+ navstackNavigateTo(currentIndex - 1);
627
+ };
628
+
629
+ const navstackNavigateTo = async (index: number) => {
630
+ const onscreen = document.getElementById(getOnscreenId());
631
+ const offscreenLeading = document.getElementById(getOffscreenLeadingId());
632
+ const offscreenTrailing = document.getElementById(getOffscreenTrailingId());
633
+ if (!onscreen || !offscreenLeading || !offscreenTrailing) return;
634
+ const currentIndex = views.value.findIndex(
635
+ (view) => view.location === "onscreen"
636
+ );
637
+ if (currentIndex === -1) return;
638
+ const oldView = views.value[currentIndex];
639
+ const newView = views.value[index];
640
+
641
+ // Ensure index is within bounds
642
+ if (index < 0 || index >= views.value.length) {
643
+ console.warn(`Navigation index ${index} out of bounds`);
644
+ return;
645
+ }
646
+
647
+ const currentView = document.getElementById(getViewId(oldView));
648
+ if (!currentView) return;
649
+
650
+ const nextView = document.getElementById(getViewId(newView));
651
+ if (!nextView) return;
652
+
653
+ // determine which offscreen container to use
654
+ const offscreenContainerOutflow =
655
+ index > currentIndex ? offscreenLeading : offscreenTrailing;
656
+
657
+ const targets = [currentView, nextView];
658
+ const manipulateDOM = () => {
659
+ currentView.style.width = `${calculateContentWidth(
660
+ containerDimensions.value.width,
661
+ padding
662
+ )}px`;
663
+ nextView.style.width = `${calculateContentWidth(
664
+ containerDimensions.value.width,
665
+ padding
666
+ )}px`;
667
+ currentView.parentNode?.removeChild(currentView);
668
+ offscreenContainerOutflow.appendChild(currentView);
669
+ nextView.parentNode?.removeChild(nextView);
670
+ onscreen.appendChild(nextView);
671
+ };
672
+ const updateData = () => {
673
+ // set up all views before the new view to leading
674
+ const prevViews = getAllViewsBeforeIndex(index);
675
+ prevViews.forEach((view) => {
676
+ view.location = "leading";
677
+ });
678
+ // set up all views after the new view to trailing
679
+ const nextViews = getAllViewsAfterIndex(index);
680
+ nextViews.forEach((view) => {
681
+ view.location = "trailing";
682
+ });
683
+ // set up the new view to onscreen
684
+ newView.location = "onscreen";
685
+ currentView.style.width = "";
686
+ nextView.style.width = "";
687
+ };
688
+ await animate(targets, manipulateDOM, updateData);
689
+ };
690
+
691
+ /*
692
+ State
693
+ */
694
+ const isOverlayOpen = ref(false);
695
+
696
+ return {
697
+ splitViewShow,
698
+ splitViewHide,
699
+ splitViewPush,
700
+ splitViewPop,
701
+ slideoverShow,
702
+ slideoverHide,
703
+ slideoverPush,
704
+ slideoverPop,
705
+ navstackShow,
706
+ navstackPush,
707
+ navstackPop,
708
+ getOffscreenLeadingId,
709
+ getOffscreenTrailingId,
710
+ getOnscreenId,
711
+ getViewId,
712
+ getDarkenId,
713
+ getOverlayId,
714
+ getViewElements,
715
+ isOnscreen,
716
+ hasPrecedingViews,
717
+ isOverlayOpen,
718
+ getSingleViewBefore,
719
+ getSingleViewAfter,
720
+ };
721
+ };