clapton 0.0.12 → 0.0.14

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