frontpack 0.1.0

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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +51 -0
  4. data/Rakefile +5 -0
  5. data/app/assets/config/frontpack_manifest.js +1 -0
  6. data/app/assets/images/logo.svg +141 -0
  7. data/app/assets/javascripts/frontpack/auto-complete.js +173 -0
  8. data/app/assets/javascripts/frontpack/base-webcomponent.js +160 -0
  9. data/app/assets/javascripts/frontpack/expandable-list.js +99 -0
  10. data/app/assets/javascripts/frontpack/image-preview.js +160 -0
  11. data/app/assets/javascripts/frontpack/image-uploader.js +34 -0
  12. data/app/assets/javascripts/frontpack/localized-time.js +17 -0
  13. data/app/assets/javascripts/frontpack/modal-dialog.js +206 -0
  14. data/app/assets/javascripts/frontpack/shortcuts.js +63 -0
  15. data/app/assets/javascripts/frontpack/tab-control.js +77 -0
  16. data/app/assets/javascripts/frontpack/text-box.js +70 -0
  17. data/app/assets/javascripts/frontpack/toggle-button.js +73 -0
  18. data/app/assets/stylesheets/frontpack/000-reset.scss +15 -0
  19. data/app/assets/stylesheets/frontpack/010-config.scss +148 -0
  20. data/app/assets/stylesheets/frontpack/020-mixins.scss +218 -0
  21. data/app/assets/stylesheets/frontpack/030-consts.scss +13 -0
  22. data/app/assets/stylesheets/frontpack/031-margins.scss +5 -0
  23. data/app/assets/stylesheets/frontpack/100-layout.scss +113 -0
  24. data/app/assets/stylesheets/frontpack/101-container.scss +131 -0
  25. data/app/assets/stylesheets/frontpack/200-design.scss +75 -0
  26. data/app/assets/stylesheets/frontpack/201-box.scss +6 -0
  27. data/app/assets/stylesheets/frontpack/202-borders.scss +5 -0
  28. data/app/assets/stylesheets/frontpack/300-typography.scss +128 -0
  29. data/app/assets/stylesheets/frontpack/800-components.scss +85 -0
  30. data/app/assets/stylesheets/frontpack/801-card.scss +53 -0
  31. data/app/assets/stylesheets/frontpack/802-buttons.scss +58 -0
  32. data/app/assets/stylesheets/frontpack/803-form-controls.scss +161 -0
  33. data/app/assets/stylesheets/frontpack/804-table.scss +60 -0
  34. data/app/assets/stylesheets/frontpack/805-switch-toggle.scss +43 -0
  35. data/app/assets/stylesheets/frontpack/frontpack.scss +18 -0
  36. data/app/controllers/concerns/frontpack/searchable.rb +33 -0
  37. data/app/controllers/frontpack/autocomplete_controller.rb +33 -0
  38. data/app/helpers/frontpack/application_helper.rb +7 -0
  39. data/app/helpers/frontpack/form_builder.rb +130 -0
  40. data/app/models/concerns/frontpack/auto_completable.rb +35 -0
  41. data/config/importmap.rb +1 -0
  42. data/config/routes.rb +3 -0
  43. data/lib/frontpack/button_to_patch.rb +64 -0
  44. data/lib/frontpack/engine.rb +28 -0
  45. data/lib/frontpack/extended_model_translations.rb +31 -0
  46. data/lib/frontpack/form_builder_options.rb +5 -0
  47. data/lib/frontpack/version.rb +5 -0
  48. data/lib/frontpack.rb +9 -0
  49. data/lib/generators/frontpack/install_generator.rb +37 -0
  50. data/lib/generators/frontpack/locale_generator.rb +11 -0
  51. data/lib/generators/templates/initializers/customize_form_with_errors.rb +31 -0
  52. data/lib/generators/templates/locales/frontpack.en.yml +43 -0
  53. data/lib/generators/templates/views/layouts/_form-errors.html.slim +3 -0
  54. data/lib/generators/templates/views/layouts/_navigation.html.slim +1 -0
  55. data/lib/generators/templates/views/layouts/application.html.slim +37 -0
  56. data/lib/generators/templates/views/layouts/errors.html.slim +19 -0
  57. data/lib/generators/templates/views/layouts/mailer.html.slim +6 -0
  58. data/lib/generators/templates/views/layouts/mailer.text.slim +1 -0
  59. data/lib/tasks/frontpack_tasks.rake +6 -0
  60. data/lib/templates/rails/file_utils/searchable.rb +30 -0
  61. data/lib/templates/rails/scaffold_controller/controller.rb.tt +93 -0
  62. data/lib/templates/slim/scaffold/_form.html.slim.tt +10 -0
  63. data/lib/templates/slim/scaffold/edit.html.slim.tt +10 -0
  64. data/lib/templates/slim/scaffold/index.html.slim.tt +20 -0
  65. data/lib/templates/slim/scaffold/new.html.slim.tt +8 -0
  66. data/lib/templates/slim/scaffold/partial.html.slim.tt +6 -0
  67. data/lib/templates/slim/scaffold/show.html.slim.tt +12 -0
  68. data/lib/templates/test_unit/model/fixtures.yml.tt +18 -0
  69. data/lib/templates/test_unit/model/unit_test.rb.tt +41 -0
  70. metadata +143 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df0faa6eaf72f4c463429cf921ca5b4787a86a824b495605e2749ef02808099a
