clapton 0.0.12 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -0
  3. data/app/helpers/clapton/clapton_helper.rb +16 -1
  4. data/lib/clapton/engine.rb +16 -10
  5. data/lib/clapton/javascripts/dist/client.js +65 -39
  6. data/lib/clapton/javascripts/dist/components-for-test.js +439 -0
  7. data/lib/clapton/javascripts/dist/components.js +357 -369
  8. data/lib/clapton/javascripts/node_modules/diff-dom/LICENSE.txt +165 -0
  9. data/lib/clapton/javascripts/node_modules/diff-dom/README.md +224 -0
  10. data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js +2 -0
  11. data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js.map +1 -0
  12. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/TraceLogger.d.ts +28 -0
  13. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/apply.d.ts +4 -0
  14. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/fromVirtual.d.ts +2 -0
  15. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/index.d.ts +2 -0
  16. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/undo.d.ts +3 -0
  17. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/helpers.d.ts +11 -0
  18. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/index.d.ts +10 -0
  19. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/types.d.ts +104 -0
  20. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/apply.d.ts +3 -0
  21. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/diff.d.ts +22 -0
  22. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromDOM.d.ts +2 -0
  23. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromString.d.ts +2 -0
  24. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/helpers.d.ts +40 -0
  25. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/index.d.ts +3 -0
  26. data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/index.d.ts +2 -0
  27. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.d.ts +136 -0
  28. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js +1996 -0
  29. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js.map +1 -0
  30. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js +2 -0
  31. data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js.map +1 -0
  32. data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js +1991 -0
  33. data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js.map +1 -0
  34. data/lib/clapton/javascripts/node_modules/diff-dom/index.html +62 -0
  35. data/lib/clapton/javascripts/node_modules/diff-dom/package.json +54 -0
  36. data/lib/clapton/javascripts/node_modules/diff-dom/rollup.config.mjs +67 -0
  37. data/lib/clapton/javascripts/node_modules/diff-dom/src/TraceLogger.ts +143 -0
  38. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/apply.ts +227 -0
  39. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/fromVirtual.ts +83 -0
  40. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/index.ts +2 -0
  41. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/undo.ts +90 -0
  42. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/helpers.ts +40 -0
  43. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/index.ts +121 -0
  44. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/types.ts +154 -0
  45. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/apply.ts +349 -0
  46. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/diff.ts +855 -0
  47. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromDOM.ts +74 -0
  48. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromString.ts +239 -0
  49. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/helpers.ts +461 -0
  50. data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/index.ts +3 -0
  51. data/lib/clapton/javascripts/node_modules/diff-dom/src/index.ts +2 -0
  52. data/lib/clapton/javascripts/node_modules/diff-dom/tsconfig.json +103 -0
  53. data/lib/clapton/javascripts/rollup.config.mjs +17 -2
  54. data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -1
  55. data/lib/clapton/javascripts/src/actions/initialize-actions.ts +11 -0
  56. data/lib/clapton/javascripts/src/channel/clapton-channel.js +8 -4
  57. data/lib/clapton/javascripts/src/client.ts +15 -18
  58. data/lib/clapton/javascripts/src/components/box.ts +5 -0
  59. data/lib/clapton/javascripts/src/components/embed.spec.ts +8 -0
  60. data/lib/clapton/javascripts/src/components/embed.ts +11 -0
  61. data/lib/clapton/javascripts/src/components-for-test.ts +29 -0
  62. data/lib/clapton/javascripts/src/components.ts +5 -1
  63. data/lib/clapton/javascripts/src/dom/update-component.ts +3 -2
  64. data/lib/clapton/javascripts/src/inputs/initialize-inputs.ts +3 -3
  65. data/lib/clapton/test_helper/base.rb +1 -1
  66. data/lib/clapton/version.rb +2 -1
  67. metadata +51 -3
  68. data/lib/clapton/javascripts/src/dom/update-component.spec.ts +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a69f6b50e12d2656c4e56ea26907b860f49f74c386b33899cb56f987ab63161
4
- data.tar.gz: c4aa0019acf0fb2573d8bede7112f371c446df08e82d3e88f67889c7a28d3e03
3
+ metadata.gz: 7def55e21b26dedfd8843c9ace0bf4db854abf18205de4dea7c3a203d06130f6
4
+ data.tar.gz: 743f8549cd8a901f2f1c76a4807f05ccace77d58e9665ad35c02626df555a08a
5
5
  SHA512:
