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,444 @@
1
+ // See: http://updates.html5rocks.com/2015/04/cut-and-copy-commands
2
+
3
+ // ___ ___ _____ ___
4
+ // / /\ / /\ / /::\ / /\
5
+ // / /:/ / /::\ / /:/\:\ / /:/_
6
+ // / /:/ / /:/\:\ / /:/ \:\ / /:/ /\
7
+ // / /:/ ___ / /:/ \:\/__/:/ \__\:|/ /:/ /:/_
8
+ // /__/:/ / /\/__/:/ \__\:\ \:\ / /:/__/:/ /:/ /\
9
+ // \ \:\ / /:/\ \:\ / /:/\ \:\ /:/\ \:\/:/ /:/
10
+ // \ \:\ /:/ \ \:\ /:/ \ \:\/:/ \ \::/ /:/
11
+ // \ \:\/:/ \ \:\/:/ \ \::/ \ \:\/:/
12
+ // \ \::/ \ \::/ \__\/ \ \::/
13
+ // \__\/ \__\/ \__\/
14
+
15
+ import ScrollContainer from "../scroll_container";
16
+ import Events from "../../utilities/events";
17
+ import UIEvents from "../../utilities/ui_events";
18
+ import Range from "../../utilities/text_range";
19
+ import Builder from "../../utilities/builder";
20
+ import { Communicator } from "../iframe";
21
+ import { classes as select_classes } from "../select";
22
+ import { indent, clean, highlight } from "../../utilities/markup";
23
+ import { force_repaint as repaint } from "../../utilities/painting";
24
+
25
+ const classes = {
26
+ root: "code-block",
27
+ header: "code-block__header",
28
+ code: "code-block__code",
29
+ select: "code-block__demo-selector",
30
+ code_container: "code-block__code-container",
31
+ toggler: "code-block__toggler",
32
+ content: "code-block__content",
33
+ iframe: "code-block__iframe",
34
+ demo_content: "code-block__demo__content"
35
+ };
36
+
37
+ const variants = {
38
+ root: { with_demo: `${classes.root}--with-demo` }
39
+ };
40
+
41
+ const states = {
42
+ root: { hidden: `${classes.root}--is-hidden` }
43
+ };
44
+
45
+ const attrs = {
46
+ language: "data-code-block-language",
47
+ cached_max_height: "data-cached-max-height"
48
+ };
49
+
50
+ var CodeBlock, CodeCaches,
51
+ clean_and_highlight_code, update_helper, toggle_code_block_visibility,
52
+ select_code, hide, show, cache_content_height, hook_up_iframe_communication,
53
+ attach_event_listeners;
54
+
55
+ //*
56
+ // Cleans a string of code and updates the string with syntax highlighting
57
+ // markup.
58
+ //
59
+ // @param {String} code - The raw code.
60
+ //
61
+ // @param {Object} [options = {}] - The options for the highlighting operation.
62
+ //
63
+ // @param {String} [options.language_code = "html"]
64
+ // The language of the passed code. This is used by the syntax highlighter to
65
+ // pick the appropriate highlighter.
66
+ //
67
+ // @param {Boolean} [options.collapse_newlines = false]
68
+ // Whether or not to combine multiple consecutive newlines into a single newline.
69
+ //
70
+ // @private
71
+ // @returns String - The cleaned and syntax-highlighted string.
72
+
73
+ clean_and_highlight_code = (code, options = {}) => {
74
+ var { language_code } = options;
75
+
76
+ code = clean(code, options);
77
+ if(!language_code || language_code === "html") { code = indent(code); }
78
+ return highlight(code, options);
79
+ };
80
+
81
+ //*
82
+ // Updates helper code (that is, a template language that generates markup) for
83
+ // changes in classes that have corresponding attributes in the helper markup.
84
+ // It does this by searching through the helper markup for the symbol that sets
85
+ // a given class (the `setter`), and then assigns that a value depending on the
86
+ // nature of the change.
87
+ //
88
+ // - If there is no `constant` for the change, the value of the `setter` is
89
+ // assumed to be `true` if the class is active and `false` otherwise.
90
+ //
91
+ // - If there is a `constant`, this value is used when a class is added. The
92
+ // cache is required to store values for when a `setter` with a constant is
93
+ // removed — the value of the `setter` is then returned to the previous
94
+ // `constant`, which is stored in the cache.
95
+ //
96
+ // @param {String} code - The raw code.
97
+ // @param {Object} change - The details about the class change. This should
98
+ // include whether the class was added or removed and
99
+ // all of the `setters` for the corresponding variation.
100
+ // @param {Object} cache - The cache of previous constant values.
101
+ //
102
+ // @private
103
+ // @returns String - The helper code with the relevant attributes updated.
104
+
105
+ update_helper = (code, change, cache) => {
106
+ var add, helper_param, constant, helper_matcher, regex,
107
+ constants_for_param, index, replace_value, set_by,
108
+ constant_replacer, boolean_replacer;
109
+
110
+ add = !!change.add;
111
+
112
+ constant_replacer = (match, param_portion, constant_portion) => {
113
+ cache[helper_param] = cache[helper_param] || [constant_portion];
114
+
115
+ if(change.method === "add") {
116
+ cache[helper_param].push(constant);
117
+ return `${param_portion}${constant}`;
118
+
119
+ } else {
120
+ constants_for_param = cache[helper_param];
121
+ if(!constants_for_param) { return match; }
122
+
123
+ index = constants_for_param.indexOf(constant);
124
+ if(index >= 0) { constants_for_param.splice(index, 1); }
125
+
126
+ replace_value = constants_for_param[constants_for_param.length - 1];
127
+ return `${param_portion}${replace_value}`;
128
+ }
129
+ };
130
+
131
+ boolean_replacer = (match, param_portion) => {
132
+ return `${param_portion}${add ? "true" : "false"}`;
133
+ };
134
+
135
+ if(!change.set_by) { return code; }
136
+
137
+ for(set_by of change.set_by) {
138
+ constant = set_by.constant || "";
139
+ helper_param = set_by.setter;
140
+ helper_matcher = `:?\"?${helper_param.replace(":", "").replace("?", "\\?")}\"?:?\\s*(?:=>\\s*)?`;
141
+
142
+ if(constant) {
143
+ // If a value was actually declared for the set_by, find the current constant
144
+ // and replace it as needed
145
+ // key: VALUE, :key => VALUE, "key" => VALUE, :"key" => VALUE
146
+
147
+ regex = new RegExp(`(${helper_matcher})([a-zA-Z\\-_:]*)`);
148
+ code = code.replace(regex, constant_replacer);
149
+
150
+ } else {
151
+ // No constant declared, assume it is true/ false
152
+ regex = new RegExp(`(${helper_matcher})(true|false)`);
153
+ code = code.replace(regex, boolean_replacer);
154
+ }
155
+ }
156
+
157
+ return code;
158
+ };
159
+
160
+ //*
161
+ // Handles a click on the contained `button` that toggles the visibility of the
162
+ // code block.
163
+ //
164
+ // @private
165
+ // @param {Object} event - The `click` event on the select.
166
+
167
+ toggle_code_block_visibility = (event) => {
168
+ var code_block = CodeBlock.for(event.target);
169
+ if(!code_block) { return; }
170
+ code_block.toggle();
171
+ };
172
+
173
+ //*
174
+ // Handles a focus on the code area of a code block by selecting all of the
175
+ // text within the code block.
176
+ //
177
+ // @private
178
+ // @param {Object} event - The `focusin` event on the code.
179
+
180
+ select_code = () => {
181
+ Range(this).select_all();
182
+ };
183
+
184
+ $(document).on("click", `.${classes.toggler}`, toggle_code_block_visibility);
185
+ $(document).on("click", `.${classes.code}`, select_code);
186
+
187
+ //*
188
+ // Hides a code block.
189
+ //
190
+ // @param {Object} self - The internal details of a [`CodeBlock`](@link).
191
+ // @param {Object} options ({}) - The options for how the code block should be
192
+ // hidden. Currently, only the `without_transition` (which hides automatically
193
+ // rather than scaling the height of the code block) option is supported.
194
+ //
195
+ // @private
196
+
197
+ hide = (self, options = {}) => {
198
+ var { node, toggler, content } = self,
199
+ { without_transition } = options,
200
+ scroll_container;
201
+
202
+ ScrollContainer.init();
203
+ scroll_container = ScrollContainer.for(node);
204
+ if(scroll_container) { scroll_container.maintain_current_height(); }
205
+
206
+ node.classList.add(states.root.hidden);
207
+ if(toggler) { toggler.querySelector("span").textContent = "Show"; }
208
+
209
+ content.style.transition = "none";
210
+
211
+ if(!without_transition) {
212
+ content.style.height = `${Math.min(content.scrollHeight, parseInt(content.getAttribute(attrs.cached_max_height), 10))}px`;
213
+ repaint(content);
214
+ content.style.transition = null;
215
+ }
216
+
217
+ repaint(content);
218
+ content.style.height = "0px";
219
+
220
+ if(without_transition) {
221
+ repaint(content);
222
+ content.style.transition = null;
223
+ }
224
+
225
+ self.is_hidden = true;
226
+ };
227
+
228
+ //*
229
+ // Shows a code block.
230
+ //
231
+ // @param {Object} self - The internal details of a [`CodeBlock`](@link).
232
+ //
233
+ // @private
234
+
235
+ show = async function(self) {
236
+ var { node, toggler, content } = self;
237
+
238
+ node.classList.remove(states.root.hidden);
239
+ self.is_hidden = false;
240
+ if(toggler) { toggler.querySelector("span").textContent = "Hide"; }
241
+
242
+ await UIEvents.transition(content, function() {
243
+ content.style.height = `${Math.min(content.scrollHeight, parseInt(content.getAttribute(attrs.cached_max_height), 10))}px`;
244
+ });
245
+
246
+ content.style.height = null;
247
+ };
248
+
249
+ //*
250
+ // Caches the max height of the main content area of a code block. This is done
251
+ // so that the transition from hidden to shown caps out at the `max-height`
252
+ // specified in CSS.
253
+ //
254
+ // In order to allow the code areas to scroll, an appropriate max-height is also
255
+ // set on them.
256
+ //
257
+ // @param {Object} self - The internal details of a [`CodeBlock`](@link).
258
+ //
259
+ // @private
260
+
261
+ cache_content_height = (self) => {
262
+ var { node, content } = self,
263
+ max_height, header, header_height, max_code_height, code_container;
264
+
265
+ max_height = parseInt(window.getComputedStyle(content).maxHeight, 10);
266
+
267
+ content.setAttribute(attrs.cached_max_height, max_height);
268
+
269
+ header = node.querySelector(`.${classes.header}`);
270
+ header_height = (header ? header.offsetHeight : 0);
271
+ max_code_height = `${max_height - header_height}px`;
272
+
273
+ for(code_container of Array.from(node.querySelectorAll(`.${classes.code_container}`))) {
274
+ code_container.style.maxHeight = max_code_height;
275
+ }
276
+ };
277
+
278
+ //*
279
+ // Does all of the necessary work to manage the two-way communication between
280
+ // a code block connected to an `iframe` and that `iframe`. This includes
281
+ // listening for changes to markup of the associated demo and triggering an
282
+ // intial markup request to get the most up-to-date representation possible.
283
+ //
284
+ // @param {Object} self - The internal details of a [`CodeBlock`](@link).
285
+ //
286
+ // @private
287
+
288
+ hook_up_iframe_communication = (self) => {
289
+ var communicator = Communicator(),
290
+ registered = communicator.register.from_node(self.node),
291
+ handle_markup_change, handle_class_change;
292
+
293
+ if(!registered) { return false; }
294
+
295
+ handle_markup_change = (event) => {
296
+ if(!event.html || !self.code_caches.markup) { return; }
297
+ self.code_caches.markup.code = event.html;
298
+ };
299
+
300
+ handle_class_change = (event) => {
301
+ if(!self.code_caches.helper) { return; }
302
+ if(event.details.add === undefined) { event.details.add = event.add; }
303
+ self.code_caches.helper.update(event.details);
304
+ };
305
+
306
+ communicator.on(Events.types.markup_request, handle_markup_change);
307
+ communicator.on(Events.types.markup_change, handle_markup_change);
308
+ communicator.on(Events.types.class_change, handle_class_change);
309
+
310
+ communicator.trigger(Events.types.markup_request);
311
+ return communicator;
312
+ };
313
+
314
+ attach_event_listeners = (self) => {
315
+ var select = self.node.querySelector(`.${select_classes.root}`);
316
+
317
+ if(select && self.communicator) {
318
+ select.addEventListener("change", function(event) {
319
+ self.communicator.trigger(Events.types.markup_request, {
320
+ demo: event.target.value
321
+ });
322
+ });
323
+ }
324
+ };
325
+
326
+ //*
327
+ // An API for cacheing, updating, and highlighting code within a code block.
328
+ //
329
+ // @param {HTMLElement} node - The main code block.
330
+ //
331
+ // @private
332
+ // @factory
333
+
334
+ CodeCaches = (() => {
335
+ const languages = {
336
+ markup: ["html"],
337
+ helper: ["erb", "haml", "slim"]
338
+ };
339
+
340
+ var CodeCache;
341
+
342
+ CodeCache = (node, options = {}) => {
343
+ var language = node.getAttribute(attrs.language) || "html",
344
+ dom_code = node.querySelector("code"),
345
+ code = dom_code.innerHTML,
346
+ helper_cache = null, code_cache;
347
+
348
+ code_cache = {
349
+ language: language,
350
+ highlight() { this.code = code; },
351
+ get code() { return code; },
352
+ set code(new_code) {
353
+ code = new_code;
354
+ dom_code.innerHTML = clean_and_highlight_code(new_code, {
355
+ language_code: language,
356
+ collapse_newlines: options.generated_from_helper
357
+ });
358
+ }
359
+ };
360
+
361
+ code_cache.highlight();
362
+
363
+ if(languages.helper.includes(language)) {
364
+ helper_cache = {};
365
+
366
+ Object.defineProperty(code_cache, "update", {
367
+ value: function(change) {
368
+ this.code = update_helper(this.code, change, helper_cache);
369
+ }
370
+ });
371
+ }
372
+
373
+ return code_cache;
374
+ };
375
+
376
+ return (node) => {
377
+ var code_nodes, code_caches, api, index;
378
+
379
+ code_nodes = Array.from(node.querySelectorAll(`.${classes.code}`));
380
+ code_caches = code_nodes.map((code_node) => {
381
+ return CodeCache(code_node, { generated_from_helper: code_nodes.length > 1 });
382
+ });
383
+
384
+ api = {
385
+ get markup() {
386
+ return code_caches.filter((code_cache) => languages.markup.includes(code_cache.language))[0];
387
+ },
388
+
389
+ get helper() {
390
+ return code_caches.filter((code_cache) => languages.helper.includes(code_cache.language))[0];
391
+ },
392
+
393
+ length: code_caches.length
394
+ };
395
+
396
+ for(index = 0; index < code_caches.length; index++) {
397
+ Object.defineProperty(api, index, { value: code_caches[index] });
398
+ }
399
+
400
+ return api;
401
+ };
402
+ })();
403
+
404
+ //*
405
+ // The constructor around a code block.
406
+ //
407
+ // @factory
408
+ // @public
409
+ //
410
+ // @param {HTMLElement} node - The node with the `code-block` root class.
411
+
412
+ CodeBlock = (node) => {
413
+ var self, api, toggle;
414
+
415
+ self = {
416
+ node: node,
417
+ is_hidden: node.classList.contains(states.root.hidden),
418
+ toggler: node.querySelector(`.${classes.toggler}`),
419
+ content: node.querySelector(`.${classes.content}`),
420
+ code_caches: CodeCaches(node)
421
+ };
422
+
423
+ self.communicator = hook_up_iframe_communication(self);
424
+
425
+ attach_event_listeners(self);
426
+
427
+ if(self.is_hidden) { hide(self, { without_transition: true }); }
428
+ if(self.toggler) { cache_content_height(self); }
429
+
430
+ //*
431
+ // Toggles the code block.
432
+ //
433
+ // @method
434
+
435
+ toggle = () => { return (self.is_hidden ? show(self) : hide(self)); };
436
+ api = { toggle };
437
+
438
+ return api;
439
+ };
440
+
441
+ CodeBlock.init = Builder.initialize_once(CodeBlock, { name: classes.root, cache: true });
442
+
443
+ export { classes, states, variants, attrs };
444
+ export default CodeBlock;
@@ -0,0 +1,3 @@
1
+ {
2
+ "main": "code_block.js"
3
+ }
@@ -0,0 +1,10 @@
1
+ import "spec_helper";
2
+ import CodeBlock from "~components/code_block";
3
+
4
+ describe("CodeBlock", () => {
5
+ describe(".init", () => {
6
+ it("has an init method", () => {
7
+ expect(CodeBlock.init).to.be.a.function;
8
+ });
9
+ });
10
+ });
@@ -0,0 +1,244 @@
1
+ import Events from "../../utilities/events";
2
+ import UIEvents from "../../utilities/ui_events";
3
+ import { Communicator } from "../iframe";
4
+
5
+ //*
6
+ // The name of classes relevant to `Demo`.
7
+ // @object
8
+
9
+ const classes = {
10
+ root: "demo",
11
+ section: "demo__section",
12
+ content: "content"
13
+ };
14
+
15
+ var Demo, create_self, set_correct_background_color, allocate_minimum_height;
16
+
17
+ //*
18
+ // The delay after a change in markup to keep track of height changes and
19
+ // communicate them to the attached [`Iframe`](@link).
20
+ //
21
+ // @type Number
22
+ // @value 1000
23
+
24
+ const HEIGHT_CHANGE_WATCH_DURATION = 1000;
25
+
26
+ //*
27
+ // Updates the background color of the parent for the demo to match the
28
+ // background color of the last section. This is necessary because, during the
29
+ // transition from a larger size to a smaller size, not doing this would show
30
+ // white below all of the demo sections regardless of their color.
31
+ //
32
+ // @private
33
+ // @param {HTMLElement} node - The base `Demo` node.
34
+
35
+ set_correct_background_color = (node) => {
36
+ var parent = node.parentNode,
37
+ sections = node.querySelectorAll(`.${classes.section}`),
38
+ last_section = sections[sections.length - 1];
39
+
40
+ parent.style.backgroundColor = window.getComputedStyle(last_section).backgroundColor;
41
+ };
42
+
43
+ //*
44
+ // Spreads the minimum height of the total demo between the sections that are
45
+ // present. This is important because the resizable demo will show the full
46
+ // minimum width, so if there are colored sections that don't completely fill
47
+ // the minimum width, there will be an awkward white patch below the sections.
48
+ //
49
+ // @private
50
+ // @param {HTMLElement} node - The base `Demo` node.
51
+
52
+ allocate_minimum_height = (node) => {
53
+ var min_height = parseInt(window.getComputedStyle(node).minHeight, 10),
54
+ demo_sections = node.querySelectorAll(`.${classes.section}`), demo_section;
55
+
56
+ for(demo_section of demo_sections) {
57
+ demo_section.style.minHeight = `${min_height / demo_sections.length}px`;
58
+ }
59
+ };
60
+
61
+ //*
62
+ // Caches all of the internal details for an [`Demo`](@link).
63
+ //
64
+ // @private
65
+ // @param {HTMLElement} node - The node backing the `Demo`.
66
+ // @returns Object - The private, internal details of the `Demo`.
67
+
68
+ create_self = (node) => {
69
+ return {
70
+ markup_source: document.querySelector(`.${classes.content}`),
71
+ demo_handlers: window.parent.demo_handlers || {},
72
+ parent: node.parentNode,
73
+ height: 0,
74
+ actions: {},
75
+ context: {
76
+ body: document.body,
77
+ document: document
78
+ }
79
+ };
80
+ };
81
+
82
+ //*
83
+ // The constructor for a new `Demo`. This will sign the demo up for all the
84
+ // required events and will do the required initialization work.
85
+ //
86
+ // @param {HTMLElement} node - The base `Demo` node.
87
+ //
88
+ // @factory
89
+
90
+ Demo = (node) => {
91
+ var self = create_self(node),
92
+ communicator = Communicator(),
93
+ send_markup, height_update, apply_class_change;
94
+
95
+ //*
96
+ // Sends the markup for the current "main" section.
97
+ //
98
+ // @param {Object} [event = {}] - The (optional) event that specifies the demo
99
+ // to send markup for.
100
+ //
101
+ // @method
102
+ // @private
103
+
104
+ send_markup = (event = {}) => {
105
+ if(event.demo) {
106
+ self.markup_source = document.querySelector(`#${classes.section}--${event.demo} .${classes.content}`);
107
+ }
108
+
109
+ communicator.trigger(Events.types.markup_request, {
110
+ html: self.markup_source.innerHTML
111
+ });
112
+ };
113
+
114
+ //*
115
+ // Sends the height for the demo as a whole, and sets that height on the
116
+ // demo's container. The height is set on the container after a delay to
117
+ // ensure that there is no patch of unstyled background color underneath a
118
+ // demo that is shrinking.
119
+ //
120
+ // @method
121
+ // @private
122
+
123
+ height_update = () => {
124
+ var new_height = node.offsetHeight;
125
+ if(new_height === self.height) { return; }
126
+
127
+ self.height = new_height;
128
+ setTimeout(() => {
129
+ self.parent.style.minHeight = `${new_height}px`;
130
+ }, HEIGHT_CHANGE_WATCH_DURATION);
131
+
132
+ communicator.trigger(Events.types.height_change, { height: new_height });
133
+ };
134
+
135
+ //*
136
+ // Applies a class change to the demo. This class change will avoid adding
137
+ // classes to components that have a class procluded from the new class, will
138
+ // filter to the passed filter, and will perform the optional JavaScript
139
+ // action instead of a simple class addition/ removal. If appropriate, the
140
+ // component will then return the class change event, send a markup change
141
+ // event, and send a height update event.
142
+ //
143
+ // @param {Object} event - The class change event.
144
+ // @private
145
+ //
146
+
147
+ apply_class_change = (event) => {
148
+ var details = event.details,
149
+ markup_change_in_source = false,
150
+ minimum_one_class_change = false,
151
+ matches = node.querySelectorAll(`.${classes.content} .${details.for}`),
152
+ bail_early, class_list, action, match, preclude;
153
+
154
+ if(details.filter_to) {
155
+ // Check on matches
156
+ matches = matches.filter((a_match) => a_match.matches(details.filter_to));
157
+ }
158
+
159
+ // Some height changes may occur over time. Watch for transitions
160
+ // and send height again on each transitionend event
161
+ //
162
+ // TODO: integrate better iframe resizing
163
+ // see: https://github.com/davidjbradshaw/iframe-resizer/tree/master/test
164
+
165
+ document.addEventListener(UIEvents.transition_end, height_update);
166
+
167
+ for(match of matches) {
168
+ bail_early = false;
169
+ class_list = match.classList;
170
+ action = null;
171
+
172
+ for(preclude of details.preclude) {
173
+ if(class_list.contains(preclude)) {
174
+ bail_early = true;
175
+ break;
176
+ }
177
+ }
178
+
179
+ if(bail_early) { continue; }
180
+
181
+ minimum_one_class_change = true;
182
+
183
+ action = details.javascript_action;
184
+ if(action) {
185
+ if(!event.add) {
186
+ action = action.replace(/addClass/g, "removeClass")
187
+ .replace(/classList\.add/g, "classList.remove")
188
+ .replace(/(true|false)/, { true: "false", false: "true" });
189
+ }
190
+
191
+ eval(action);
192
+ } else {
193
+ class_list[event.add ? "add" : "remove"](details.name);
194
+ }
195
+
196
+ // Only update markup in source when the markup source is above in the
197
+ // DOM tree.
198
+ markup_change_in_source = markup_change_in_source ||
199
+ $(match).closest(self.markup_source).length > 0;
200
+ }
201
+
202
+ if(markup_change_in_source) { send_markup(); }
203
+
204
+ if(minimum_one_class_change) {
205
+ // Pass along the class change event
206
+ communicator.trigger(event.type, event);
207
+ height_update();
208
+ }
209
+
210
+ setTimeout(() => {
211
+ document.removeEventListener(UIEvents.transition_end, height_update);
212
+ }, HEIGHT_CHANGE_WATCH_DURATION);
213
+ };
214
+
215
+ communicator.register.from_node(node);
216
+ communicator.on(Events.types.height_request, height_update);
217
+ communicator.on(Events.types.markup_request, send_markup);
218
+ communicator.on(Events.types.class_change, apply_class_change);
219
+
220
+ window.addEventListener("resize", height_update);
221
+ setInterval(height_update, HEIGHT_CHANGE_WATCH_DURATION);
222
+
223
+ height_update();
224
+ allocate_minimum_height(node);
225
+ set_correct_background_color(node);
226
+
227
+ return {};
228
+ };
229
+
230
+ //*
231
+ // Initializes the `Demo` component.
232
+ //
233
+ // @method
234
+ // @static
235
+ //
236
+ // @param {HTMLElement} [context = document] - The context in which to search
237
+ // for DOM nodes that should be used as the root of an `Demo` component.
238
+
239
+ Demo.init = (context = document) => {
240
+ var demo, demos = Array.from(context.querySelectorAll(`.${classes.root}`));
241
+ for(demo of demos) { Demo(demo); }
242
+ };
243
+
244
+ export default Demo;