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