layout.mjs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // Composables
  2. import { useResizeObserver } from "./resizeObserver.mjs"; // Utilities
  3. import { computed, inject, onActivated, onBeforeUnmount, onDeactivated, onMounted, provide, reactive, ref, shallowRef } from 'vue';
  4. import { convertToUnit, findChildrenWithProvide, getCurrentInstance, getUid, propsFactory } from "../util/index.mjs"; // Types
  5. export const VuetifyLayoutKey = Symbol.for('vuetify:layout');
  6. export const VuetifyLayoutItemKey = Symbol.for('vuetify:layout-item');
  7. const ROOT_ZINDEX = 1000;
  8. export const makeLayoutProps = propsFactory({
  9. overlaps: {
  10. type: Array,
  11. default: () => []
  12. },
  13. fullHeight: Boolean
  14. }, 'layout');
  15. // Composables
  16. export const makeLayoutItemProps = propsFactory({
  17. name: {
  18. type: String
  19. },
  20. order: {
  21. type: [Number, String],
  22. default: 0
  23. },
  24. absolute: Boolean
  25. }, 'layout-item');
  26. export function useLayout() {
  27. const layout = inject(VuetifyLayoutKey);
  28. if (!layout) throw new Error('[Vuetify] Could not find injected layout');
  29. return {
  30. getLayoutItem: layout.getLayoutItem,
  31. mainRect: layout.mainRect,
  32. mainStyles: layout.mainStyles
  33. };
  34. }
  35. export function useLayoutItem(options) {
  36. const layout = inject(VuetifyLayoutKey);
  37. if (!layout) throw new Error('[Vuetify] Could not find injected layout');
  38. const id = options.id ?? `layout-item-${getUid()}`;
  39. const vm = getCurrentInstance('useLayoutItem');
  40. provide(VuetifyLayoutItemKey, {
  41. id
  42. });
  43. const isKeptAlive = shallowRef(false);
  44. onDeactivated(() => isKeptAlive.value = true);
  45. onActivated(() => isKeptAlive.value = false);
  46. const {
  47. layoutItemStyles,
  48. layoutItemScrimStyles
  49. } = layout.register(vm, {
  50. ...options,
  51. active: computed(() => isKeptAlive.value ? false : options.active.value),
  52. id
  53. });
  54. onBeforeUnmount(() => layout.unregister(id));
  55. return {
  56. layoutItemStyles,
  57. layoutRect: layout.layoutRect,
  58. layoutItemScrimStyles
  59. };
  60. }
  61. const generateLayers = (layout, positions, layoutSizes, activeItems) => {
  62. let previousLayer = {
  63. top: 0,
  64. left: 0,
  65. right: 0,
  66. bottom: 0
  67. };
  68. const layers = [{
  69. id: '',
  70. layer: {
  71. ...previousLayer
  72. }
  73. }];
  74. for (const id of layout) {
  75. const position = positions.get(id);
  76. const amount = layoutSizes.get(id);
  77. const active = activeItems.get(id);
  78. if (!position || !amount || !active) continue;
  79. const layer = {
  80. ...previousLayer,
  81. [position.value]: parseInt(previousLayer[position.value], 10) + (active.value ? parseInt(amount.value, 10) : 0)
  82. };
  83. layers.push({
  84. id,
  85. layer
  86. });
  87. previousLayer = layer;
  88. }
  89. return layers;
  90. };
  91. export function createLayout(props) {
  92. const parentLayout = inject(VuetifyLayoutKey, null);
  93. const rootZIndex = computed(() => parentLayout ? parentLayout.rootZIndex.value - 100 : ROOT_ZINDEX);
  94. const registered = ref([]);
  95. const positions = reactive(new Map());
  96. const layoutSizes = reactive(new Map());
  97. const priorities = reactive(new Map());
  98. const activeItems = reactive(new Map());
  99. const disabledTransitions = reactive(new Map());
  100. const {
  101. resizeRef,
  102. contentRect: layoutRect
  103. } = useResizeObserver();
  104. const computedOverlaps = computed(() => {
  105. const map = new Map();
  106. const overlaps = props.overlaps ?? [];
  107. for (const overlap of overlaps.filter(item => item.includes(':'))) {
  108. const [top, bottom] = overlap.split(':');
  109. if (!registered.value.includes(top) || !registered.value.includes(bottom)) continue;
  110. const topPosition = positions.get(top);
  111. const bottomPosition = positions.get(bottom);
  112. const topAmount = layoutSizes.get(top);
  113. const bottomAmount = layoutSizes.get(bottom);
  114. if (!topPosition || !bottomPosition || !topAmount || !bottomAmount) continue;
  115. map.set(bottom, {
  116. position: topPosition.value,
  117. amount: parseInt(topAmount.value, 10)
  118. });
  119. map.set(top, {
  120. position: bottomPosition.value,
  121. amount: -parseInt(bottomAmount.value, 10)
  122. });
  123. }
  124. return map;
  125. });
  126. const layers = computed(() => {
  127. const uniquePriorities = [...new Set([...priorities.values()].map(p => p.value))].sort((a, b) => a - b);
  128. const layout = [];
  129. for (const p of uniquePriorities) {
  130. const items = registered.value.filter(id => priorities.get(id)?.value === p);
  131. layout.push(...items);
  132. }
  133. return generateLayers(layout, positions, layoutSizes, activeItems);
  134. });
  135. const transitionsEnabled = computed(() => {
  136. return !Array.from(disabledTransitions.values()).some(ref => ref.value);
  137. });
  138. const mainRect = computed(() => {
  139. return layers.value[layers.value.length - 1].layer;
  140. });
  141. const mainStyles = computed(() => {
  142. return {
  143. '--v-layout-left': convertToUnit(mainRect.value.left),
  144. '--v-layout-right': convertToUnit(mainRect.value.right),
  145. '--v-layout-top': convertToUnit(mainRect.value.top),
  146. '--v-layout-bottom': convertToUnit(mainRect.value.bottom),
  147. ...(transitionsEnabled.value ? undefined : {
  148. transition: 'none'
  149. })
  150. };
  151. });
  152. const items = computed(() => {
  153. return layers.value.slice(1).map((_ref, index) => {
  154. let {
  155. id
  156. } = _ref;
  157. const {
  158. layer
  159. } = layers.value[index];
  160. const size = layoutSizes.get(id);
  161. const position = positions.get(id);
  162. return {
  163. id,
  164. ...layer,
  165. size: Number(size.value),
  166. position: position.value
  167. };
  168. });
  169. });
  170. const getLayoutItem = id => {
  171. return items.value.find(item => item.id === id);
  172. };
  173. const rootVm = getCurrentInstance('createLayout');
  174. const isMounted = shallowRef(false);
  175. onMounted(() => {
  176. isMounted.value = true;
  177. });
  178. provide(VuetifyLayoutKey, {
  179. register: (vm, _ref2) => {
  180. let {
  181. id,
  182. order,
  183. position,
  184. layoutSize,
  185. elementSize,
  186. active,
  187. disableTransitions,
  188. absolute
  189. } = _ref2;
  190. priorities.set(id, order);
  191. positions.set(id, position);
  192. layoutSizes.set(id, layoutSize);
  193. activeItems.set(id, active);
  194. disableTransitions && disabledTransitions.set(id, disableTransitions);
  195. const instances = findChildrenWithProvide(VuetifyLayoutItemKey, rootVm?.vnode);
  196. const instanceIndex = instances.indexOf(vm);
  197. if (instanceIndex > -1) registered.value.splice(instanceIndex, 0, id);else registered.value.push(id);
  198. const index = computed(() => items.value.findIndex(i => i.id === id));
  199. const zIndex = computed(() => rootZIndex.value + layers.value.length * 2 - index.value * 2);
  200. const layoutItemStyles = computed(() => {
  201. const isHorizontal = position.value === 'left' || position.value === 'right';
  202. const isOppositeHorizontal = position.value === 'right';
  203. const isOppositeVertical = position.value === 'bottom';
  204. const styles = {
  205. [position.value]: 0,
  206. zIndex: zIndex.value,
  207. transform: `translate${isHorizontal ? 'X' : 'Y'}(${(active.value ? 0 : -110) * (isOppositeHorizontal || isOppositeVertical ? -1 : 1)}%)`,
  208. position: absolute.value || rootZIndex.value !== ROOT_ZINDEX ? 'absolute' : 'fixed',
  209. ...(transitionsEnabled.value ? undefined : {
  210. transition: 'none'
  211. })
  212. };
  213. if (!isMounted.value) return styles;
  214. const item = items.value[index.value];
  215. if (!item) throw new Error(`[Vuetify] Could not find layout item "${id}"`);
  216. const overlap = computedOverlaps.value.get(id);
  217. if (overlap) {
  218. item[overlap.position] += overlap.amount;
  219. }
  220. return {
  221. ...styles,
  222. height: isHorizontal ? `calc(100% - ${item.top}px - ${item.bottom}px)` : elementSize.value ? `${elementSize.value}px` : undefined,
  223. left: isOppositeHorizontal ? undefined : `${item.left}px`,
  224. right: isOppositeHorizontal ? `${item.right}px` : undefined,
  225. top: position.value !== 'bottom' ? `${item.top}px` : undefined,
  226. bottom: position.value !== 'top' ? `${item.bottom}px` : undefined,
  227. width: !isHorizontal ? `calc(100% - ${item.left}px - ${item.right}px)` : elementSize.value ? `${elementSize.value}px` : undefined
  228. };
  229. });
  230. const layoutItemScrimStyles = computed(() => ({
  231. zIndex: zIndex.value - 1
  232. }));
  233. return {
  234. layoutItemStyles,
  235. layoutItemScrimStyles,
  236. zIndex
  237. };
  238. },
  239. unregister: id => {
  240. priorities.delete(id);
  241. positions.delete(id);
  242. layoutSizes.delete(id);
  243. activeItems.delete(id);
  244. disabledTransitions.delete(id);
  245. registered.value = registered.value.filter(v => v !== id);
  246. },
  247. mainRect,
  248. mainStyles,
  249. getLayoutItem,
  250. items,
  251. layoutRect,
  252. rootZIndex
  253. });
  254. const layoutClasses = computed(() => ['v-layout', {
  255. 'v-layout--full-height': props.fullHeight
  256. }]);
  257. const layoutStyles = computed(() => ({
  258. zIndex: rootZIndex.value,
  259. position: parentLayout ? 'relative' : undefined,
  260. overflow: parentLayout ? 'hidden' : undefined
  261. }));
  262. return {
  263. layoutClasses,
  264. layoutStyles,
  265. getLayoutItem,
  266. items,
  267. layoutRect,
  268. layoutRef: resizeRef
  269. };
  270. }
  271. //# sourceMappingURL=layout.mjs.map