clapton 0.0.14 → 0.0.16
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.
- checksums.yaml +4 -4
- data/README.md +80 -14
- data/lib/clapton/engine.rb +11 -3
- data/lib/clapton/javascripts/dist/client.js +7 -8
- data/lib/clapton/javascripts/dist/components-for-test.js +127 -12
- data/lib/clapton/javascripts/dist/components.js +110 -1
- data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -6
- data/lib/clapton/javascripts/src/components/bold.spec.ts +21 -0
- data/lib/clapton/javascripts/src/components/bold.ts +20 -0
- data/lib/clapton/javascripts/src/components/component.ts +3 -0
- data/lib/clapton/javascripts/src/components/presets.ts +105 -0
- data/lib/clapton/javascripts/src/components-for-test.ts +3 -2
- data/lib/clapton/javascripts/src/components.ts +3 -2
- data/lib/clapton/javascripts/src/dom/update-component.ts +1 -2
- data/lib/clapton/test_helper/base.rb +11 -3
- data/lib/clapton/version.rb +1 -2
- metadata +5 -3
- data/lib/clapton/javascripts/src/components/component.spec.ts +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f420efd6abefefc796433bad77369a93a3686057519240abf5a9e118758ade2
|
4
|
+
data.tar.gz: f34d535b066e305f893bd291e464d2dfd231df8fbf837b04668fcbaa0a0b4b84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08125a3863fc85cae262381041e72fdacdfc1fe8dd4f677cd4f5f55e658b713b3c8fa6238289c2b0fc1b5e4cc1bbb4e9ead48179eee8af6cde67b26fe5293e30'
|
7
|
+
data.tar.gz: 6e9a70c6be7c02f4642d44ce4f766ea8a72460962e8a0437f71bafbdaad11df2f0cfbe6fd4c10953a63ab0d324c5720286a17edc5dc66a59ca4637c5ef239258
|
data/README.md
CHANGED
@@ -37,10 +37,10 @@ class TaskListComponent < Clapton::Component
|
|
37
37
|
@state.tasks.each do |task|
|
38
38
|
@root.add(TaskItemComponent.new(id: task[:id], title: task[:title], due: task[:due], done: task[:done]))
|
39
39
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@root.add(
|
40
|
+
btn = c.button
|
41
|
+
btn.add(c.text("Add Task"))
|
42
|
+
btn.add_action(:click, :TaskListState, :add_task)
|
43
|
+
@root.add(btn)
|
44
44
|
@root.render
|
45
45
|
end
|
46
46
|
end
|
@@ -51,17 +51,17 @@ end
|
|
51
51
|
# app/components/task_item_component.rb
|
52
52
|
class TaskItemComponent < Clapton::Component
|
53
53
|
def render
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
btn = c.button
|
55
|
+
btn.add(c.text(@state.done ? "✅" : "🟩"))
|
56
|
+
btn.add_action(:click, :TaskListState, :toggle_done)
|
57
57
|
|
58
|
-
|
59
|
-
|
58
|
+
tf = c.input(@state, :title)
|
59
|
+
tf.add_action(:input, :TaskListState, :update_title)
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
dt = c.datetime(@state, :due)
|
62
|
+
dt.add_action(:input, :TaskListState, :update_due)
|
63
63
|
|
64
|
-
@root.add(
|
64
|
+
@root.add(btn).add(tf).add(dt)
|
65
65
|
@root.render
|
66
66
|
end
|
67
67
|
end
|
@@ -177,13 +177,13 @@ class TaskListComponent < Clapton::Component
|
|
177
177
|
end
|
178
178
|
```
|
179
179
|
|
180
|
-
### Preset Components
|
180
|
+
### Preset Components Classes
|
181
181
|
|
182
182
|
```ruby
|
183
183
|
block_quote = Clapton::BlockQuote.new
|
184
184
|
block_quote.add(Clapton::Text.new("Hello"))
|
185
185
|
|
186
|
-
box = Clapton
|
186
|
+
box = **Clapton**::Box.new
|
187
187
|
box.add(Clapton::Text.new("Hello"))
|
188
188
|
|
189
189
|
button = Clapton::Button.new
|
@@ -253,6 +253,72 @@ text_field = Clapton::TextField.new(:ExampleState, :example_attribute, { id: "ex
|
|
253
253
|
text = Clapton::Text.new("Hello")`
|
254
254
|
```
|
255
255
|
|
256
|
+
### Preset Component Methods
|
257
|
+
|
258
|
+
```javascript
|
259
|
+
c.bq(...props)
|
260
|
+
c.box(...props)
|
261
|
+
c.b(...props)
|
262
|
+
c.button(...props)
|
263
|
+
c.check(...props)
|
264
|
+
c.code(...props)
|
265
|
+
c.datetime(...props)
|
266
|
+
c.el(...props)
|
267
|
+
c.embed(...props)
|
268
|
+
c.em(...props)
|
269
|
+
c.form(...props)
|
270
|
+
c.h(...props)
|
271
|
+
c.img(...props)
|
272
|
+
c.a(...props)
|
273
|
+
c.li(...props)
|
274
|
+
c.ul(...props)
|
275
|
+
c.ol(...props)
|
276
|
+
c.p(...props)
|
277
|
+
c.q(...props)
|
278
|
+
c.radio(...props)
|
279
|
+
c.select(...props)
|
280
|
+
c.span(...props)
|
281
|
+
c.textarea(...props)
|
282
|
+
c.input(...props)
|
283
|
+
c.text(...props)
|
284
|
+
```
|
285
|
+
|
286
|
+
### Streaming
|
287
|
+
|
288
|
+
Clapton supports streaming.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
# app/states/chat_state.rb
|
292
|
+
class ChatState < Clapton::State
|
293
|
+
attribute :messages
|
294
|
+
|
295
|
+
def send(params)
|
296
|
+
self.messages << { role: "user", content: params[:content] }
|
297
|
+
yield continue: true # Continue the streaming
|
298
|
+
|
299
|
+
client = OpenAI::Client.new(
|
300
|
+
access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"),
|
301
|
+
log_errors: true
|
302
|
+
)
|
303
|
+
self.messages << { role: "assistant", content: "" }
|
304
|
+
client.chat(
|
305
|
+
parameters: {
|
306
|
+
model: "gpt-4o-mini",
|
307
|
+
messages: messages,
|
308
|
+
stream: proc do |chunk, _bytesize|
|
309
|
+
if chunk.dig("choices", 0, "finish_reason") == "stop"
|
310
|
+
yield continue: false # Stop the streaming
|
311
|
+
end
|
312
|
+
|
313
|
+
self.messages.last[:content] << chunk.dig("choices", 0, "delta", "content")
|
314
|
+
yield continue: true
|
315
|
+
end
|
316
|
+
}
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
256
322
|
### Optional
|
257
323
|
|
258
324
|
#### Action Cable
|
data/lib/clapton/engine.rb
CHANGED
@@ -21,6 +21,10 @@ module Clapton
|
|
21
21
|
FileUtils.mkdir_p(components_path) unless components_path.exist?
|
22
22
|
FileUtils.touch(components_path.join(".keep"))
|
23
23
|
|
24
|
+
FileUtils.mkdir_p(Rails.root.join("public", "clapton")) unless Rails.root.join("public", "clapton").exist?
|
25
|
+
File.write(Rails.root.join("public", "clapton", "components.js"), File.read(File.join(__dir__, "javascripts", "dist", "components.js")))
|
26
|
+
File.write(Rails.root.join("public", "clapton", "client.js"), File.read(File.join(__dir__, "javascripts", "dist", "client.js")))
|
27
|
+
|
24
28
|
compile_components
|
25
29
|
|
26
30
|
listener = Listen.to(Rails.root.join("app", "components")) do |modified, added, removed|
|
@@ -32,9 +36,9 @@ module Clapton
|
|
32
36
|
end
|
33
37
|
|
34
38
|
def compile_components
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
puts "Clapton: Compiling components"
|
40
|
+
|
41
|
+
start_time = Time.now
|
38
42
|
Dir.glob(Rails.root.join("app", "components", "**", "*.rb")).each do |file|
|
39
43
|
code = File.read(file)
|
40
44
|
js = ""
|
@@ -44,12 +48,16 @@ module Clapton
|
|
44
48
|
js += "import { #{match[0]}Component } from '#{match[0]}Component';"
|
45
49
|
js += "\n"
|
46
50
|
end
|
51
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\(/, '\1@c.\2(')
|
52
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\./, '\1@c.\2().')
|
47
53
|
js += Ruby2JS.convert(code, preset: true)
|
48
54
|
js += "\n"
|
49
55
|
js += "export { #{File.basename(file, ".rb").camelize} };"
|
50
56
|
js += "\n"
|
51
57
|
File.write(Rails.root.join("public", "clapton", "#{File.basename(file, ".rb").camelize}.js"), js)
|
52
58
|
end
|
59
|
+
end_time = Time.now
|
60
|
+
puts "Clapton: Component compilation took #{end_time - start_time} seconds"
|
53
61
|
end
|
54
62
|
end
|
55
63
|
end
|
@@ -1287,8 +1287,7 @@ function getConfig(name) {
|
|
1287
1287
|
|
1288
1288
|
const updateComponent = async (component, state, property, target) => {
|
1289
1289
|
state[property] = target.value;
|
1290
|
-
component.
|
1291
|
-
const componentName = component.getAttribute("data-component");
|
1290
|
+
const componentName = component.dataset.component;
|
1292
1291
|
const module = await import(`${componentName}`);
|
1293
1292
|
const ComponentClass = module[componentName];
|
1294
1293
|
const instance = new ComponentClass(state, component.dataset.id);
|
@@ -1348,26 +1347,26 @@ const handleAction = async (target, stateName, fn) => {
|
|
1348
1347
|
if (!targetComponent)
|
1349
1348
|
return;
|
1350
1349
|
const component = target.closest(`[data-component]`);
|
1351
|
-
const attribute = target.
|
1350
|
+
const attribute = target.dataset.attribute;
|
1352
1351
|
if (attribute) {
|
1353
|
-
const state = JSON.parse(component.
|
1352
|
+
const state = JSON.parse(component.dataset.state || "{}");
|
1354
1353
|
if (target.tagName === "INPUT") {
|
1355
1354
|
state[attribute] = target.value;
|
1356
|
-
component.
|
1355
|
+
component.dataset.state = JSON.stringify(state);
|
1357
1356
|
}
|
1358
1357
|
}
|
1359
1358
|
claptonChannel.perform("action", {
|
1360
1359
|
data: {
|
1361
1360
|
component: {
|
1362
1361
|
name: stateName.replace("State", "Component"),
|
1363
|
-
id: targetComponent.
|
1362
|
+
id: targetComponent.dataset.id,
|
1364
1363
|
},
|
1365
1364
|
state: {
|
1366
1365
|
name: stateName,
|
1367
1366
|
action: fn,
|
1368
|
-
attributes: JSON.parse(targetComponent.
|
1367
|
+
attributes: JSON.parse(targetComponent.dataset.state || "{}"),
|
1369
1368
|
},
|
1370
|
-
params: JSON.parse(component.
|
1369
|
+
params: JSON.parse(component.dataset.state || "{}")
|
1371
1370
|
}
|
1372
1371
|
});
|
1373
1372
|
};
|
@@ -75,6 +75,20 @@ var Clapton = (function (exports) {
|
|
75
75
|
}
|
76
76
|
}
|
77
77
|
|
78
|
+
class Bold {
|
79
|
+
constructor(attributes = {}) {
|
80
|
+
this.children = [];
|
81
|
+
this.attributes = attributes;
|
82
|
+
}
|
83
|
+
get render() {
|
84
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
85
|
+
}
|
86
|
+
add(child) {
|
87
|
+
this.children.push(child);
|
88
|
+
return this;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
78
92
|
class Button {
|
79
93
|
constructor(attributes = {}) {
|
80
94
|
this.attributes = attributes;
|
@@ -372,18 +386,6 @@ var Clapton = (function (exports) {
|
|
372
386
|
}
|
373
387
|
}
|
374
388
|
|
375
|
-
class Component {
|
376
|
-
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
377
|
-
this._state = state;
|
378
|
-
this.id = id;
|
379
|
-
this._errors = errors;
|
380
|
-
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
381
|
-
}
|
382
|
-
get render() {
|
383
|
-
return this._root.render;
|
384
|
-
}
|
385
|
-
}
|
386
|
-
|
387
389
|
class TextField {
|
388
390
|
constructor(state, attribute, attributes = {}) {
|
389
391
|
this.state = state;
|
@@ -409,7 +411,119 @@ var Clapton = (function (exports) {
|
|
409
411
|
}
|
410
412
|
}
|
411
413
|
|
414
|
+
class TextArea {
|
415
|
+
constructor(state, attribute, attributes = {}) {
|
416
|
+
this.state = state;
|
417
|
+
this.attributes = attributes;
|
418
|
+
this.attribute = attribute;
|
419
|
+
this.attributes["data-attribute"] = attribute;
|
420
|
+
}
|
421
|
+
get render() {
|
422
|
+
return `<textarea ${htmlAttributes(this.attributes)}>${this.state[this.attribute] || ""}</textarea>`;
|
423
|
+
}
|
424
|
+
add_action(event, klass, fn, options = {}) {
|
425
|
+
this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
|
426
|
+
return this;
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
const Clapton = {
|
431
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
432
|
+
};
|
433
|
+
|
434
|
+
class Presets {
|
435
|
+
bq(...props) {
|
436
|
+
return new Clapton.BlockQuote(...props);
|
437
|
+
}
|
438
|
+
box(...props) {
|
439
|
+
return new Clapton.Box(...props);
|
440
|
+
}
|
441
|
+
b(...props) {
|
442
|
+
return new Clapton.Bold(...props);
|
443
|
+
}
|
444
|
+
button(...props) {
|
445
|
+
return new Clapton.Button(...props);
|
446
|
+
}
|
447
|
+
check(...props) {
|
448
|
+
return new Clapton.Checkbox(props[0], props[1], props[2]);
|
449
|
+
}
|
450
|
+
code(...props) {
|
451
|
+
return new Clapton.Code(...props);
|
452
|
+
}
|
453
|
+
datetime(...props) {
|
454
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2]);
|
455
|
+
}
|
456
|
+
el(...props) {
|
457
|
+
return new Clapton.Element(props[0], props[1]);
|
458
|
+
}
|
459
|
+
embed(...props) {
|
460
|
+
return new Clapton.Embed(props[0]);
|
461
|
+
}
|
462
|
+
em(...props) {
|
463
|
+
return new Clapton.Emphasis(...props);
|
464
|
+
}
|
465
|
+
form(...props) {
|
466
|
+
return new Clapton.Form(...props);
|
467
|
+
}
|
468
|
+
h(...props) {
|
469
|
+
return new Clapton.Heading(props[0], props[1]);
|
470
|
+
}
|
471
|
+
img(...props) {
|
472
|
+
return new Clapton.Image(props[0], props[1]);
|
473
|
+
}
|
474
|
+
a(...props) {
|
475
|
+
return new Clapton.Link(props[0], props[1]);
|
476
|
+
}
|
477
|
+
li(...props) {
|
478
|
+
return new Clapton.ListItem(...props);
|
479
|
+
}
|
480
|
+
ul(...props) {
|
481
|
+
return new Clapton.List(...props);
|
482
|
+
}
|
483
|
+
ol(...props) {
|
484
|
+
return new Clapton.OrderedList(...props);
|
485
|
+
}
|
486
|
+
p(...props) {
|
487
|
+
return new Clapton.Paragraph(...props);
|
488
|
+
}
|
489
|
+
q(...props) {
|
490
|
+
return new Clapton.Quote(...props);
|
491
|
+
}
|
492
|
+
radio(...props) {
|
493
|
+
return new Clapton.RadioButton(props[0], props[1], props[2]);
|
494
|
+
}
|
495
|
+
select(...props) {
|
496
|
+
return new Clapton.Select(props[0], props[1], props[2]);
|
497
|
+
}
|
498
|
+
span(...props) {
|
499
|
+
return new Clapton.Span(...props);
|
500
|
+
}
|
501
|
+
textarea(...props) {
|
502
|
+
return new Clapton.TextArea(props[0], props[1], props[2]);
|
503
|
+
}
|
504
|
+
input(...props) {
|
505
|
+
return new Clapton.TextField(props[0], props[1], props[2]);
|
506
|
+
}
|
507
|
+
text(...props) {
|
508
|
+
return new Clapton.Text(props[0]);
|
509
|
+
}
|
510
|
+
}
|
511
|
+
|
512
|
+
class Component {
|
513
|
+
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
514
|
+
this._state = state;
|
515
|
+
this.id = id;
|
516
|
+
this._errors = errors;
|
517
|
+
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
518
|
+
this._c = new Presets();
|
519
|
+
}
|
520
|
+
get render() {
|
521
|
+
return this._root.render;
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
412
525
|
exports.BlockQuote = BlockQuote;
|
526
|
+
exports.Bold = Bold;
|
413
527
|
exports.Box = Box;
|
414
528
|
exports.Button = Button;
|
415
529
|
exports.Checkbox = Checkbox;
|
@@ -432,6 +546,7 @@ var Clapton = (function (exports) {
|
|
432
546
|
exports.Select = Select;
|
433
547
|
exports.Span = Span;
|
434
548
|
exports.Text = Text;
|
549
|
+
exports.TextArea = TextArea;
|
435
550
|
exports.TextField = TextField;
|
436
551
|
|
437
552
|
return exports;
|
@@ -90,6 +90,20 @@ class Button {
|
|
90
90
|
}
|
91
91
|
}
|
92
92
|
|
93
|
+
class Bold {
|
94
|
+
constructor(attributes = {}) {
|
95
|
+
this.children = [];
|
96
|
+
this.attributes = attributes;
|
97
|
+
}
|
98
|
+
get render() {
|
99
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
100
|
+
}
|
101
|
+
add(child) {
|
102
|
+
this.children.push(child);
|
103
|
+
return this;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
93
107
|
class Checkbox {
|
94
108
|
constructor(state, attribute, attributes = {}) {
|
95
109
|
this.state = state;
|
@@ -369,12 +383,91 @@ class Span {
|
|
369
383
|
}
|
370
384
|
}
|
371
385
|
|
386
|
+
class Presets {
|
387
|
+
bq(...props) {
|
388
|
+
return new Clapton.BlockQuote(...props);
|
389
|
+
}
|
390
|
+
box(...props) {
|
391
|
+
return new Clapton.Box(...props);
|
392
|
+
}
|
393
|
+
b(...props) {
|
394
|
+
return new Clapton.Bold(...props);
|
395
|
+
}
|
396
|
+
button(...props) {
|
397
|
+
return new Clapton.Button(...props);
|
398
|
+
}
|
399
|
+
check(...props) {
|
400
|
+
return new Clapton.Checkbox(props[0], props[1], props[2]);
|
401
|
+
}
|
402
|
+
code(...props) {
|
403
|
+
return new Clapton.Code(...props);
|
404
|
+
}
|
405
|
+
datetime(...props) {
|
406
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2]);
|
407
|
+
}
|
408
|
+
el(...props) {
|
409
|
+
return new Clapton.Element(props[0], props[1]);
|
410
|
+
}
|
411
|
+
embed(...props) {
|
412
|
+
return new Clapton.Embed(props[0]);
|
413
|
+
}
|
414
|
+
em(...props) {
|
415
|
+
return new Clapton.Emphasis(...props);
|
416
|
+
}
|
417
|
+
form(...props) {
|
418
|
+
return new Clapton.Form(...props);
|
419
|
+
}
|
420
|
+
h(...props) {
|
421
|
+
return new Clapton.Heading(props[0], props[1]);
|
422
|
+
}
|
423
|
+
img(...props) {
|
424
|
+
return new Clapton.Image(props[0], props[1]);
|
425
|
+
}
|
426
|
+
a(...props) {
|
427
|
+
return new Clapton.Link(props[0], props[1]);
|
428
|
+
}
|
429
|
+
li(...props) {
|
430
|
+
return new Clapton.ListItem(...props);
|
431
|
+
}
|
432
|
+
ul(...props) {
|
433
|
+
return new Clapton.List(...props);
|
434
|
+
}
|
435
|
+
ol(...props) {
|
436
|
+
return new Clapton.OrderedList(...props);
|
437
|
+
}
|
438
|
+
p(...props) {
|
439
|
+
return new Clapton.Paragraph(...props);
|
440
|
+
}
|
441
|
+
q(...props) {
|
442
|
+
return new Clapton.Quote(...props);
|
443
|
+
}
|
444
|
+
radio(...props) {
|
445
|
+
return new Clapton.RadioButton(props[0], props[1], props[2]);
|
446
|
+
}
|
447
|
+
select(...props) {
|
448
|
+
return new Clapton.Select(props[0], props[1], props[2]);
|
449
|
+
}
|
450
|
+
span(...props) {
|
451
|
+
return new Clapton.Span(...props);
|
452
|
+
}
|
453
|
+
textarea(...props) {
|
454
|
+
return new Clapton.TextArea(props[0], props[1], props[2]);
|
455
|
+
}
|
456
|
+
input(...props) {
|
457
|
+
return new Clapton.TextField(props[0], props[1], props[2]);
|
458
|
+
}
|
459
|
+
text(...props) {
|
460
|
+
return new Clapton.Text(props[0]);
|
461
|
+
}
|
462
|
+
}
|
463
|
+
|
372
464
|
class Component {
|
373
465
|
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
374
466
|
this._state = state;
|
375
467
|
this.id = id;
|
376
468
|
this._errors = errors;
|
377
469
|
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
470
|
+
this._c = new Presets();
|
378
471
|
}
|
379
472
|
get render() {
|
380
473
|
return this._root.render;
|
@@ -406,8 +499,24 @@ class Text {
|
|
406
499
|
}
|
407
500
|
}
|
408
501
|
|
502
|
+
class TextArea {
|
503
|
+
constructor(state, attribute, attributes = {}) {
|
504
|
+
this.state = state;
|
505
|
+
this.attributes = attributes;
|
506
|
+
this.attribute = attribute;
|
507
|
+
this.attributes["data-attribute"] = attribute;
|
508
|
+
}
|
509
|
+
get render() {
|
510
|
+
return `<textarea ${htmlAttributes(this.attributes)}>${this.state[this.attribute] || ""}</textarea>`;
|
511
|
+
}
|
512
|
+
add_action(event, klass, fn, options = {}) {
|
513
|
+
this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
|
514
|
+
return this;
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
409
518
|
const Clapton = {
|
410
|
-
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed
|
519
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
411
520
|
};
|
412
521
|
|
413
522
|
export { Clapton };
|
@@ -9,12 +9,12 @@ export const handleAction = async (target: HTMLElement, stateName: string, fn: s
|
|
9
9
|
}
|
10
10
|
if (!targetComponent) return;
|
11
11
|
const component = target.closest(`[data-component]`) as HTMLElement;
|
12
|
-
const attribute = target.
|
12
|
+
const attribute = target.dataset.attribute;
|
13
13
|
if (attribute) {
|
14
|
-
const state = JSON.parse(component.
|
14
|
+
const state = JSON.parse(component.dataset.state || "{}");
|
15
15
|
if (target.tagName === "INPUT") {
|
16
16
|
state[attribute] = (target as HTMLInputElement).value;
|
17
|
-
component.
|
17
|
+
component.dataset.state = JSON.stringify(state);
|
18
18
|
}
|
19
19
|
};
|
20
20
|
claptonChannel.perform(
|
@@ -23,14 +23,14 @@ export const handleAction = async (target: HTMLElement, stateName: string, fn: s
|
|
23
23
|
data: {
|
24
24
|
component: {
|
25
25
|
name: stateName.replace("State", "Component"),
|
26
|
-
id: targetComponent.
|
26
|
+
id: targetComponent.dataset.id,
|
27
27
|
},
|
28
28
|
state: {
|
29
29
|
name: stateName,
|
30
30
|
action: fn,
|
31
|
-
attributes: JSON.parse(targetComponent.
|
31
|
+
attributes: JSON.parse(targetComponent.dataset.state || "{}"),
|
32
32
|
},
|
33
|
-
params: JSON.parse(component.
|
33
|
+
params: JSON.parse(component.dataset.state || "{}")
|
34
34
|
}
|
35
35
|
}
|
36
36
|
);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
2
|
+
import { Text } from "./text"
|
3
|
+
import { Bold } from "./bold"
|
4
|
+
|
5
|
+
describe("Bold", () => {
|
6
|
+
it("returns empty string if no params", () => {
|
7
|
+
expect(new Bold().render).toBe("<strong ></strong>")
|
8
|
+
})
|
9
|
+
|
10
|
+
it("returns attributes and data attributes", () => {
|
11
|
+
expect(new Bold({ id: "1", "data-foo": "bar" }).render).toBe(`<strong id='1' data-foo='bar'></strong>`)
|
12
|
+
})
|
13
|
+
|
14
|
+
|
15
|
+
it("adds children", () => {
|
16
|
+
const text = new Text("Hello")
|
17
|
+
const bold = new Bold()
|
18
|
+
bold.add(text)
|
19
|
+
expect(bold.render).toBe(`<strong >Hello</strong>`)
|
20
|
+
})
|
21
|
+
})
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { htmlAttributes } from "../html/html-attributes";
|
2
|
+
|
3
|
+
export class Bold {
|
4
|
+
attributes: Record<string, any>;
|
5
|
+
children: any[];
|
6
|
+
|
7
|
+
constructor(attributes: Record<string, any> = {}) {
|
8
|
+
this.children = [];
|
9
|
+
this.attributes = attributes;
|
10
|
+
}
|
11
|
+
|
12
|
+
get render(): string {
|
13
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
14
|
+
}
|
15
|
+
|
16
|
+
add(child: any): Bold {
|
17
|
+
this.children.push(child);
|
18
|
+
return this;
|
19
|
+
}
|
20
|
+
}
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import { Box } from "./box";
|
2
|
+
import { Presets } from "./presets";
|
2
3
|
|
3
4
|
export class Component {
|
4
5
|
id: string;
|
5
6
|
_state: any;
|
6
7
|
_errors: any[];
|
7
8
|
_root: Box;
|
9
|
+
_c: Presets;
|
8
10
|
|
9
11
|
constructor(state: any = {}, id: string = Math.random().toString(36).substring(2, 10), errors: any[] = []) {
|
10
12
|
this._state = state;
|
11
13
|
this.id = id;
|
12
14
|
this._errors = errors;
|
13
15
|
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
16
|
+
this._c = new Presets()
|
14
17
|
}
|
15
18
|
|
16
19
|
get render(): string {
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import { Clapton } from "components";
|
2
|
+
|
3
|
+
export class Presets {
|
4
|
+
bq(...props: any[]) {
|
5
|
+
return new Clapton.BlockQuote(...props)
|
6
|
+
}
|
7
|
+
|
8
|
+
box(...props: any[]) {
|
9
|
+
return new Clapton.Box(...props)
|
10
|
+
}
|
11
|
+
|
12
|
+
b(...props: any[]) {
|
13
|
+
return new Clapton.Bold(...props)
|
14
|
+
}
|
15
|
+
|
16
|
+
button(...props: any[]) {
|
17
|
+
return new Clapton.Button(...props)
|
18
|
+
}
|
19
|
+
|
20
|
+
check(...props: any[]) {
|
21
|
+
return new Clapton.Checkbox(props[0], props[1], props[2])
|
22
|
+
}
|
23
|
+
|
24
|
+
code(...props: any[]) {
|
25
|
+
return new Clapton.Code(...props)
|
26
|
+
}
|
27
|
+
|
28
|
+
datetime(...props: any[]) {
|
29
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2])
|
30
|
+
}
|
31
|
+
|
32
|
+
el(...props: any[]) {
|
33
|
+
return new Clapton.Element(props[0], props[1])
|
34
|
+
}
|
35
|
+
|
36
|
+
embed(...props: any[]) {
|
37
|
+
return new Clapton.Embed(props[0])
|
38
|
+
}
|
39
|
+
|
40
|
+
em(...props: any[]) {
|
41
|
+
return new Clapton.Emphasis(...props)
|
42
|
+
}
|
43
|
+
|
44
|
+
form(...props: any[]) {
|
45
|
+
return new Clapton.Form(...props)
|
46
|
+
}
|
47
|
+
|
48
|
+
h(...props: any[]) {
|
49
|
+
return new Clapton.Heading(props[0], props[1])
|
50
|
+
}
|
51
|
+
|
52
|
+
img(...props: any[]) {
|
53
|
+
return new Clapton.Image(props[0], props[1])
|
54
|
+
}
|
55
|
+
|
56
|
+
a(...props: any[]) {
|
57
|
+
return new Clapton.Link(props[0], props[1])
|
58
|
+
}
|
59
|
+
|
60
|
+
li(...props: any[]) {
|
61
|
+
return new Clapton.ListItem(...props)
|
62
|
+
}
|
63
|
+
|
64
|
+
ul(...props: any[]) {
|
65
|
+
return new Clapton.List(...props)
|
66
|
+
}
|
67
|
+
|
68
|
+
ol(...props: any[]) {
|
69
|
+
return new Clapton.OrderedList(...props)
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
p(...props: any[]) {
|
74
|
+
return new Clapton.Paragraph(...props)
|
75
|
+
}
|
76
|
+
|
77
|
+
q(...props: any[]) {
|
78
|
+
return new Clapton.Quote(...props)
|
79
|
+
}
|
80
|
+
|
81
|
+
radio(...props: any[]) {
|
82
|
+
return new Clapton.RadioButton(props[0], props[1], props[2])
|
83
|
+
}
|
84
|
+
|
85
|
+
select(...props: any[]) {
|
86
|
+
return new Clapton.Select(props[0], props[1], props[2])
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
span(...props: any[]) {
|
91
|
+
return new Clapton.Span(...props)
|
92
|
+
}
|
93
|
+
|
94
|
+
textarea(...props: any[]) {
|
95
|
+
return new Clapton.TextArea(props[0], props[1], props[2])
|
96
|
+
}
|
97
|
+
|
98
|
+
input(...props: any[]) {
|
99
|
+
return new Clapton.TextField(props[0], props[1], props[2])
|
100
|
+
}
|
101
|
+
|
102
|
+
text(...props: any[]) {
|
103
|
+
return new Clapton.Text(props[0])
|
104
|
+
}
|
105
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { BlockQuote } from "./components/block-quote"
|
2
2
|
import { Box } from "./components/box"
|
3
|
+
import { Bold } from "./components/bold"
|
3
4
|
import { Button } from "./components/button"
|
4
5
|
import { Checkbox } from "./components/checkbox"
|
5
6
|
import { Code } from "./components/code"
|
@@ -22,8 +23,8 @@ import { Span } from "./components/span"
|
|
22
23
|
import { Component } from "./components/component"
|
23
24
|
import { TextField } from "./components/text-field"
|
24
25
|
import { Text } from "./components/text"
|
25
|
-
|
26
|
+
import { TextArea } from "./components/text-area"
|
26
27
|
|
27
28
|
export {
|
28
|
-
|
29
|
+
Component, Box, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
29
30
|
};
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { BlockQuote } from "./components/block-quote"
|
2
2
|
import { Box } from "./components/box"
|
3
3
|
import { Button } from "./components/button"
|
4
|
+
import { Bold } from "./components/bold"
|
4
5
|
import { Checkbox } from "./components/checkbox"
|
5
6
|
import { Code } from "./components/code"
|
6
7
|
import { DateTimeField } from "./components/datetime-field"
|
@@ -22,8 +23,8 @@ import { Span } from "./components/span"
|
|
22
23
|
import { Component } from "./components/component"
|
23
24
|
import { TextField } from "./components/text-field"
|
24
25
|
import { Text } from "./components/text"
|
25
|
-
|
26
|
+
import { TextArea } from "./components/text-area"
|
26
27
|
|
27
28
|
export const Clapton = {
|
28
|
-
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed
|
29
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
29
30
|
};
|
@@ -2,8 +2,7 @@ import morphdom from "morphdom";
|
|
2
2
|
|
3
3
|
export const updateComponent = async (component: HTMLElement, state: any, property: string, target: HTMLInputElement) => {
|
4
4
|
state[property] = target.value;
|
5
|
-
component.
|
6
|
-
const componentName = component.getAttribute("data-component") as string;
|
5
|
+
const componentName = component.dataset.component as string;
|
7
6
|
const module = await import(`${componentName}`);
|
8
7
|
const ComponentClass = module[componentName] as any;
|
9
8
|
const instance = new ComponentClass(state, component.dataset.id);
|
@@ -3,14 +3,22 @@ module Clapton
|
|
3
3
|
module Base
|
4
4
|
require "execjs"
|
5
5
|
|
6
|
-
def render_component(component,
|
6
|
+
def render_component(component, params)
|
7
7
|
js = File.read(File.join(__dir__, "..", "javascripts", "dist", "components-for-test.js"))
|
8
8
|
Dir.glob(Rails.root.join("app", "components", "**", "*.rb")).each do |file|
|
9
|
-
|
9
|
+
code = File.read(file)
|
10
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\(/, '\1@c.\2(')
|
11
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\./, '\1@c.\2().')
|
12
|
+
js += Ruby2JS.convert(code, preset: true)
|
10
13
|
js += "\n"
|
11
14
|
end
|
15
|
+
js = js.sub("const Clapton = {
|
16
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
17
|
+
};", "const Clapton = {
|
18
|
+
Box, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
19
|
+
};")
|
12
20
|
context = ExecJS.compile(js)
|
13
|
-
html = context.eval("new #{component.name.camelize}(#{
|
21
|
+
html = context.eval("new #{component.name.camelize}(#{params.to_json}).render")
|
14
22
|
@page = Capybara.string(html)
|
15
23
|
end
|
16
24
|
|
data/lib/clapton/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clapton
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moeki Kawakami
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -6708,6 +6708,8 @@ files:
|
|
6708
6708
|
- lib/clapton/javascripts/src/components.ts
|
6709
6709
|
- lib/clapton/javascripts/src/components/block-quote.spec.ts
|
6710
6710
|
- lib/clapton/javascripts/src/components/block-quote.ts
|
6711
|
+
- lib/clapton/javascripts/src/components/bold.spec.ts
|
6712
|
+
- lib/clapton/javascripts/src/components/bold.ts
|
6711
6713
|
- lib/clapton/javascripts/src/components/box.spec.ts
|
6712
6714
|
- lib/clapton/javascripts/src/components/box.ts
|
6713
6715
|
- lib/clapton/javascripts/src/components/button.spec.ts
|
@@ -6716,7 +6718,6 @@ files:
|
|
6716
6718
|
- lib/clapton/javascripts/src/components/checkbox.ts
|
6717
6719
|
- lib/clapton/javascripts/src/components/code.spec.ts
|
6718
6720
|
- lib/clapton/javascripts/src/components/code.ts
|
6719
|
-
- lib/clapton/javascripts/src/components/component.spec.ts
|
6720
6721
|
- lib/clapton/javascripts/src/components/component.ts
|
6721
6722
|
- lib/clapton/javascripts/src/components/datetime-field.spec.ts
|
6722
6723
|
- lib/clapton/javascripts/src/components/datetime-field.ts
|
@@ -6742,6 +6743,7 @@ files:
|
|
6742
6743
|
- lib/clapton/javascripts/src/components/ordered-list.ts
|
6743
6744
|
- lib/clapton/javascripts/src/components/paragraph.spec.ts
|
6744
6745
|
- lib/clapton/javascripts/src/components/paragraph.ts
|
6746
|
+
- lib/clapton/javascripts/src/components/presets.ts
|
6745
6747
|
- lib/clapton/javascripts/src/components/quote.spec.ts
|
6746
6748
|
- lib/clapton/javascripts/src/components/quote.ts
|
6747
6749
|
- lib/clapton/javascripts/src/components/radio-button.spec.ts
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
2
|
-
import { Component } from "./component"
|
3
|
-
import { Text } from "./text"
|
4
|
-
|
5
|
-
class TestComponent extends Component {
|
6
|
-
get render() {
|
7
|
-
this._root.add(new Text("Hello, world!"))
|
8
|
-
return this._root.render
|
9
|
-
}
|
10
|
-
}
|
11
|
-
|
12
|
-
describe("Component", () => {
|
13
|
-
it("returns empty string if no params", () => {
|
14
|
-
expect(new TestComponent().render).toMatch(/<div data-component='TestComponent' data-state='{}' data-id='.{8}'>Hello, world!<\/div>/)
|
15
|
-
})
|
16
|
-
})
|