4
+ data.tar.gz: 4196fb91c2dfdf7ffb84cb3fb51e69d6e066e204dc3f216e1f8968ea2deb917b
5
+ SHA512:
6
+ metadata.gz: 0a6b40d4a9068b6b320db01776623f4b3fff5366c3926efb301bd5cd2e0e5c7c0c1a39aa9f967393f2a9e27e06ee3703e0719d9cffd1b46eb341189725c7f6c1
7
+ data.tar.gz: 479e6c6cf85251fb224891e46213a6552d67d4bd074bbd493a952bf6acfd558ce44e3de972886434110887c1f17b92068af7b83cc3ec98e4c02676c05a22bc14
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Francis Schiavo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Frontpack
2
+
3
+ This project aims to provide a standard front-end development environment for Ruby on Rails applications.
4
+
5
+ Key features:
6
+ * New form builder with options for enum fields, toggle fields, and autocomplete fields.
7
+ * New methods for ActiveRecord models to easily translate enum field names and values.
8
+ * New methods for ActiveRecord models to easily expose autocomplete endpoints.
9
+ * Integrated sass files for customizing the front end (kinda of a mix including some boostrap and tailwind features).
10
+ * Integrated javascript files and web components for customizing the front end and form builder.
11
+
12
+ ## Goals
13
+
14
+ Allow even faster development of Rails applications by adding more features for front-end.
15
+ Some of the features will be simple extensions for Rails form builders.
16
+
17
+ Other features will be more complex, like having a standard CSS lib and web components. The idea is to provide a high degree
18
+ of customization for the front-end, while keeping a standard development process.
19
+
20
+ Realistically this project will not replace the need for a project specific front-end for larger projects, but it can
21
+ be a good alternative for internal use applications where faster development time matters more than a creative and unique UI.
22
+
23
+ Also, this project will likely be a lot more open to contributions since the idea is to provide a wide range of options
24
+ out of the box. As long as a feature is not too specific it will be welcome here.
25
+
26
+ Keep in mind this project will favor faster development over a faster runtime. That means we won't bother losing a few
27
+ milliseconds of page load time if that means we can save a few hours of development time.
28
+
29
+ ## Setup
30
+
31
+ Run `bundle add frontpack`
32
+
33
+ Or manually add this line to your application's Gemfile:
34
+
35
+ ```ruby
36
+ gem 'frontpack'
37
+ ```
38
+
39
+ And then execute:
40
+ ```bash
41
+ $ bundle
42
+ ```
43
+
44
+ ## TODO
45
+ Possibly remove the embedded css files and make it a plugin for this gem. It already works fine with Bootstrap.
46
+
47
+ ## Contributing
48
+ WIP
49
+
50
+ ## License
51
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'bundler/gem_tasks'
@@ -0,0 +1 @@
1
+ //= link_tree ../javascripts/frontpack .js
@@ -0,0 +1,141 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
6
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
7
+ xmlns:cc="http://creativecommons.org/ns#"
8
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ xmlns:xlink="http://www.w3.org/1999/xlink"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13
+ width="200"
14
+ height="120"
15
+ viewBox="0 0 52.916665 31.750001"
16
+ version="1.1"
17
+ id="svg8"
18
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
19
+ sodipodi:docname="logo.svg">
20
+ <defs
21
+ id="defs2">
22
+ <linearGradient
23
+ id="linearGradient899"
24
+ osb:paint="solid">
25
+ <stop
26
+ style="stop-color:#000000;stop-opacity:1;"
27
+ offset="0"
28
+ id="stop897" />
29
+ </linearGradient>
30
+ <linearGradient
31
+ inkscape:collect="always"
32
+ xlink:href="#linearGradient899"
33
+ id="linearGradient901"
34
+ x1="3.5352253"
35
+ y1="276.21649"
36
+ x2="46.7946"
37
+ y2="276.21649"
38
+ gradientUnits="userSpaceOnUse" />
39
+ </defs>
40
+ <sodipodi:namedview
41
+ id="base"
42
+ pagecolor="#ffffff"
43
+ bordercolor="#666666"
44
+ borderopacity="1.0"
45
+ inkscape:pageopacity="0.0"
46
+ inkscape:pageshadow="2"
47
+ inkscape:zoom="3.959798"
48
+ inkscape:cx="113.07665"
49
+ inkscape:cy="45.395589"
50
+ inkscape:document-units="mm"
51
+ inkscape:current-layer="layer1"
52
+ showgrid="false"
53
+ units="px"
54
+ inkscape:pagecheckerboard="true"
55
+ inkscape:window-width="1920"
56
+ inkscape:window-height="1015"
57
+ inkscape:window-x="0"
58
+ inkscape:window-y="0"
59
+ inkscape:window-maximized="1" />
60
+ <metadata
61
+ id="metadata5">
62
+ <rdf:RDF>
63
+ <cc:Work
64
+ rdf:about="">
65
+ <dc:format>image/svg+xml</dc:format>
66
+ <dc:type
67
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
68
+ <dc:title></dc:title>
69
+ </cc:Work>
70
+ </rdf:RDF>
71
+ </metadata>
72
+ <g
73
+ inkscape:label="Layer 1"
74
+ inkscape:groupmode="layer"
75
+ id="layer1"
76
+ transform="translate(0,-265.24998)">
77
+ <path
78
+ style="clip-rule:evenodd;fill:#d40000;fill-rule:evenodd;stroke-width:0.20672259"
79
+ d="m 19.732038,290.78116 c 5.677843,0.77521 11.254806,1.53656 16.942571,2.3128 -1.937817,-3.22611 -3.825815,-6.36975 -5.728902,-9.53899 z"
80
+ id="path927" />
81
+ <path
82
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
83
+ d="m 38.277123,272.92384 c -0.500476,0.75165 -1.001985,1.50266 -1.50184,2.25472 -1.716624,2.58258 -3.433041,5.16517 -5.148013,7.749 -0.0955,0.14387 -0.219332,0.25799 -0.08475,0.47981 1.657089,2.73637 3.301154,5.48083 4.949353,8.22279 0.260057,0.43308 0.523009,0.86452 0.859347,1.28004 0.333236,-6.65503 0.666266,-13.31025 0.999296,-19.96528 z"
84
+ id="path925" />
85
+ <path
86
+ style="clip-rule:evenodd;fill:#d40000;fill-rule:evenodd;stroke-width:0.20672259"
87
+ d="m 15.655675,281.4559 c 0.07753,0.0733 0.271013,0.11617 0.361145,0.0717 1.58949,-0.7812 3.189109,-1.54504 4.74387,-2.39054 0.50709,-0.27556 0.896969,-0.76963 1.333773,-1.17026 1.446439,-1.32653 2.890603,-2.65575 4.334147,-3.9852 0.08828,-0.0817 0.192873,-0.16062 0.245587,-0.26357 0.51846,-1.01335 1.028031,-2.03188 1.555587,-3.07975 -0.629057,-0.23649 -1.225451,-0.46781 -1.829288,-0.67825 -0.08187,-0.0286 -0.210857,0.0282 -0.299542,0.0774 -1.39765,0.7808 -2.821556,1.52004 -4.174761,2.37152 -0.677017,0.42606 -1.228554,1.05388 -1.828049,1.60067 -0.966221,0.88187 -1.931204,1.76582 -2.883366,2.66299 -0.252615,0.23815 -0.482904,0.5102 -0.679704,0.79671 -0.651177,0.94824 -1.282921,1.91011 -1.943607,2.89928 0.369207,0.38183 0.704924,0.74688 1.064208,1.08716 z"
88
+ id="path923" />
89
+ <path
90
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
91
+ d="m 21.464994,280.15955 c -0.790714,3.34042 -1.576673,6.66203 -2.388886,10.09281 3.80845,-2.45401 7.529043,-4.85137 11.225037,-7.23261 -2.94125,-0.95194 -5.872989,-1.90102 -8.836151,-2.8602 z"
92
+ id="path921" />
93
+ <path
94
+ style="clip-rule:evenodd;fill:#ff8080;fill-rule:evenodd;stroke-width:0.20672259"
95
+ d="m 37.306354,272.95609 c -3.217845,0.53768 -6.358788,1.06235 -9.540248,1.59383 1.08364,2.63593 2.145573,5.21891 3.237689,7.8753 2.112911,-3.17506 4.184066,-6.28643 6.302559,-9.46913 z"
96
+ id="path919" />
97
+ <path
98
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
99
+ d="m 21.59771,279.47633 c 2.89763,0.94058 5.737585,1.86255 8.660024,2.81142 -1.085294,-2.64399 -2.133378,-5.19556 -3.203168,-7.80109 -1.82226,1.66598 -3.617232,3.30736 -5.456856,4.98967 z"
100
+ id="path917" />
101
+ <path
102
+ style="clip-rule:evenodd;fill:#ff2a2a;fill-rule:evenodd;stroke-width:0.20672259"
103
+ d="m 15.775368,283.40385 c -1.027618,2.45648 -2.0203,4.82944 -3.030968,7.2452 1.942367,-0.0575 3.825195,-0.11301 5.772729,-0.17054 -0.914335,-2.35911 -1.810683,-4.67193 -2.741761,-7.07466 z"
104
+ id="path915" />
105
+ <path
106
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
107
+ d="m 18.585347,288.79848 0.07153,-0.0156 c 0.693141,-2.88193 1.396205,-5.74401 2.103816,-8.73858 -1.578535,0.82048 -3.084302,1.60293 -4.631207,2.40707 0.789474,2.05192 1.631249,4.21651 2.455865,6.34722 z"
108
+ id="path913" />
109
+ <path
110
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
111
+ d="m 36.141679,272.48868 c -0.921776,-0.24414 -1.843553,-0.4891 -2.765742,-0.73158 -1.325505,-0.34854 -2.652045,-0.69274 -3.976723,-1.04437 -0.165585,-0.0441 -0.285278,-0.0728 -0.382644,0.12674 -0.468227,0.95919 -0.950923,1.91115 -1.426592,2.86683 -0.01468,0.0295 -0.01153,0.0678 -0.02294,0.14202 2.870551,-0.43495 5.721875,-0.86721 8.572786,-1.29926 z"
112
+ id="path911" />
113
+ <path
114
+ style="clip-rule:evenodd;fill:#ff0000;fill-rule:evenodd;stroke-width:0.20672259"
115
+ d="m 30.793971,270.30693 7.51292,2.0784 c -0.43081,-1.22774 -0.830404,-2.36615 -1.241369,-3.53703 l -6.260593,1.38815 z"
116
+ id="path909" />
117
+ <path
118
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
119
+ d="m 19.222261,291.33456 c -0.930458,-0.10725 -1.885725,-0.008 -2.829413,0.008 -0.77521,0.0137 -1.55042,0.0426 -2.32563,0.0669 -0.07979,0.003 -0.159382,0.0185 -0.238971,0.10463 6.409022,0.59247 12.817836,1.18493 19.22665,1.77782 l 0.01304,-0.0855 c -2.054408,-0.28051 -4.109023,-0.56104 -6.163434,-0.84135 -2.560052,-0.34957 -5.115763,-0.73408 -7.682224,-1.0299 z"
120
+ id="path907" />
121
+ <path
122
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
123
+ d="m 12.871122,288.43775 c 0.825857,-1.91632 1.648198,-3.83388 2.477364,-5.74874 0.08289,-0.19206 0.07648,-0.3225 -0.08579,-0.47712 -0.346881,-0.33014 -0.669161,-0.68592 -1.048291,-1.08033 -0.512464,2.53028 -1.012321,4.99793 -1.512589,7.46537 l 0.05602,0.0262 c 0.03824,-0.0614 0.08497,-0.11946 0.113283,-0.18543 z"
124
+ id="path905" />
125
+ <path
126
+ style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
127
+ d="m 29.14846,269.80108 c 1.392689,-0.35927 2.805639,-0.64021 4.210318,-0.9536 0.09096,-0.0205 0.179849,-0.0505 0.26936,-0.0761 l -0.01386,-0.0687 c -1.978541,0.26606 -3.956876,0.53232 -6.035059,0.81201 0.566627,0.24744 1.003225,0.43226 1.569231,0.28631 z"
128
+ id="path81" />
129
+ <text
130
+ xml:space="preserve"
131
+ style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:19.75555611px;line-height:1.25;font-family:'URW Chancery L';-inkscape-font-specification:'URW Chancery L, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ff5555;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332;stroke-opacity:1"
132
+ x="-1.0690777"
133
+ y="284.57193"
134
+ id="text12"><tspan
135
+ sodipodi:role="line"
136
+ id="tspan10"
137
+ x="-1.0690777"
138
+ y="284.57193"
139
+ style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:19.75555611px;font-family:'URW Chancery L';-inkscape-font-specification:'URW Chancery L, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ff5555;stroke:#ffffff;stroke-width:0.26458332;stroke-opacity:1">Genesis</tspan></text>
140
+ </g>
141
+ </svg>
@@ -0,0 +1,173 @@
1
+ class AutoComplete extends HTMLInputElement {
2
+ constructor() {
3
+ super();
4
+ }
5
+
6
+ connectedCallback() {
7
+ this.addEventListener('keydown', this.onKeyDown.bind(this));
8
+ this.addEventListener('keyup', this.onKeyUp.bind(this));
9
+ this.addEventListener('blur', this.onBlur.bind(this));
10
+
11
+ this.hiddenInput = document.createElement('input');
12
+ this.hiddenInput.type = 'hidden';
13
+ this.hiddenInput.name = this.name;
14
+ this.hiddenInput.value = this.value;
15
+ this.parentElement.appendChild(this.hiddenInput);
16
+ this.removeAttribute('name');
17
+ this.value = this.getAttribute('display-value');
18
+
19
+ this.items = document.createElement('ul');
20
+ this.items.style.display = 'none';
21
+ this.items.style.position = 'absolute';
22
+ this.items.className = this.autocompleteStyle || 'auto-complete-list';
23
+ this.items.addEventListener('click', this.onItemClick.bind(this));
24
+
25
+ this.parentElement.appendChild(this.items);
26
+ this.lastUsedTerm = '';
27
+ }
28
+
29
+ set selectedItemIndex(value) {
30
+ if (value >= this.items.childNodes.length) {
31
+ this._selectedItemIndex = 0
32
+ } else if (value < 0) {
33
+ this._selectedItemIndex = this.items.childNodes.length - 1;
34
+ } else {
35
+ this._selectedItemIndex = value;
36
+ }
37
+ this.updateSelection();
38
+ }
39
+
40
+ async fetchData() {
41
+ this.lastUsedTerm = this.value;
42
+ let response = await fetch(`${this.url}/${encodeURIComponent(this.value)}`, {
43
+ cache: 'reload',
44
+ headers: {'Content-Type': 'application/json'},
45
+ mode: 'cors',
46
+ credentials: 'same-origin'
47
+ });
48
+ let data = await response.json();
49
+ this.updateList(data);
50
+ }
51
+
52
+ updateListPosition() {
53
+ let rect = this.getBoundingClientRect();
54
+ this.items.style.width = rect.width + 'px';
55
+ this.items.style.left = rect.left + 'px';
56
+ this.items.style.top = rect.bottom + 'px';
57
+
58
+ }
59
+
60
+ updateList(data) {
61
+ this.items.innerHTML = '';
62
+ for (let item of data) {
63
+ let li = document.createElement('li');
64
+ li.dataset.key = item.id;
65
+ li.dataset.value = item.value;
66
+ li.innerText = item.label || item.value;
67
+ this.items.appendChild(li);
68
+ }
69
+ this.selectedItemIndex = 0;
70
+ this.items.style.display = 'inline-block';
71
+ this.updateListPosition();
72
+ }
73
+
74
+ onBlur() {
75
+ if (this._timeout) {
76
+ window.clearTimeout(this._timeout);
77
+ }
78
+
79
+ window.setTimeout(_ => { this.items.style.display = 'none'; }, 100)
80
+ }
81
+
82
+ get url() {
83
+ return this.dataset.url;
84
+ }
85
+
86
+ get autocompleteStyle() {
87
+ return this.getAttribute('autocomplete-style');
88
+ }
89
+
90
+ get selectedItemIndex() {
91
+ return this._selectedItemIndex;
92
+ }
93
+
94
+ previousItem() {
95
+ this.selectedItemIndex--;
96
+ }
97
+
98
+ nextItem() {
99
+ this.selectedItemIndex++;
100
+ }
101
+
102
+ updateSelection() {
103
+ if (this.selectedItem) {
104
+ this.selectedItem.classList.remove('selected');
105
+ }
106
+ this.selectedItem = this.items.childNodes[this.selectedItemIndex];
107
+ this.selectedItem.classList.add('selected');
108
+ this.previewItem(this.selectedItem);
109
+ }
110
+
111
+ previewItem(item) {
112
+ this.value = item.innerText;
113
+ this.hiddenInput.value = item.dataset.key;
114
+ }
115
+
116
+ selectItem(item) {
117
+ this.value = item.innerText;
118
+ this.hiddenInput.value = item.dataset.key;
119
+ this.items.style.display = 'none';
120
+ }
121
+
122
+ onKeyDown(event) {
123
+ let key = event.code.toLowerCase();
124
+ if (['enter'].includes(key)) {
125
+ event.preventDefault();
126
+ event.stopPropagation();
127
+ }
128
+ }
129
+
130
+ onKeyUp(event) {
131
+ let key = event.code.toLowerCase();
132
+ if (['arrowdown', 'enter', 'arrowup', 'insert', 'tab'].includes(key)) {
133
+ event.preventDefault();
134
+ event.stopPropagation();
135
+ if (key === 'arrowdown') {
136
+ this.nextItem();
137
+ } else if (key === 'arrowup') {
138
+ this.previousItem();
139
+ } else if (key === 'enter') {
140
+ if (this.value !== '') {
141
+ this.selectItem(this.selectedItem);
142
+ } else {
143
+ this.value = '';
144
+ this.hiddenInput.value = '';
145
+ }
146
+ }
147
+ } else {
148
+ if (this.value === this.lastUsedTerm) {
149
+ return
150
+ }
151
+
152
+ this.items.style.display = 'none';
153
+
154
+ if (this._timeout) {
155
+ window.clearTimeout(this._timeout);
156
+ }
157
+
158
+ if (this.value.length < this.minLength) {
159
+ return;
160
+ }
161
+
162
+ this._timeout = window.setTimeout(this.fetchData.bind(this), 500);
163
+ }
164
+ }
165
+
166
+ onItemClick(event) {
167
+ let item = event.target;
168
+ if (item.tagName.toLowerCase() === 'li') {
169
+ this.selectItem(item);
170
+ }
171
+ }
172
+ }
173
+ customElements.define('auto-complete', AutoComplete, { extends: 'input' });
@@ -0,0 +1,160 @@
1
+ class BaseWebComponent extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.bindableElements = new Map();
5
+ this.bindableAttributes = new Map();
6
+ this.attachShadow({mode: 'open'});
7
+ this.shadowRoot.innerHTML = this.template();
8
+ }
9
+
10
+ template() {
11
+ return '<slot></slot>';
12
+ }
13
+
14
+ domTreeLoaded() {
15
+ // Placeholder
16
+ }
17
+
18
+ raiseEvent(eventName, detail, cancelable = true, bubbles = true) {
19
+ let event = new CustomEvent(eventName, { bubbles: bubbles, cancelable: cancelable, detail: detail });
20
+ this.dispatchEvent(event);
21
+ }
22
+
23
+ /**
24
+ * This function registers "magic events".
25
+ * Magic events are special methods following a name pattern to auto register event listeners.
26
+ * The name pattern consists of the event trigger followed by a dollar sign and the method name.
27
+ * Pattern sample: click$sayHello
28
+ */
29
+ registerEvents() {
30
+ for (let prop of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
31
+ if (prop.indexOf('$') > 0 && typeof this[prop] === 'function') {
32
+ let [_, listener] = /^([a-z]+)\$.+/.exec(prop);
33
+ this.addEventListener(listener, this[prop].bind(this));
34
+ }
35
+ }
36
+ }
37
+
38
+ prepareBindableElements() {
39
+ for (let [attr, _] of this.bindableElements) {
40
+ // Save a reference to the bindable element
41
+ let element = this.shadowRoot.getElementById(attr);
42
+ if (!element) { throw new Error(`Element ${attr} not found.`); }
43
+ this.bindableElements.set(attr, element);
44
+ element.innerText = this.getAttribute(attr);
45
+
46
+ Object.defineProperty(this, attr, {
47
+ get() {
48
+ return this.getAttribute(attr);
49
+ },
50
+ set(value) {
51
+ this.setAttribute(attr, value);
52
+ let element = this.bindableElements.get(attr);
53
+ element.innerText = value;
54
+ }
55
+ })
56
+ }
57
+ }
58
+
59
+ prepareBindableAttributes(nodes) {
60
+ for (let node of nodes) {
61
+ if (node.nodeType !== 1) { continue; }
62
+ for (let attr of node.attributes) {
63
+ let result = /^%([a-z]+)$/.exec(attr.value);
64
+ if (!result) {
65
+ continue
66
+ }
67
+ let [_, name] = result
68
+ if (name) {
69
+ this.bindableAttributes.set(name, attr);
70
+ attr.value = this.getAttribute(name);
71
+
72
+ Object.defineProperty(this, name, {
73
+ get() {
74
+ return this.getAttribute(attr);
75
+ },
76
+ set(value) {
77
+ this.setAttribute(name, value);
78
+ let attribute = this.bindableAttributes.get(name);
79
+ attribute.value = value;
80
+ }
81
+ })
82
+ }
83
+ }
84
+
85
+ this.prepareBindableAttributes(node.childNodes);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Called by native code when this element is attached to the DOMTree
91
+ */
92
+ connectedCallback() {
93
+ this.prepareBindableElements();
94
+ this.prepareBindableAttributes(this.shadowRoot.childNodes);
95
+ this.registerEvents();
96
+ if (document.readyState === 'interactive') {
97
+ this.domTreeLoaded();
98
+ } else {
99
+ document.addEventListener('readystatechange', event => {
100
+ if (event.target.readyState === 'interactive') {
101
+ this.domTreeLoaded();
102
+ }
103
+ })
104
+ }
105
+ }
106
+
107
+ defineBindableElement(...attrs) {
108
+ for (let attr of attrs) {
109
+ this.bindableElements.set(attr, null);
110
+ }
111
+ }
112
+
113
+ defineBindableAttribute(...attrs) {
114
+ for (let attr of attrs) {
115
+ this.bindableAttributes.set(attr, null);
116
+ }
117
+ }
118
+
119
+ _camelize(text) {
120
+ let words = text.split('-');
121
+ for (let i = 1; i < words.length; i++) {
122
+ words[i] = words[i][0].toUpperCase() + words[i].slice(1);
123
+ }
124
+ return words.join('');
125
+ }
126
+
127
+ defineAttribute(...attrs) {
128
+ for (let prop of attrs) {
129
+ let name = '';
130
+ let defaultValue = null;
131
+
132
+ if (typeof prop === 'string') {
133
+ name = prop;
134
+ } else {
135
+ name = prop[0]
136
+ defaultValue = prop[1]
137
+ }
138
+ let propName = this._camelize(name);
139
+
140
+ Object.defineProperty(this, propName, {
141
+ get() {
142
+ return this.getAttribute(name) || defaultValue;
143
+ },
144
+ set(value) {
145
+ this.setAttribute(name, value);
146
+ }
147
+ })
148
+ }
149
+ }
150
+
151
+ findParentElement(tagName, node = this) {
152
+ if (node.parentNode.tagName.toUpperCase() === tagName.toUpperCase()) {
153
+ return node.parentNode;
154
+ } else {
155
+ this.findParentElement(tagName, node.parentNode);
156
+ }
157
+ }
158
+ }
159
+
160
+ module.exports = BaseWebComponent;
@@ -0,0 +1,99 @@
1
+ class ExpandableList extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ const shadowRoot = this.attachShadow({mode: 'open'});
5
+ shadowRoot.innerHTML = `
6
+ <style>
7
+ :host(*) {
8
+ display: grid;
9
+ grid-template-columns: 1fr;
10
+ grid-template-rows: min-content 1fr;
11
+ }
12
+
13
+ #label {
14
+ display: block;
15
+ padding: .5rem;
16
+ font-weight: bold;
17
+ }
18
+
19
+ #area {
20
+ overflow-x: auto;
21
+ }
22
+
23
+ h3 {
24
+ margin: 0;
25
+ }
26
+
27
+ </style>
28
+ <h3 id="label"></h3>
29
+ <div id="area">
30
+ <slot></slot>
31
+ </div>
32
+ `;
33
+ }
34
+
35
+ connectedCallback() {
36
+ this.label = this.shadowRoot.getElementById('label');
37
+ this.label.innerText = this.getAttribute('caption');
38
+ }
39
+ }
40
+
41
+ customElements.define('expandable-list', ExpandableList);
42
+
43
+ class ExpandableItem extends HTMLElement {
44
+ constructor() {
45
+ super();
46
+ const shadowRoot = this.attachShadow({mode: 'open'});
47
+ shadowRoot.innerHTML = `
48
+ <style>
49
+ #label {
50
+ display: block;
51
+ padding: .5rem;
52
+ font-weight: bold;
53
+ cursor: pointer;
54
+ }
55
+ </style>
56
+ <span id="label"></span>
57
+ <div id="area">
58
+ <slot></slot>
59
+ </div>
60
+ `;
61
+ }
62
+
63
+ connectedCallback() {
64
+ this.label = this.shadowRoot.getElementById('label');
65
+ this.label.innerText = this.getAttribute('caption');
66
+ this.label.addEventListener('click', this.toggle.bind(this));
67
+ this.area = this.shadowRoot.getElementById('area');
68
+ this.expanded = this.hasAttribute('expanded') || this.parentNode.hasAttribute('expanded');
69
+ }
70
+
71
+ set expanded(value) {
72
+ this._expanded = value;
73
+ if (value) {
74
+ this.setAttribute('expanded', 'expanded');
75
+ } else {
76
+ this.removeAttribute('expanded');
77
+ }
78
+
79
+ this.area.style.display = this._expanded ? 'block' : 'none';
80
+ }
81
+
82
+ get expanded() {
83
+ return this._expanded;
84
+ }
85
+
86
+ expand() {
87
+ this.expanded = true;
88
+ }
89
+
90
+ collapse() {
91
+ this.expanded = false;
92
+ }
93
+
94
+ toggle() {
95
+ this.expanded = !this.expanded;
96
+ }
97
+ }
98
+
99
+ customElements.define('expandable-item', ExpandableItem);