clapton 0.0.12 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/app/helpers/clapton/clapton_helper.rb +16 -1
- data/lib/clapton/engine.rb +16 -10
- data/lib/clapton/javascripts/dist/client.js +65 -39
- data/lib/clapton/javascripts/dist/components-for-test.js +439 -0
- data/lib/clapton/javascripts/dist/components.js +357 -369
- data/lib/clapton/javascripts/node_modules/diff-dom/LICENSE.txt +165 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/README.md +224 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/browser/diffDOM.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/TraceLogger.d.ts +28 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/apply.d.ts +4 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/fromVirtual.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/index.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/dom/undo.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/helpers.d.ts +11 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/index.d.ts +10 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/types.d.ts +104 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/apply.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/diff.d.ts +22 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromDOM.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/fromString.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/helpers.d.ts +40 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/diffDOM/virtual/index.d.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/dts/index.d.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.d.ts +136 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js +1996 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/index.min.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js +1991 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/dist/module.js.map +1 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/index.html +62 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/package.json +54 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/rollup.config.mjs +67 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/TraceLogger.ts +143 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/apply.ts +227 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/fromVirtual.ts +83 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/index.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/dom/undo.ts +90 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/helpers.ts +40 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/index.ts +121 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/types.ts +154 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/apply.ts +349 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/diff.ts +855 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromDOM.ts +74 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/fromString.ts +239 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/helpers.ts +461 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/diffDOM/virtual/index.ts +3 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/src/index.ts +2 -0
- data/lib/clapton/javascripts/node_modules/diff-dom/tsconfig.json +103 -0
- data/lib/clapton/javascripts/rollup.config.mjs +17 -2
- data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -1
- data/lib/clapton/javascripts/src/actions/initialize-actions.ts +11 -0
- data/lib/clapton/javascripts/src/channel/clapton-channel.js +8 -4
- data/lib/clapton/javascripts/src/client.ts +15 -18
- data/lib/clapton/javascripts/src/components/box.ts +5 -0
- data/lib/clapton/javascripts/src/components/embed.spec.ts +8 -0
- data/lib/clapton/javascripts/src/components/embed.ts +11 -0
- data/lib/clapton/javascripts/src/components-for-test.ts +29 -0
- data/lib/clapton/javascripts/src/components.ts +5 -1
- data/lib/clapton/javascripts/src/dom/update-component.ts +3 -2
- data/lib/clapton/javascripts/src/inputs/initialize-inputs.ts +3 -3
- data/lib/clapton/test_helper/base.rb +1 -1
- data/lib/clapton/version.rb +2 -1
- metadata +51 -3
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7def55e21b26dedfd8843c9ace0bf4db854abf18205de4dea7c3a203d06130f6
|
4
|
+
data.tar.gz: 743f8549cd8a901f2f1c76a4807f05ccace77d58e9665ad35c02626df555a08a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
data/lib/clapton/engine.rb
CHANGED
@@ -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
|
-
|
34
|
-
js
|
35
|
-
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
|
-
|
39
|
+
code = File.read(file)
|
40
|
+
js = ""
|
41
|
+
js += "import { Clapton } from 'components';"
|
41
42
|
js += "\n"
|
42
|
-
|
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
|
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
|
-
|
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
|
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)
|
1397
|
-
|
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
|
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
|
1440
|
+
initializeInputs();
|
1415
1441
|
});
|