6
- metadata.gz: 4f714e419f33f1ee5a3a1c9e0a3eadc682d219bb3ba89347dcbee75bb2b70d7b8c123a9abc7d3f6053e7daf874d2a361a4acc7b36b4eab4c857782eb14095106
7
- data.tar.gz: 59ae4e4669c37a77bfd1c4287af8ecea6704009f7145a35568451c0634efb81de0c5a509b11472ccf159835f063b032cf6d0be2d6711a42bf1b1eaa421f4b981
6
+ metadata.gz: b05ba6c72f46212b628cee66c18e01644e285e6380862669eb7de55174338b4b9109ebf69505d398227c85f0a19a6e55580e6cf760951fad573bc13d78c73845
7
+ data.tar.gz: 2359db5b15a7ef59f43dc61d2bcb96609858cc0a19c996e9b18288046fe739a3d68bf5868f221c97c349af8781ff8483c979199e3c1c27914f617e8912555aba
data/README.md CHANGED
@@ -12,6 +12,7 @@ Clapton is a Ruby on Rails gem for building web apps with pure Ruby only (no Jav
12
12
  - Action Cable (WebSocket)
13
13
  - [Ruby2JS](https://www.ruby2js.com/) (for compiling Ruby to JavaScript)
14
14
  - [Morphdom](https://github.com/patrick-steele-idem/morphdom)
15
+ - importmap
15
16
 
16
17
  ## Installation
17
18
 
@@ -159,6 +160,23 @@ After running the generator, you will see the following files:
159
160
  - `app/components/task_list_component.rb`
160
161
  - `app/states/task_list_state.rb`
161
162
 
163
+ ### Special Event
164
+
165
+ #### render
166
+
167
+ The `render` event is a special event that is triggered when the component is rendered.
168
+
169
+ ```ruby
170
+ # app/components/task_list_component.rb
171
+ class TaskListComponent < Clapton::Component
172
+ def render
173
+ # ...
174
+ @root.add_action(:render, :TaskListState, :add_empty_task, debounce: 500)
175
+ @root.render
176
+ end
177
+ end
178
+ ```
179
+
162
180
  ### Preset Components
163
181
 
164
182
  ```ruby
@@ -183,6 +201,8 @@ datetime_field = Clapton::DateTimeField.new(:ExampleState, :example_attribute, {
183
201
  element = Clapton::Element.new("div", { id: "example-element" })
184
202
  element.add(Clapton::Text.new("Hello"))
185
203
 
204
+ embed = Clapton::Embed.new("<blockquote>This is a test</blockquote>")
205
+
186
206
  emphasis = Clapton::Emphasis.new
187
207
  emphasis.add(Clapton::Text.new("Hello"))
188
208
 
@@ -310,6 +330,19 @@ class TaskListComponentTest < ActiveSupport::TestCase
310
330
  end
311
331
  ```
312
332
 
333
+ ## Deployment
334
+
335
+ Run `bundle exec rake clapton:compile` to compile the components.
336
+
337
+ `app/components` is codes that are compiled to JavaScript.
338
+ So, you need to ignore the directory from autoloading.
339
+
340
+ ```ruby
341
+ # config/application.rb
342
+
343
+ Rails.autoloaders.main.ignore(Rails.root.join("app/components"))
344
+ ```
345
+
313
346
  ## Development
314
347
 
315
348
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/dev` to start the development server.
@@ -2,7 +2,22 @@ module Clapton
2
2
  module ClaptonHelper
3
3
 
4
4
  def clapton_javascript_tag
5
- tag.script(src: "/clapton/index.js", type: "text/javascript")
5
+ all_components = Dir.glob(Rails.root.join("app", "components", "**", "*.rb"))
6
+ tags = <<~HTML
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "client": "/clapton/client.js",
11
+ "components": "/clapton/components.js",
12
+ #{ all_components.map do
13
+ |component| "\"#{File.basename(component, ".rb").camelize}\": \"/clapton/#{File.basename(component, ".rb").camelize}.js\""
14
+ end.join(",\n") }
15
+ }
16
+ }
17
+ </script>
18
+ <script type="module" src="/clapton/client.js"></script>
19
+ HTML
20
+ tags.html_safe
6
21
  end
7
22
 
8
23
  def clapton_tag
@@ -21,6 +21,8 @@ module Clapton
21
21
  FileUtils.mkdir_p(components_path) unless components_path.exist?
22
22
  FileUtils.touch(components_path.join(".keep"))
23
23
 
24
+ compile_components
25
+
24
26
  listener = Listen.to(Rails.root.join("app", "components")) do |modified, added, removed|
25
27
  compile_components
26
28
  end
@@ -30,20 +32,24 @@ module Clapton
30
32
  end
31
33
 
32
34
  def compile_components
33
- js = File.read(File.join(__dir__, "javascripts", "dist", "components.js"))
34
- js += "\n"
35
- js += File.read(File.join(__dir__, "javascripts", "dist", "client.js"))
36
- js += "\n"
37
- js += "window.components = [];"
38
- js += "\n"
35
+ FileUtils.mkdir_p(Rails.root.join("public", "clapton")) unless Rails.root.join("public", "clapton").exist?
36
+ File.write(Rails.root.join("public", "clapton", "components.js"), File.read(File.join(__dir__, "javascripts", "dist", "components.js")))
37
+ File.write(Rails.root.join("public", "clapton", "client.js"), File.read(File.join(__dir__, "javascripts", "dist", "client.js")))
39
38
  Dir.glob(Rails.root.join("app", "components", "**", "*.rb")).each do |file|
40
- js += Ruby2JS.convert(File.read(file), preset: true)
39
+ code = File.read(file)
40
+ js = ""
41
+ js += "import { Clapton } from 'components';"
41
42
  js += "\n"
42
- js += "window.#{File.basename(file, ".rb").camelize} = #{File.basename(file, ".rb").camelize};"
43
+ code.scan(/(\w+)Component\.new/).each do |match|
44
+ js += "import { #{match[0]}Component } from '#{match[0]}Component';"
45
+ js += "\n"
46
+ end
47
+ js += Ruby2JS.convert(code, preset: true)
43
48
  js += "\n"
49
+ js += "export { #{File.basename(file, ".rb").camelize} };"
50
+ js += "\n"
51
+ File.write(Rails.root.join("public", "clapton", "#{File.basename(file, ".rb").camelize}.js"), js)
44
52
  end
45
- FileUtils.mkdir_p(Rails.root.join("public", "clapton")) unless Rails.root.join("public", "clapton").exist?
46
- File.write(Rails.root.join("public", "clapton", "index.js"), js)
47
53
  end
48
54
  end
49
55
  end
@@ -1285,30 +1285,66 @@ function getConfig(name) {
1285
1285
  }
1286
1286
  }
1287
1287
 
1288
+ const updateComponent = async (component, state, property, target) => {
1289
+ state[property] = target.value;
1290
+ component.setAttribute("data-state", JSON.stringify(state));
1291
+ const componentName = component.getAttribute("data-component");
1292
+ const module = await import(`${componentName}`);
1293
+ const ComponentClass = module[componentName];
1294
+ const instance = new ComponentClass(state, component.dataset.id);
1295
+ morphdom(component, instance.render);
1296
+ };
1297
+
1298
+ const initializeInputs = () => {
1299
+ const inputElements = document.querySelectorAll("[data-attribute]");
1300
+ inputElements.forEach((element) => {
1301
+ const attribute = element.getAttribute("data-attribute");
1302
+ const component = element.closest(`[data-component]`);
1303
+ const state = JSON.parse(component.getAttribute("data-state") || "{}");
1304
+ if (!attribute || !component)
1305
+ return;
1306
+ if (element.tagName === "INPUT") {
1307
+ element.addEventListener("input", async (event) => {
1308
+ await updateComponent(component, state, attribute, event.target);
1309
+ });
1310
+ }
1311
+ });
1312
+ };
1313
+
1288
1314
  const consumer = createConsumer();
1289
1315
 
1290
1316
  const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonChannel", {
1291
- connected() {},
1317
+ connected() {
1318
+ window.actionCableConnected = true;
1319
+ },
1292
1320
 
1293
1321
  disconnected() {},
1294
1322
 
1295
- received(response) {
1323
+ async received(response) {
1296
1324
  const { data, errors } = response;
1297
1325
  const component = document.querySelector(`[data-id="${data.component.id}"]`);
1298
- const instance = new window[data.component.name](data.state, data.component.id, errors);
1326
+ const module = await import(`${data.component.name}`);
1327
+ const instance = new module[data.component.name](data.state, data.component.id, errors);
1299
1328
  morphdom(component, instance.render, {
1300
1329
  onBeforeElUpdated: (_fromEl, toEl) => {
1301
1330
  toEl.setAttribute("data-set-event-handler", "true");
1302
1331
  return true;
1303
1332
  }
1304
1333
  });
1334
+
1305
1335
  initializeInputs();
1306
1336
  initializeActions();
1307
1337
  }
1308
1338
  });
1309
1339
 
1310
1340
  const handleAction = async (target, stateName, fn) => {
1311
- const targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`);
1341
+ let targetComponent = target;
1342
+ if (target.dataset.component === stateName.replace("State", "Component")) {
1343
+ targetComponent = target;
1344
+ }
1345
+ else {
1346
+ targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`);
1347
+ }
1312
1348
  if (!targetComponent)
1313
1349
  return;
1314
1350
  const component = target.closest(`[data-component]`);
@@ -1356,6 +1392,16 @@ const initializeActionsForElement = (element) => {
1356
1392
  const { eventType, componentName, stateName, fnName, bounceTime } = splitActionAttribute(action);
1357
1393
  if (!eventType || !componentName || !fnName)
1358
1394
  return;
1395
+ if (eventType === "render") {
1396
+ const interval = setInterval(() => {
1397
+ if (window.actionCableConnected === true) {
1398
+ handleAction(element, stateName, fnName);
1399
+ clearInterval(interval);
1400
+ }
1401
+ }, 10);
1402
+ element.setAttribute("data-render-event-handler", "true");
1403
+ return;
1404
+ }
1359
1405
  if (bounceTime > 0) {
1360
1406
  element.addEventListener(eventType, debounce((event) => handleAction(event.target, stateName, fnName), bounceTime));
1361
1407
  }
@@ -1366,50 +1412,30 @@ const initializeActionsForElement = (element) => {
1366
1412
  });
1367
1413
  };
1368
1414
 
1369
- const updateComponent = (component, state, property, target) => {
1370
- state[property] = target.value;
1371
- component.setAttribute("data-state", JSON.stringify(state));
1372
- const componentName = component.getAttribute("data-component");
1373
- const ComponentClass = window[componentName];
1374
- const instance = new ComponentClass(state, component.dataset.id);
1375
- morphdom(component, instance.render);
1376
- };
1377
-
1378
- const initializeInputs$1 = () => {
1379
- const inputElements = document.querySelectorAll("[data-attribute]");
1380
- inputElements.forEach((element) => {
1381
- const attribute = element.getAttribute("data-attribute");
1382
- const component = element.closest(`[data-component]`);
1383
- const state = JSON.parse(component.getAttribute("data-state") || "{}");
1384
- if (!attribute || !component)
1385
- return;
1386
- if (element.tagName === "INPUT") {
1387
- element.addEventListener("input", (event) => {
1388
- updateComponent(component, state, attribute, event.target);
1389
- });
1390
- }
1391
- });
1392
- };
1393
-
1394
- const initializeComponents = () => {
1415
+ const initializeComponents = async () => {
1395
1416
  const components = document.querySelector("#clapton")?.getAttribute("data-clapton") || "[]";
1396
- JSON.parse(components).forEach((component) => createAndAppendComponent(component, document.querySelector("#clapton")));
1397
- document.querySelectorAll(".clapton-component").forEach((element) => {
1417
+ const componentArray = JSON.parse(components);
1418
+ for (const component of componentArray) {
1419
+ await createAndAppendComponent(component, document.querySelector("#clapton"));
1420
+ }
1421
+ const elements = document.querySelectorAll(".clapton-component");
1422
+ for (const element of elements) {
1398
1423
  const component = JSON.parse(element.getAttribute("data-clapton") || "{}");
1399
- createAndAppendComponent(component, element);
1400
- });
1424
+ await createAndAppendComponent(component, element);
1425
+ }
1401
1426
  };
1402
- const createAndAppendComponent = (component, element) => {
1427
+ const createAndAppendComponent = async (component, element) => {
1403
1428
  const componentDom = document.createElement('div');
1404
- const instance = new window[component.component](component.state);
1429
+ const module = await import(`${component.component}`);
1430
+ const instance = new module[component.component](component.state);
1405
1431
  componentDom.innerHTML = instance.render;
1406
1432
  const firstChild = componentDom.firstChild;
1407
1433
  if (firstChild) {
1408
1434
  element.appendChild(firstChild);
1409
1435
  }
1410
1436
  };
1411
- document.addEventListener("DOMContentLoaded", () => {
1412
- initializeComponents();
1437
+ document.addEventListener("DOMContentLoaded", async () => {
1438
+ await initializeComponents();
1413
1439
  initializeActions();
1414
- initializeInputs$1();
1440
+ initializeInputs();
1415
1441
  });