1 | import { window } from "./platform"
|
2 | import { find, equalRecords } from "./utils"
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const axes = {
|
13 | row: {},
|
14 | column: {},
|
15 | }
|
16 |
|
17 | axes.row.main = {
|
18 | start: "x",
|
19 | end: "x2",
|
20 | size: "w",
|
21 | }
|
22 | axes.row.cross = {
|
23 | start: "y",
|
24 | end: "y2",
|
25 | size: "h",
|
26 | }
|
27 | axes.column.main = axes.row.cross
|
28 | axes.column.cross = axes.row.main
|
29 |
|
30 |
|
31 |
|
32 | const types = [
|
33 | { name: "side", values: [ "start", "end" ]},
|
34 | { name: "standing", values: [ "above", "right", "below", "left" ]},
|
35 | { name: "flow", values: [ "column", "row" ]},
|
36 | ]
|
37 |
|
38 | const validTypeValues = (
|
39 | types.reduce(
|
40 | (xs, { values }) => (xs.concat(values)),
|
41 | []
|
42 | )
|
43 | )
|
44 |
|
45 | const centerOfSize = (flow, axis, size) => (
|
46 | size[axes[flow][axis].size] / 2
|
47 | )
|
48 |
|
49 | const centerOfBounds = (flow, axis, bounds) => (
|
50 | bounds[axes[flow][axis].start] + (bounds[axes[flow][axis].size] / 2)
|
51 | )
|
52 |
|
53 | const centerOfBoundsFromBounds = (flow, axis, boundsTo, boundsFrom) => (
|
54 | centerOfBounds(flow, axis, boundsTo) - boundsFrom[axes[flow][axis].start]
|
55 | )
|
56 |
|
57 | const place = (flow, axis, align, bounds, size) => {
|
58 | const axisProps = axes[flow][axis]
|
59 | return (
|
60 | align === "center"
|
61 | ? centerOfBounds(flow, axis, bounds) - centerOfSize(flow, axis, size)
|
62 | : align === "end"
|
63 | ? bounds[axisProps.end]
|
64 | : align === "start"
|
65 | |
66 |
|
67 |
|
68 | ? bounds[axisProps.start] - size[axisProps.size]
|
69 | : null
|
70 | )
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | const El = {}
|
78 |
|
79 | El.calcBounds = (el) => {
|
80 |
|
81 | if (el === window) {
|
82 | return {
|
83 | x: 0,
|
84 | y: 0,
|
85 | x2: el.innerWidth,
|
86 | y2: el.innerHeight,
|
87 | w: el.innerWidth,
|
88 | h: el.innerHeight,
|
89 | }
|
90 | }
|
91 |
|
92 | const b = el.getBoundingClientRect()
|
93 |
|
94 | return {
|
95 | x: b.left,
|
96 | y: b.top,
|
97 | x2: b.right,
|
98 | y2: b.bottom,
|
99 | w: b.right - b.left,
|
100 | h: b.bottom - b.top,
|
101 | }
|
102 | }
|
103 |
|
104 | El.calcSize = (el) => (
|
105 | el === window ?
|
106 | { w: el.innerWidth, h: el.innerHeight } :
|
107 | { w: el.offsetWidth, h: el.offsetHeight }
|
108 | )
|
109 |
|
110 | El.calcScrollSize = (el) => (
|
111 | el === window ?
|
112 | {
|
113 | w: el.scrollX || el.pageXOffset,
|
114 | h: el.scrollY || el.pageYOffset,
|
115 | } :
|
116 | { w: el.scrollLeft, h: el.scrollTop }
|
117 | )
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | const getPreferenceType = (preference) => (
|
124 | types.reduce((found, type) => (
|
125 | found ?
|
126 | found :
|
127 | type.values.indexOf(preference) !== -1 ?
|
128 | type.name :
|
129 | null
|
130 | ), null)
|
131 | )
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | const fitWithinChecker = (dimension) => (domainSize, itemSize) => (
|
138 | domainSize[dimension] >= itemSize[dimension]
|
139 | )
|
140 |
|
141 | const doesWidthFitWithin = fitWithinChecker("w")
|
142 | const doesHeightFitWithin = fitWithinChecker("h")
|
143 |
|
144 | const doesFitWithin = (domainSize, itemSize) => (
|
145 | doesWidthFitWithin(domainSize, itemSize)
|
146 | && doesHeightFitWithin(domainSize, itemSize)
|
147 | )
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | const createPreferenceError = (givenValue) => (
|
154 | new Error(
|
155 | `The given layout placement of "${givenValue}" is not a valid choice. Valid choices are: ${validTypeValues.join(" | ")}.`
|
156 | )
|
157 | )
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | const pickZone = (opts, frameBounds, targetBounds, size) => {
|
167 | const t = targetBounds
|
168 | const f = frameBounds
|
169 | const zones = [
|
170 | { side: "start", standing: "above", flow: "column", order: -1, w: f.x2, h: t.y },
|
171 | { side: "end", standing: "right", flow: "row", order: 1, w: (f.x2 - t.x2), h: f.y2 },
|
172 | { side: "end", standing: "below", flow: "column", order: 1, w: f.x2, h: (f.y2 - t.y2) },
|
173 | { side: "start", standing: "left", flow: "row", order: -1, w: t.x, h: f.y2 },
|
174 | ]
|
175 |
|
176 | |
177 |
|
178 |
|
179 |
|
180 |
|
181 | zones.forEach((z) => {
|
182 |
|
183 |
|
184 | z.cutOff = - Math.max(0, Math.min(z.w,size.w)) * Math.max(0, Math.min(z.h,size.h))
|
185 | })
|
186 | zones.sort((a,b) => a.cutOff - b.cutOff)
|
187 |
|
188 | const availZones = zones.filter((zone) => (
|
189 | doesFitWithin(zone, size)
|
190 | ))
|
191 |
|
192 |
|
193 |
|
194 | if (opts.place) {
|
195 | const type = getPreferenceType(opts.place)
|
196 | if (!type) throw createPreferenceError(opts.place)
|
197 | const finder = (z) => z[type] === opts.place
|
198 | return find(finder, availZones) || find(finder, zones)
|
199 | }
|
200 |
|
201 | |
202 |
|
203 |
|
204 |
|
205 | if (opts.preferPlace) {
|
206 | const preferenceType = getPreferenceType(opts.preferPlace)
|
207 | if (!preferenceType) throw createPreferenceError(opts.preferPlace)
|
208 |
|
209 |
|
210 | const preferredAvailZones = availZones.filter((zone) => (
|
211 | zone[preferenceType] === opts.preferPlace
|
212 | ))
|
213 | if (preferredAvailZones.length) return preferredAvailZones[0]
|
214 |
|
215 |
|
216 |
|
217 | const preferredZones = zones.filter((zone) => (
|
218 | zone[preferenceType] === opts.preferPlace
|
219 | ))
|
220 | if (!availZones.length && preferredZones.length) return preferredZones[0]
|
221 | }
|
222 |
|
223 |
|
224 | return availZones.length ? availZones[0] : zones[0]
|
225 | }
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | const calcRelPos = (zone, masterBounds, slaveSize) => {
|
232 | const { main, cross } = axes[zone.flow]
|
233 |
|
234 | const crossAlign = "center"
|
235 | const mainStart = place(zone.flow, "main", zone.side, masterBounds, slaveSize)
|
236 | const mainSize = slaveSize[main.size]
|
237 | const crossStart = place(zone.flow, "cross", crossAlign, masterBounds, slaveSize)
|
238 | const crossSize = slaveSize[cross.size]
|
239 |
|
240 | return {
|
241 | [main.start]: mainStart,
|
242 | mainLength: mainSize,
|
243 | [main.end]: mainStart + mainSize,
|
244 | [cross.start]: crossStart,
|
245 | crossLength: crossSize,
|
246 | [cross.end]: crossStart + crossSize,
|
247 | }
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 | export default {
|
253 | El,
|
254 | types,
|
255 | validTypeValues,
|
256 | calcRelPos,
|
257 | place,
|
258 | pickZone,
|
259 | axes,
|
260 | centerOfSize,
|
261 | centerOfBounds,
|
262 | centerOfBoundsFromBounds,
|
263 | doesFitWithin,
|
264 | equalCoords: equalRecords,
|
265 | }
|
266 | export {
|
267 | El,
|
268 | types,
|
269 | validTypeValues,
|
270 | calcRelPos,
|
271 | place,
|
272 | pickZone,
|
273 | axes,
|
274 | centerOfSize,
|
275 | centerOfBounds,
|
276 | centerOfBoundsFromBounds,
|
277 | doesFitWithin,
|
278 | equalRecords as equalCoords,
|
279 | }
|