docks_theme_api 1.0.2

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 (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;