docks_theme_api 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. checksums.yaml +15 -0
  2. data/.babelrc +4 -0
  3. data/.editorconfig +8 -0
  4. data/.eslintrc +115 -0
  5. data/.gitignore +24 -0
  6. data/.rubocop.yml +20 -0
  7. data/.travis.yml +16 -0
  8. data/Gemfile +4 -0
  9. data/README.md +5 -0
  10. data/Rakefile +3 -0
  11. data/assets/images/icons.svg +63 -0
  12. data/assets/scripts/coffeescript/pattern_library_helpers.coffee +8 -0
  13. data/assets/scripts/javascript/pattern_library_helpers.js +11 -0
  14. data/assets/scripts/pattern_library.js +10380 -0
  15. data/assets/scripts/pattern_library_demo.js +0 -0
  16. data/assets/styles/less/pattern-library-helpers.less +103 -0
  17. data/assets/styles/pattern-library-demo.css +1882 -0
  18. data/assets/styles/pattern-library.css +1882 -0
  19. data/assets/styles/sass/pattern-library-helpers.sass +90 -0
  20. data/assets/styles/scss/pattern-library-helpers.scss +99 -0
  21. data/assets/styles/stylus/pattern-library-helpers.styl +90 -0
  22. data/assets/templates/erb/demo.erb +26 -0
  23. data/assets/templates/erb/layouts/demo.erb +17 -0
  24. data/assets/templates/erb/layouts/pattern.erb +76 -0
  25. data/assets/templates/erb/partials/sidebar.erb +124 -0
  26. data/assets/templates/erb/partials/symbols/class.erb +1 -0
  27. data/assets/templates/erb/partials/symbols/demo.erb +40 -0
  28. data/assets/templates/erb/partials/symbols/factory.erb +70 -0
  29. data/assets/templates/erb/partials/symbols/function.erb +103 -0
  30. data/assets/templates/erb/partials/symbols/mixin.erb +62 -0
  31. data/assets/templates/erb/partials/symbols/variable.erb +59 -0
  32. data/assets/templates/erb/pattern.erb +102 -0
  33. data/assets/templates/haml/demo.haml +14 -0
  34. data/assets/templates/haml/layouts/demo.haml +6 -0
  35. data/assets/templates/haml/layouts/pattern.haml +38 -0
  36. data/assets/templates/haml/partials/sidebar.haml +68 -0
  37. data/assets/templates/haml/partials/symbols/class.haml +1 -0
  38. data/assets/templates/haml/partials/symbols/demo.haml +23 -0
  39. data/assets/templates/haml/partials/symbols/factory.haml +38 -0
  40. data/assets/templates/haml/partials/symbols/function.haml +54 -0
  41. data/assets/templates/haml/partials/symbols/mixin.haml +31 -0
  42. data/assets/templates/haml/partials/symbols/variable.haml +22 -0
  43. data/assets/templates/haml/pattern.haml +54 -0
  44. data/assets/templates/slim/demo.slim +24 -0
  45. data/assets/templates/slim/layouts/demo.slim +5 -0
  46. data/assets/templates/slim/layouts/pattern.slim +48 -0
  47. data/assets/templates/slim/partials/sidebar.slim +112 -0
  48. data/assets/templates/slim/partials/symbols/class.slim +1 -0
  49. data/assets/templates/slim/partials/symbols/demo.slim +30 -0
  50. data/assets/templates/slim/partials/symbols/factory.slim +57 -0
  51. data/assets/templates/slim/partials/symbols/function.slim +81 -0
  52. data/assets/templates/slim/partials/symbols/mixin.slim +45 -0
  53. data/assets/templates/slim/partials/symbols/variable.slim +35 -0
  54. data/assets/templates/slim/pattern.slim +63 -0
  55. data/docks_config.rb +32 -0
  56. data/docks_theme_api.gemspec +37 -0
  57. data/gulpfile.js +88 -0
  58. data/karma.conf.js +6 -0
  59. data/lib/docks_theme_api/components/base_component.rb +99 -0
  60. data/lib/docks_theme_api/components/code_block_component.rb +10 -0
  61. data/lib/docks_theme_api/components/popover_component.rb +15 -0
  62. data/lib/docks_theme_api/components/table_component.rb +34 -0
  63. data/lib/docks_theme_api/components/tablist_component.rb +11 -0
  64. data/lib/docks_theme_api/components.rb +21 -0
  65. data/lib/docks_theme_api/helpers/ui_helper.rb +69 -0
  66. data/lib/docks_theme_api/theme.rb +21 -0
  67. data/lib/docks_theme_api.rb +1 -0
  68. data/package.json +60 -0
  69. data/source/behaviors/filterable/filterable.coffee +353 -0
  70. data/source/behaviors/filterable/filterable.js +0 -0
  71. data/source/behaviors/filterable/filterable.scss +34 -0
  72. data/source/behaviors/filterable/package.json +3 -0
  73. data/source/behaviors/index.js +0 -0
  74. data/source/components/avatar/avatar.erb +20 -0
  75. data/source/components/avatar/avatar.js +142 -0
  76. data/source/components/avatar/avatar.scss +200 -0
  77. data/source/components/avatar/avatar_container.erb +13 -0
  78. data/source/components/avatar/package.json +3 -0
  79. data/source/components/avatar/spec/avatar_spec.js +81 -0
  80. data/source/components/badge/badge.scss +158 -0
  81. data/source/components/button/button.scss +213 -0
  82. data/source/components/card/card.scss +32 -0
  83. data/source/components/code_block/code-block.scss +353 -0
  84. data/source/components/code_block/code_block.erb +95 -0
  85. data/source/components/code_block/code_block.js +444 -0
  86. data/source/components/code_block/package.json +3 -0
  87. data/source/components/code_block/spec/code_block_spec.js +10 -0
  88. data/source/components/demo/demo.js +244 -0
  89. data/source/components/demo/demo.scss +90 -0
  90. data/source/components/demo/package.json +3 -0
  91. data/source/components/exploded/exploded.erb +25 -0
  92. data/source/components/exploded/exploded.js +694 -0
  93. data/source/components/exploded/exploded.scss +166 -0
  94. data/source/components/exploded/package.json +3 -0
  95. data/source/components/field/field.js +24 -0
  96. data/source/components/field/field.scss +101 -0
  97. data/source/components/field/package.json +3 -0
  98. data/source/components/header/header.scss +33 -0
  99. data/source/components/iframe/iframe.erb +12 -0
  100. data/source/components/iframe/iframe.js +381 -0
  101. data/source/components/iframe/package.json +3 -0
  102. data/source/components/index.js +37 -0
  103. data/source/components/inline_group/inline-group.scss +14 -0
  104. data/source/components/internal_link/internal_link.js +49 -0
  105. data/source/components/internal_link/package.json +3 -0
  106. data/source/components/list/list.scss +230 -0
  107. data/source/components/modal/modal.coffee +84 -0
  108. data/source/components/modal/modal.erb +19 -0
  109. data/source/components/modal/modal.js +0 -0
  110. data/source/components/modal/modal.scss +57 -0
  111. data/source/components/modal/package.json +3 -0
  112. data/source/components/notice/notice.scss +48 -0
  113. data/source/components/popover/package.json +3 -0
  114. data/source/components/popover/popover.coffee +562 -0
  115. data/source/components/popover/popover.erb +21 -0
  116. data/source/components/popover/popover.js +0 -0
  117. data/source/components/popover/popover.scss +139 -0
  118. data/source/components/range/range.scss +78 -0
  119. data/source/components/resizable/package.json +3 -0
  120. data/source/components/resizable/resizable.erb +30 -0
  121. data/source/components/resizable/resizable.js +250 -0
  122. data/source/components/resizable/resizable.scss +245 -0
  123. data/source/components/resizable/size_buttons.js +249 -0
  124. data/source/components/scroll_container/package.json +3 -0
  125. data/source/components/scroll_container/scroll-container.scss +4 -0
  126. data/source/components/scroll_container/scroll_container.js +24 -0
  127. data/source/components/section/section.scss +99 -0
  128. data/source/components/select/package.json +3 -0
  129. data/source/components/select/select.erb +21 -0
  130. data/source/components/select/select.js +35 -0
  131. data/source/components/select/select.scss +163 -0
  132. data/source/components/table/package.json +3 -0
  133. data/source/components/table/table.erb +16 -0
  134. data/source/components/table/table.js +351 -0
  135. data/source/components/table/table.scss +236 -0
  136. data/source/components/tablist/package.json +3 -0
  137. data/source/components/tablist/tablist.erb +13 -0
  138. data/source/components/tablist/tablist.js +246 -0
  139. data/source/components/tablist/tablist.scss +191 -0
  140. data/source/components/tablist/tablist_panel.erb +14 -0
  141. data/source/components/tablist/tablist_tab.erb +20 -0
  142. data/source/components/toggle/package.json +3 -0
  143. data/source/components/toggle/toggle.erb +11 -0
  144. data/source/components/toggle/toggle.js +211 -0
  145. data/source/components/toggle/toggle_container.erb +30 -0
  146. data/source/components/vertical_spacer/vertical-spacer.scss +3 -0
  147. data/source/components/vertical_stack/vertical-stack.scss +19 -0
  148. data/source/components/xray/package.json +3 -0
  149. data/source/components/xray/xray.erb +50 -0
  150. data/source/components/xray/xray.js +123 -0
  151. data/source/components/xray/xray.scss +79 -0
  152. data/source/foundation/app/app.js +15 -0
  153. data/source/foundation/app/package.json +3 -0
  154. data/source/pattern-library-demo.scss +13 -0
  155. data/source/pattern-library.scss +13 -0
  156. data/source/pattern_library.js +8 -0
  157. data/source/pattern_library_demo.js +8 -0
  158. data/source/structures/index.js +11 -0
  159. data/source/structures/sidebar/package.json +3 -0
  160. data/source/structures/sidebar/sidebar.js +69 -0
  161. data/source/structures/sidebar/sidebar.scss +79 -0
  162. data/source/utilities/builder/builder.js +138 -0
  163. data/source/utilities/builder/package.json +3 -0
  164. data/source/utilities/client/client.js +7 -0
  165. data/source/utilities/client/package.json +3 -0
  166. data/source/utilities/colors/colors.scss +112 -0
  167. data/source/utilities/defaults/defaults.scss +38 -0
  168. data/source/utilities/dom_cache/dom_cache.js +24 -0
  169. data/source/utilities/dom_cache/package.json +3 -0
  170. data/source/utilities/events/events.js +25 -0
  171. data/source/utilities/events/package.json +3 -0
  172. data/source/utilities/font_sizes/font-sizes.scss +85 -0
  173. data/source/utilities/foundation/a11y.scss +10 -0
  174. data/source/utilities/foundation/base.scss +29 -0
  175. data/source/utilities/foundation/icon.scss +114 -0
  176. data/source/utilities/foundation/layout.scss +67 -0
  177. data/source/utilities/foundation/page.scss +39 -0
  178. data/source/utilities/foundation/type.scss +208 -0
  179. data/source/utilities/functions/functions.scss +127 -0
  180. data/source/utilities/keycodes/keycodes.js +23 -0
  181. data/source/utilities/keycodes/package.json +3 -0
  182. data/source/utilities/markup/markup.js +90 -0
  183. data/source/utilities/markup/package.json +3 -0
  184. data/source/utilities/media/media.scss +172 -0
  185. data/source/utilities/mixins/mixins.scss +89 -0
  186. data/source/utilities/naming_convention/naming_convention.js +3 -0
  187. data/source/utilities/naming_convention/package.json +3 -0
  188. data/source/utilities/numbers/numbers.js +14 -0
  189. data/source/utilities/numbers/package.json +3 -0
  190. data/source/utilities/painting/package.json +3 -0
  191. data/source/utilities/painting/painting.js +7 -0
  192. data/source/utilities/pattern/package.json +3 -0
  193. data/source/utilities/pattern/pattern.js +50 -0
  194. data/source/utilities/query_string/package.json +3 -0
  195. data/source/utilities/query_string/query_string.js +24 -0
  196. data/source/utilities/template/package.json +3 -0
  197. data/source/utilities/template/template.js +10 -0
  198. data/source/utilities/text_range/package.json +3 -0
  199. data/source/utilities/text_range/text_range.js +30 -0
  200. data/source/utilities/ui_events/package.json +3 -0
  201. data/source/utilities/ui_events/ui_events.js +85 -0
  202. data/source/utilities/variables/variables.scss +18 -0
  203. data/source/utilities/z_indexes/z-indexes.scss +88 -0
  204. data/source/vendor/array_includes.js +28 -0
  205. data/source/vendor/highlight.js +1142 -0
  206. data/source/vendor/index.js +1 -0
  207. data/source/vendor/matrix.js +399 -0
  208. data/source/vendor/query_string.js +66 -0
  209. data/spec/assets/.eslintrc +9 -0
  210. data/spec/assets/spec_fixture.js +33 -0
  211. data/spec/assets/spec_helper.js +19 -0
  212. data/spec/lib/components/base_component_spec.rb +156 -0
  213. data/spec/lib/components_spec.rb +30 -0
  214. data/spec/lib/helpers/ui_helper_spec.rb +62 -0
  215. data/spec/lib/theme_spec.rb +25 -0
  216. data/spec/spec_helper.rb +15 -0
  217. data/tasks/gulp/.eslintrc +6 -0
  218. data/tasks/gulp/browser_sync.js +8 -0
  219. data/tasks/gulp/code_quality/scripts.js +10 -0
  220. data/tasks/gulp/config/index.js +116 -0
  221. data/tasks/gulp/minify/scripts.js +13 -0
  222. data/tasks/gulp/minify/styles.js +13 -0
  223. data/tasks/gulp/pattern_library/index.js +5 -0
  224. data/tasks/gulp/pattern_library/scripts.js +10 -0
  225. data/tasks/gulp/pattern_library/styles.js +10 -0
  226. data/tasks/gulp/scripts.js +8 -0
  227. data/tasks/gulp/spec/scripts.js +11 -0
  228. data/tasks/gulp/styles.js +17 -0
  229. data/tasks/gulp/utilities/babel/relative_require.js +22 -0
  230. data/tasks/gulp/utilities/babel/spec_helper.js +20 -0
  231. data/tasks/gulp/utilities/browserify_bundler.js +22 -0
  232. data/tasks/gulp/utilities/handle_errors.js +13 -0
  233. data/tasks/gulp/watch.js +9 -0
  234. data/tasks/rake/rspec.rake +7 -0
  235. data/tasks/rake/rubocop.rake +8 -0
  236. data/tasks/rake/templates.rake +50 -0
  237. metadata +470 -0
@@ -0,0 +1,694 @@
1
+ // ___ ___ ___ ___ _____ ___ _____
2
+ // / /\ /__/| / /\ / /\ / /::\ / /\ / /::\
3
+ // / /:/_ | |:| / /::\ / /::\ / /:/\:\ / /:/_ / /:/\:\
4
+ // / /:/ /\ | |:| / /:/\:\___ ___ / /:/\:\ / /:/ \:\ / /:/ /\ / /:/ \:\
5
+ // / /:/ /:/_ __|__|:| / /:/~/:/__/\ / /\/ /:/ \:\/__/:/ \__\:|/ /:/ /:/_/__/:/ \__\:|
6
+ // /__/:/ /:/ /\/__/::::\___/__/:/ /:/\ \:\ / /:/__/:/ \__\:\ \:\ / /:/__/:/ /:/ /\ \:\ / /:/
7
+ // \ \:\/:/ /:/ ~\~~\::::| \:\/:/ \ \:\ /:/\ \:\ / /:/\ \:\ /:/\ \:\/:/ /:/\ \:\ /:/
8
+ // \ \::/ /:/ |~~|:|~~ \ \::/ \ \:\/:/ \ \:\ /:/ \ \:\/:/ \ \::/ /:/ \ \:\/:/
9
+ // \ \:\/:/ | |:| \ \:\ \ \::/ \ \:\/:/ \ \::/ \ \:\/:/ \ \::/
10
+ // \ \::/ | |:| \ \:\ \__\/ \ \::/ \__\/ \ \::/ \__\/
11
+ // \__\/ |__|/ \__\/ \__\/ \__\/
12
+
13
+
14
+ import UIEvents from "../../utilities/ui_events";
15
+ import Client from "../../utilities/client";
16
+ import Builder from "../../utilities/builder";
17
+ import { between, Matrix } from "../../utilities/numbers";
18
+ import { force_repaint } from "../../utilities/painting";
19
+
20
+ const classes = {
21
+ root: "exploded",
22
+ structure: "exploded__structure",
23
+ content: "exploded__structure__content",
24
+ source: "exploded__source",
25
+ pane: "exploded__pane"
26
+ };
27
+
28
+ const states = {
29
+ root: {
30
+ initialized: `${classes.root}--is-being-initialized`
31
+ },
32
+
33
+ pane: {
34
+ hovered: `${classes.pane}--is-hovered`,
35
+ selected: `${classes.pane}--is-selected`
36
+ }
37
+ };
38
+
39
+ const attrs = {
40
+ id: "data-explosion-id",
41
+ node: "data-explosion-node",
42
+ range_attr: "data-explosion-attribute"
43
+ };
44
+
45
+ const events = {
46
+ pane_selected: `${classes.root}:pane-selected`
47
+ };
48
+
49
+ const LAYER_GAP = 40;
50
+
51
+ var clone, initialize_panes, initialize_ranges, reset, start_dragging,
52
+ rotate_by, update_panes, node_for_pane, main_class_for_node, Exploded;
53
+
54
+ //*
55
+ // Initializes the panes for an explosion. This does all of the required
56
+ // cloning, stores the resulting panes on the secrets object, and performs an
57
+ // initial rotation.
58
+ //
59
+ // @param {Object} self - The internal details of an `Exploded`.
60
+ // @private
61
+
62
+ initialize_panes = async function(self) {
63
+ var { source, structure } = self;
64
+ if(source.children[0].children.length < 1) { return; }
65
+
66
+ reset(self);
67
+
68
+ self.panes = clone(source.children[0], structure.children[0]);
69
+ self.spread = 1;
70
+
71
+ setTimeout(async function() {
72
+ await UIEvents.transition(self.node, () => {
73
+ self.node.classList.add(states.root.initialized);
74
+ rotate_by(20, 5, self);
75
+ });
76
+
77
+ self.node.classList.remove(states.root.initialized);
78
+ }, 400);
79
+ };
80
+
81
+ //*
82
+ // Initializes the ranges within an `Exploded` to perform their action. This
83
+ // function also contains the definitions of the possible actions for a range,
84
+ // the `attrs.range_attr` value that will give that behavior to a range, and
85
+ // the actual event handlers for when the range changes values.
86
+ //
87
+ // @param {Object} self - The internal details of an `Exploded`.
88
+ // @private
89
+
90
+ initialize_ranges = (() => {
91
+ var actions, ranges, percentage_from_center, handlers, create_range_listener;
92
+
93
+ actions = {
94
+ gap: "pane-gap",
95
+ perspective: "perspective"
96
+ };
97
+
98
+ ranges = {
99
+ [actions.gap]: { min: 0.25, max: 2, default: 1 },
100
+ [actions.perspective]: { min: 500, max: 4000, default: 2000 }
101
+ };
102
+
103
+ //*
104
+ // Calculates the difference a value from 0-100 is from 50, then normalizes that
105
+ // value for how close it is to the center. So, values close to 50 will be close,
106
+ // to 0, while 0 and 100 will be -1 and 1, respectively.
107
+ //
108
+ // @param {Number} value - The number on a scale of 0-100.
109
+ // @private
110
+ // @returns Number
111
+
112
+ percentage_from_center = (value) => {
113
+ return ((parseInt(value, 10) / 100) - 0.5) / 0.5;
114
+ };
115
+
116
+ handlers = {
117
+
118
+ //*
119
+ // @name gap
120
+ //
121
+ // Updates the spread between panes. 50% on the range will generate a z-axis
122
+ // distance between child/ parent panes of `LAYER_GAP`. Anything less than
123
+ // 50% will reduce this gap, and anything greater than 50% will increase it.
124
+ //
125
+ // @param {Object} self - The internal details of an `Exploded`.
126
+ // @param {Object} event - The `input` event on a range input.
127
+ //
128
+ // @private
129
+
130
+ [actions.gap]: function(self, event) {
131
+ var range = ranges[actions.gap],
132
+ spread_from_center = percentage_from_center(event.target.value);
133
+
134
+ if(spread_from_center < 0) {
135
+ self.spread = range.default + (spread_from_center * (range.default - range.min));
136
+ } else {
137
+ self.spread = range.default + (spread_from_center * (range.max - range.default));
138
+ }
139
+
140
+ update_panes(self);
141
+ },
142
+
143
+ //*
144
+ // @name perspective
145
+ //
146
+ // Updates the perspective of an `Exploded`. 50% on the range will generate
147
+ // the default perspective, and values lower and higher will decrease and
148
+ // increase that perspective, respectively.
149
+ //
150
+ // @param {Object} self - The internal details of an `Exploded`.
151
+ // @param {Object} event - The `input` event on a range input.
152
+ //
153
+ // @private
154
+
155
+ [actions.perspective]: function(self, event) {
156
+ var range = ranges[actions.perspective],
157
+ spread_from_center = percentage_from_center(event.target.value),
158
+ perspective;
159
+
160
+ if(spread_from_center > 0) {
161
+ perspective = range.default - (spread_from_center * (range.default - range.min));
162
+ } else {
163
+ perspective = range.default - (spread_from_center * (range.max - range.default));
164
+ }
165
+
166
+ self.structure.style.perspective = `${perspective}px`;
167
+ }
168
+ };
169
+
170
+ create_range_listener = (action) => {
171
+ return (event) => { handlers[action](self, event); };
172
+ };
173
+
174
+ return function(self) {
175
+ var range_node;
176
+
177
+ for(let name of Object.keys(actions)) {
178
+ let action = actions[name];
179
+ range_node = self.node.querySelector(`[${attrs.range_attr}="${action}"]`);
180
+
181
+ if(!range_node) { continue; }
182
+ range_node.addEventListener("input", create_range_listener(action));
183
+ }
184
+ };
185
+ })();
186
+
187
+ //*
188
+ // Resets the internal state of an `Exploded`.
189
+ //
190
+ // @param {Object} self - The internal details of an `Exploded`.
191
+ // @private
192
+
193
+ reset = (self) => {
194
+ self.rotation = { x: 0, y: 0, z: 0 };
195
+ self.source.style.display = null;
196
+ self.structure.children[0].innerHTML = "";
197
+ };
198
+
199
+ //*
200
+ // Creates the clone representations of the content of `from` into the container
201
+ // `to`. This is done by determining the position of each descendant of `from`
202
+ // relative to the `from` container itself, and then absolutely positioning an
203
+ // `exploded__pane` at the same relative position in `to`. In order to present
204
+ // a useful diagram, the DOM level of each node is captured and is used to stack
205
+ // the panes in the z-axis. Additionally, any overlap between siblings should
206
+ // be recorded and resolved by adding a small adjustment to the z-index
207
+ // stacking of those panes.
208
+ //
209
+ // @param {HTMLElement} from - The node containing the source DOM tree.
210
+ // @param {HTMLElement} to - The node in which to create the cloned presentation.
211
+ //
212
+ // @private
213
+ //
214
+ // @returns Array
215
+ // An array of objects representing the cloned panes. Each object has a `node`,
216
+ // `level`, and `adjustment` property so that future translations can be done
217
+ // performantly.
218
+
219
+ clone = (() => {
220
+ var explosion_id = 0,
221
+ destination, pane_count, widths,
222
+ clone_level, original_offset, panes,
223
+ prepare_for_cloning, append_clone, append_all_clones,
224
+ clone_node, find_overlaps, stack_siblings;
225
+
226
+ //*
227
+ // Resets the internal information used to perform explosions.
228
+ //
229
+ // @private
230
+
231
+ prepare_for_cloning = () => {
232
+ explosion_id += 1;
233
+ pane_count = 0;
234
+ clone_level = 0;
235
+ panes = [];
236
+ destination = null;
237
+ original_offset = null;
238
+
239
+ widths = {
240
+ min: Infinity,
241
+ max: 0
242
+ };
243
+ };
244
+
245
+ //*
246
+ // Appends a new pane with the provided dimensions to the `to` node.
247
+ //
248
+ // @param {Object} dims - The dimensions of the cloned node. Should have
249
+ // `width`, `height`, `left`, `top`, `level`, and
250
+ // `adjustment` properties.
251
+ // @param {HTMLElement} to - The node in which to append the new pane.
252
+ //
253
+ // @private
254
+ // @returns HTMLElement - The cloned node.
255
+
256
+ append_clone = (dims, to) => {
257
+ var parent_width = destination.offsetWidth,
258
+ parent_height = destination.offsetHeight,
259
+ node = $(`<div class='${classes.pane}' style='height: ${dims.height}px; width: ${dims.width}px; top: ${dims.top}px; left: ${dims.left}px; transform-origin: ${(parent_width / 2) - dims.left}px ${(parent_height / 2) - dims.top}px ${LAYER_GAP}px;' />`)[0];
260
+
261
+ to.appendChild(node);
262
+ return node;
263
+ };
264
+
265
+ //*
266
+ // Appends all of the required panes to the `to` node passed to
267
+ // [`clone`](@link).
268
+ //
269
+ // @private
270
+
271
+ append_all_clones = () => {
272
+ var fragment = document.createDocumentFragment(), pane;
273
+
274
+ for(pane of panes) {
275
+ pane.clone = append_clone(pane, fragment);
276
+ pane.clone.setAttribute(attrs.node, pane.id);
277
+ pane.clone.style.zIndex = (LAYER_GAP * pane.level) + (pane.adjustment || 0);
278
+ pane.node.setAttribute(attrs.id, pane.id);
279
+ }
280
+
281
+ destination.appendChild(fragment);
282
+ };
283
+
284
+ //*
285
+ // Generates the details required to clone a node as a pane. These include
286
+ // its dimensions, its ID, the node it is cloning, its level, and whether or
287
+ // not it is actually visible. These are added to the closured `panes` array
288
+ // so that they can be easily accessed by other methods.
289
+ //
290
+ // @param {HTMLElement} node - The source node to clone.
291
+ // @private
292
+
293
+ clone_node = (node) => {
294
+ var node_offsets = node.getBoundingClientRect(),
295
+ pane, child;
296
+
297
+ original_offset = original_offset || node.parentNode.getBoundingClientRect();
298
+ pane_count += 1;
299
+
300
+ // If we have a visible node
301
+ if((node_offsets.height + node_offsets.width) > 2) {
302
+ pane = {
303
+ height: node_offsets.height,
304
+ width: node_offsets.width,
305
+ top: node_offsets.top - original_offset.top,
306
+ left: node_offsets.left - original_offset.left,
307
+ level: clone_level,
308
+ node: node,
309
+ id: `${explosion_id}-${pane_count}`
310
+ };
311
+
312
+ panes.push(pane);
313
+
314
+ widths.min = Math.min(pane.left, widths.min);
315
+ widths.max = Math.max(pane.left + pane.width, widths.max);
316
+ }
317
+
318
+ clone_level += 1;
319
+ for(child of Array.from(node.children)) { clone_node(child); }
320
+ clone_level -= 1;
321
+ };
322
+
323
+ //*
324
+ // Finds pairs of nodes whose dimensions overlap one another.
325
+ //
326
+ // @param {Array} siblings - The set of nodes to check for overlap.
327
+ // @private
328
+ // @returns Array - An array of arrays that each contain a set of two
329
+ // overlapping nodes.
330
+
331
+ find_overlaps = (siblings) => {
332
+ var overlaps = [],
333
+ sibling_count = siblings.length,
334
+ index, sibling,
335
+ other_index, other,
336
+ within_other,
337
+ other_within, custom_between;
338
+
339
+ custom_between = (...args) => { return between(...args, { include_min: true }); };
340
+
341
+ for(index = 0; index < sibling_count; index++) {
342
+ sibling = siblings[index];
343
+
344
+ for(other_index = index + 1; other_index < sibling_count; other_index++) {
345
+ other = siblings[other_index];
346
+
347
+ other_within = custom_between(other.left, sibling.left, sibling.left + sibling.width) &&
348
+ custom_between(other.top, sibling.top, sibling.top + sibling.height);
349
+
350
+ within_other = custom_between(sibling.left, other.left, other.left + other.width) &&
351
+ custom_between(sibling.top, other.top, other.top + other.height);
352
+
353
+ if(other_within || within_other) { overlaps.push([sibling, other]); }
354
+ }
355
+ }
356
+
357
+ return overlaps;
358
+ };
359
+
360
+ //*
361
+ // Creates the necessary adjustments to provide z-space between siblings that
362
+ // would otherwise overlap one another (that is, on the same level with some
363
+ // overlapping coordinates). These adjustments are added directly to the
364
+ // objects in the closured `panes` array.
365
+ //
366
+ // @private
367
+
368
+ stack_siblings = () => {
369
+ var levels = [],
370
+ overlaps, pane, level, overlap;
371
+
372
+ for(pane of panes) {
373
+ levels[pane.level] = levels[pane.level] || [];
374
+ levels[pane.level].push(pane);
375
+ }
376
+
377
+ for(level of levels) {
378
+ overlaps = find_overlaps(level);
379
+
380
+ for(overlap of overlaps) {
381
+ overlap[0].adjustment = -LAYER_GAP / 8;
382
+ overlap[1].adjustment = LAYER_GAP / 8;
383
+ }
384
+ }
385
+ };
386
+
387
+ return (from, to) => {
388
+ var clone_results = [],
389
+ child, pane;
390
+
391
+ prepare_for_cloning();
392
+ destination = to;
393
+
394
+ for(child of Array.from(from.children)) { clone_node(child); }
395
+ stack_siblings();
396
+ append_all_clones();
397
+
398
+ to.style.maxWidth = `${widths.max - widths.min}px`;
399
+ to.style.height = `${from.offsetHeight}px`;
400
+ from.parentNode.style.display = "none";
401
+
402
+ for(pane in panes) {
403
+ clone_results.push({
404
+ node: pane.clone,
405
+ level: pane.level,
406
+ adjustment: pane.adjustment || 0
407
+ });
408
+ }
409
+
410
+ return clone_results;
411
+ };
412
+ })();
413
+
414
+ //*
415
+ // Attaches the events required to handle touches and clicks on the exploded
416
+ // structure. If the click ends before the user drags the `DRAG_THRESHOLD`
417
+ // distance, the action will be treated as a click and the pane on which the
418
+ // user clicked will be selected. If the user drags more than the threshold,
419
+ // the entire structure will be rotated according to the distance dragged.
420
+ //
421
+ // @param {Object} self - The internal details of an `Exploded`.
422
+ // @param {Object} event - The initial `mousedown`/ `touchdown` event.
423
+ //
424
+ // @private
425
+ // @returns Object - The listener object with a `remove` method that allows the
426
+ // drag to be cleanly cancelled.
427
+
428
+ start_dragging = (self, start_event) => {
429
+ var old_coordinates = UIEvents.coordinates(start_event),
430
+ drag_threshold_met = false,
431
+ drag, drag_end;
432
+
433
+ start_event.preventDefault();
434
+
435
+ drag = (event) => {
436
+ var new_coordinates = UIEvents.coordinates(event);
437
+ event.preventDefault();
438
+
439
+ if(drag_threshold_met) {
440
+ document.body.style.pointerEvents = "none";
441
+ rotate_by((new_coordinates.x - old_coordinates.x) / 2, (new_coordinates.y - old_coordinates.y) / 2, self);
442
+ } else {
443
+ drag_threshold_met = UIEvents.coordinates.distance_between(old_coordinates, new_coordinates) > UIEvents.DRAG_THRESHOLD;
444
+ }
445
+ };
446
+
447
+ drag_end = (event) => {
448
+ if(!drag_threshold_met && event.target.classList.contains(classes.pane)) {
449
+ self.select_pane(event.target);
450
+ }
451
+
452
+ // TODO: Maybe move to helper?
453
+ document.body.style.pointerEvents = null;
454
+ };
455
+
456
+ return UIEvents.add_drag_listeners(drag, drag_end);
457
+ };
458
+
459
+ //*
460
+ // Rotates the panes of an `Exploded` by the passed x and y degrees.
461
+ //
462
+ // @param {Number} x - The degrees in the x-axis to rotate the panes.
463
+ // @param {Number} y - The degrees in the x-axis to rotate the panes. Note that
464
+ // this will be reversed so that the rotation feels natural.
465
+ // @param {Object} self - The internal details of an `Exploded`.
466
+ //
467
+ // @private
468
+
469
+ rotate_by = (x, y, self) => {
470
+ self.rotation.x = Math.max(Math.min(90, (self.rotation.x + x) % 360), -90);
471
+ self.rotation.y = Math.max(Math.min(90, (self.rotation.y + y) % 360), -90);
472
+ update_panes(self);
473
+ };
474
+
475
+ //*
476
+ // Applies the current rotation to all panes within an `Exploded`. It will also
477
+ // make sure that the z-translation of each pane is correct given its level in
478
+ // the original source tree and its stacking order against its siblings.
479
+ //
480
+ // @param {Object} self - The internal details of an `Exploded`.
481
+ // @private
482
+
483
+ update_panes = (self) => {
484
+ var { x, y } = self.rotation,
485
+ identity_matrix = Matrix(),
486
+ rotation_matrix = identity_matrix.rotate(-y, x, 0),
487
+ updates = [],
488
+ transform = Client.name_for("transform"),
489
+ z_translate, pane;
490
+
491
+ for(pane of self.panes) {
492
+ if(!pane.node) { continue; }
493
+
494
+ z_translate = ((pane.level * LAYER_GAP) + pane.adjustment) * self.spread;
495
+ updates.push({
496
+ node: pane.node,
497
+ transform: rotation_matrix.translate(0, 0, z_translate).toString()
498
+ });
499
+ }
500
+
501
+ requestAnimationFrame(() => {
502
+ var update;
503
+
504
+ for(update of updates) {
505
+ update.node.style[transform] = update.transform;
506
+ }
507
+ });
508
+ };
509
+
510
+ //*
511
+ // Returns the original node (from the source content) that corresponds to the
512
+ // passed pane.
513
+ //
514
+ // @param {HTMLElement} pane - The exploded pane for which a corresponding source
515
+ // node should be found.
516
+ // @private
517
+ // @returns {HTMLElement | undefined} - The corresponding node or, if none exists,
518
+ // undefined.
519
+
520
+ node_for_pane = (pane) => {
521
+ var node_id = pane.getAttribute(attrs.node);
522
+ if(!node_id) { throw new Error(`The passed node must have an "${attrs.node}" attribute.`); }
523
+ return document.querySelector(`[${attrs.id}='${node_id}']`);
524
+ };
525
+
526
+ // TODO: get this out of here.
527
+
528
+ //*
529
+ // Gets the main class name for a given node.
530
+ //
531
+ // @param {HTMLElement} node - The node to retrieve the main class name for.
532
+ //
533
+ // @private
534
+ // @returns String
535
+
536
+ main_class_for_node = (node) => {
537
+ return node.getAttribute("class").split(" ")[0];
538
+ };
539
+
540
+ //*
541
+ // The constructor around an explosion.
542
+ //
543
+ // @factory
544
+ //
545
+ // @param {HTMLElement} node - The base explosion node.
546
+ //
547
+ // @returns Object - The API for manipulating this explosion, including methods
548
+ // to update the markup to demonstrate, selecting particular
549
+ // panes or all panes for particular components, and adding
550
+ // callbacks to pane selection.
551
+
552
+ Exploded = (node) => {
553
+ var self, api,
554
+ set_markup, select_pane, select_component, on;
555
+
556
+ self = {
557
+ node: node,
558
+ // TODO: write a simpler method for finding all occurances of a class
559
+ structure: node.querySelector(`.${classes.structure}`),
560
+ source: node.querySelector(`.${classes.source}`)
561
+ };
562
+
563
+ //*
564
+ // Clears the existing explosion and re-initalizes the component with the new
565
+ // markup.
566
+ //
567
+ // @method
568
+ //
569
+ // @param {String} markup - The new markup to demonstrate.
570
+
571
+ set_markup = (markup) => {
572
+ self.source.children[0].innerHTML = markup;
573
+ force_repaint(node);
574
+ initialize_panes(self);
575
+ };
576
+
577
+ //*
578
+ // Selects a given pane and emits the selected event. This event can be
579
+ // picked up by other components so that they can display useful information
580
+ // related to this pane. See [`on_pane_select`](@link Exploded#on_pane_select) for details
581
+ // on attaching listeners to pane selection.
582
+ //
583
+ // @method
584
+ //
585
+ // @param {HTMLElement} pane - The selected pane.
586
+
587
+ select_pane = (pane) => {
588
+ var panes = Array.isArray(pane) ? pane : [pane],
589
+ event, related_node;
590
+
591
+ requestAnimationFrame(() => {
592
+ for(pane of self.panes) { pane.node.classList.remove(states.pane.selected); }
593
+ for(pane of panes) { pane.classList.add(states.pane.selected); }
594
+ });
595
+
596
+ if(!panes.length) { return; }
597
+ pane = panes[0];
598
+ related_node = node_for_pane(pane);
599
+
600
+ // The event provides the selected pane, the related (source) node, and
601
+ // the class of the node for easy component identification.
602
+ // TODO: clean this up, kill $
603
+ event = $.Event(events.pane_selected, {
604
+ node: related_node,
605
+ pane: pane,
606
+ component: main_class_for_node(related_node)
607
+ });
608
+
609
+ $(node).trigger(event);
610
+ };
611
+
612
+ //*
613
+ // Selects the pane that corresponds to the provided component.
614
+ //
615
+ // @method
616
+ //
617
+ // @param {String} component - The class name of the component to select.
618
+
619
+ select_component = (component) => {
620
+ var panes = [],
621
+ components = self.source.querySelectorAll(`.${component}`),
622
+ explosion_id, pane, event;
623
+
624
+ for(component of components) {
625
+ explosion_id = component.getAttribute(attrs.id);
626
+ pane = node.querySelector(`[${attrs.node}="${explosion_id}"]`);
627
+ if(pane) { panes.push(pane); }
628
+ }
629
+
630
+ select_pane(panes);
631
+
632
+ // Event won't get triggered by select_pane. Do it here instead.
633
+ if(components.length && !panes.length) {
634
+ event = $.Event(events.pane_selected, {
635
+ node: components[0],
636
+ component: components[0].getAttribute("class").split(" ")[0]
637
+ });
638
+
639
+ $(node).trigger(event);
640
+ }
641
+ };
642
+
643
+ //*
644
+ // A helper method to attach an event listener to the `Exploded`.
645
+ //
646
+ // @method
647
+ //
648
+ // @param {String} event - The event to listen for.
649
+ // @param {Function} callback - The callback function.
650
+ // @param {Object} callback.event
651
+ // The event object. Most importantly, the `detail` property of this object
652
+ // contains the source `node`, the selected `pane`, and the name of the
653
+ // `component` that was selected.
654
+ //
655
+ // @returns Object - An object that allows you to easily remove the listener
656
+ // with the `#remove` method.
657
+
658
+ on = (event, callback) => {
659
+ var $node = $(node);
660
+ $node.on(event, callback);
661
+
662
+ return {
663
+ remove() { $node.off(event, callback); }
664
+ };
665
+ };
666
+
667
+ api = { select_pane, select_component, set_markup, on };
668
+ Object.assign(self, api);
669
+
670
+ initialize_panes(self);
671
+ initialize_ranges(self);
672
+
673
+ self.structure.querySelector(`.${classes.content}`).addEventListener(UIEvents.drag.start, (event) => {
674
+ start_dragging(self, event);
675
+ });
676
+
677
+ return api;
678
+ };
679
+
680
+ //*
681
+ // Initializes the `Exploded` component.
682
+ //
683
+ // @method
684
+ // @static
685
+ //
686
+ // @param {HTMLElement} [context = document] - The context in which to search
687
+ // for DOM nodes that should be used as the root of an `Exploded` component.
688
+
689
+ Exploded.init = () => {
690
+ Builder.build_and_cache(Exploded, { name: classes.root });
691
+ };
692
+
693
+ export { classes, states, attrs, events };
694
+ export default Exploded;