clapton 0.0.11 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de5da62107c88924d00beff129af9064183ac535a0562b6d643c40b7e31e04ef
4
- data.tar.gz: 77fe202f9dc0a04d163e42f876a8d2da74d36fd23f4216ebca8f0acc3997a90a
3
+ metadata.gz: e238016cc8650d4f218c6a542329d5fcbbe844dd68124f2464063df016f25c1f
4
+ data.tar.gz: 36ae66989cf010520398499d54f007dbebb7deb6f91d37a578677eb8555bb53c
5
5
  SHA512:
6
- metadata.gz: 82e99e9ccde384bf5a0a6b3645511da0e379bb5bfb49d85c006c852efca41aa987950fcbeb4bb1876220596b74432e1fb64f715a769b057b58ea9a7e1a43a309
7
- data.tar.gz: ce5ad2a40e2f943b403973c392fd6587ed0b401e86f076de0409ba51fe7664616fe20334d2ed6a819a7c2705b6942e48db2afb66c6e9539554a0bae6c0b62c37
6
+ metadata.gz: ac72a228524042ed8748391c92076d3f05707b8f0763485ea7316f4b27e6c2a11c29643e4a6f43fc351bfffeb757dfd633afdcc710b81487f6a8754567a7d308
7
+ data.tar.gz: c5f4c34dbfc32e4ad8dd0bff57f160568cc98875f9b918c90275aa8abe39aa54dc9a1dddb8ed58eb80c380b45ec3476c78a0afb9de0630e4f65d3d0bfb5a6f66
data/README.md CHANGED
@@ -159,6 +159,23 @@ After running the generator, you will see the following files:
159
159
  - `app/components/task_list_component.rb`
160
160
  - `app/states/task_list_state.rb`
161
161
 
162
+ ### Special Event
163
+
164
+ #### render
165
+
166
+ The `render` event is a special event that is triggered when the component is rendered.
167
+
168
+ ```ruby
169
+ # app/components/task_list_component.rb
170
+ class TaskListComponent < Clapton::Component
171
+ def render
172
+ # ...
173
+ @root.add_action(:render, :TaskListState, :add_empty_task, debounce: 500)
174
+ @root.render
175
+ end
176
+ end
177
+ ```
178
+
162
179
  ### Preset Components
163
180
 
164
181
  ```ruby
@@ -183,6 +200,8 @@ datetime_field = Clapton::DateTimeField.new(:ExampleState, :example_attribute, {
183
200
  element = Clapton::Element.new("div", { id: "example-element" })
184
201
  element.add(Clapton::Text.new("Hello"))
185
202
 
203
+ embed = Clapton::Embed.new("<blockquote>This is a test</blockquote>")
204
+
186
205
  emphasis = Clapton::Emphasis.new
187
206
  emphasis.add(Clapton::Text.new("Hello"))
188
207
 
@@ -310,6 +329,19 @@ class TaskListComponentTest < ActiveSupport::TestCase
310
329
  end
311
330
  ```
312
331
 
332
+ ## Deployment
333
+
334
+ Run `bundle exec rake clapton:compile` to compile the components.
335
+
336
+ `app/components` is codes that are compiled to JavaScript.
337
+ So, you need to ignore the directory from autoloading.
338
+
339
+ ```ruby
340
+ # config/application.rb
341
+
342
+ Rails.autoloaders.main.ignore(Rails.root.join("app/components"))
343
+ ```
344
+
313
345
  ## Development
314
346
 
315
347
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/dev` to start the development server.
@@ -16,14 +16,13 @@ module Clapton
16
16
  ActionCable.server.config.logger = Rails.logger
17
17
  end
18
18
 
19
- components_path = Rails.root.join("app", "components")
20
-
21
- compile_components if components_path.exist?
22
-
23
- if Rails.env.development?
19
+ if Rails.env.development? || Rails.env.test?
20
+ components_path = Rails.root.join("app", "components")
24
21
  FileUtils.mkdir_p(components_path) unless components_path.exist?
25
22
  FileUtils.touch(components_path.join(".keep"))
26
23
 
24
+ compile_components
25
+
27
26
  listener = Listen.to(Rails.root.join("app", "components")) do |modified, added, removed|
28
27
  compile_components
29
28
  end
@@ -1285,6 +1285,31 @@ function getConfig(name) {
1285
1285
  }
1286
1286
  }
1287
1287
 
1288
+ const updateComponent = (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 ComponentClass = window[componentName];
1293
+ const instance = new ComponentClass(state, component.dataset.id);
1294
+ morphdom(component, instance.render);
1295
+ };
1296
+
1297
+ const initializeInputs = () => {
1298
+ const inputElements = document.querySelectorAll("[data-attribute]");
1299
+ inputElements.forEach((element) => {
1300
+ const attribute = element.getAttribute("data-attribute");
1301
+ const component = element.closest(`[data-component]`);
1302
+ const state = JSON.parse(component.getAttribute("data-state") || "{}");
1303
+ if (!attribute || !component)
1304
+ return;
1305
+ if (element.tagName === "INPUT") {
1306
+ element.addEventListener("input", (event) => {
1307
+ updateComponent(component, state, attribute, event.target);
1308
+ });
1309
+ }
1310
+ });
1311
+ };
1312
+
1288
1313
  const consumer = createConsumer();
1289
1314
 
1290
1315
  const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonChannel", {
@@ -1302,13 +1327,20 @@ const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonChannel",
1302
1327
  return true;
1303
1328
  }
1304
1329
  });
1330
+
1305
1331
  initializeInputs();
1306
1332
  initializeActions();
1307
1333
  }
1308
1334
  });
1309
1335
 
1310
1336
  const handleAction = async (target, stateName, fn) => {
1311
- const targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`);
1337
+ let targetComponent = target;
1338
+ if (target.dataset.component === stateName.replace("State", "Component")) {
1339
+ targetComponent = target;
1340
+ }
1341
+ else {
1342
+ targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`);
1343
+ }
1312
1344
  if (!targetComponent)
1313
1345
  return;
1314
1346
  const component = target.closest(`[data-component]`);
@@ -1356,6 +1388,13 @@ const initializeActionsForElement = (element) => {
1356
1388
  const { eventType, componentName, stateName, fnName, bounceTime } = splitActionAttribute(action);
1357
1389
  if (!eventType || !componentName || !fnName)
1358
1390
  return;
1391
+ if (eventType === "render") {
1392
+ setTimeout(() => {
1393
+ handleAction(element, stateName, fnName);
1394
+ }, 100);
1395
+ element.setAttribute("data-render-event-handler", "true");
1396
+ return;
1397
+ }
1359
1398
  if (bounceTime > 0) {
1360
1399
  element.addEventListener(eventType, debounce((event) => handleAction(event.target, stateName, fnName), bounceTime));
1361
1400
  }
@@ -1366,31 +1405,6 @@ const initializeActionsForElement = (element) => {
1366
1405
  });
1367
1406
  };
1368
1407
 
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
1408
  const initializeComponents = () => {
1395
1409
  const components = document.querySelector("#clapton")?.getAttribute("data-clapton") || "[]";
1396
1410
  JSON.parse(components).forEach((component) => createAndAppendComponent(component, document.querySelector("#clapton")));
@@ -1411,5 +1425,5 @@ const createAndAppendComponent = (component, element) => {
1411
1425
  document.addEventListener("DOMContentLoaded", () => {
1412
1426
  initializeComponents();
1413
1427
  initializeActions();
1414
- initializeInputs$1();
1428
+ initializeInputs();
1415
1429
  });
@@ -69,6 +69,10 @@ var Clapton = (function (exports) {
69
69
  get render() {
70
70
  return `<div ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</div>`;
71
71
  }
72
+ add_action(eventType, stateName, fnName, options = {}) {
73
+ this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${eventType}->${stateName}#${fnName}@${options.debounce || 0}`;
74
+ return this;
75
+ }
72
76
  }
73
77
 
74
78
  class Button {
@@ -176,6 +180,15 @@ var Clapton = (function (exports) {
176
180
  }
177
181
  }
178
182
 
183
+ class Embed {
184
+ constructor(html) {
185
+ this.html = html;
186
+ }
187
+ get render() {
188
+ return this.html;
189
+ }
190
+ }
191
+
179
192
  class Emphasis {
180
193
  constructor(attributes = {}) {
181
194
  this.children = [];
@@ -404,6 +417,7 @@ var Clapton = (function (exports) {
404
417
  exports.Component = Component;
405
418
  exports.DateTimeField = DateTimeField;
406
419
  exports.Element = Element;
420
+ exports.Embed = Embed;
407
421
  exports.Emphasis = Emphasis;
408
422
  exports.Form = Form;
409
423
  exports.Heading = Heading;
@@ -1,7 +1,12 @@
1
1
  import { claptonChannel } from "../channel/clapton-channel";
2
2
 
3
3
  export const handleAction = async (target: HTMLElement, stateName: string, fn: string) => {
4
- const targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`) as HTMLElement;
4
+ let targetComponent = target;
5
+ if (target.dataset.component === stateName.replace("State", "Component")) {
6
+ targetComponent = target
7
+ } else {
8
+ targetComponent = target.closest(`[data-component="${stateName.replace("State", "Component")}"]`) as HTMLElement;
9
+ }
5
10
  if (!targetComponent) return;
6
11
  const component = target.closest(`[data-component]`) as HTMLElement;
7
12
  const attribute = target.getAttribute("data-attribute");
@@ -14,6 +14,14 @@ const initializeActionsForElement = (element: HTMLElement) => {
14
14
  const { eventType, componentName, stateName, fnName, bounceTime } = splitActionAttribute(action);
15
15
  if (!eventType || !componentName || !fnName) return;
16
16
 
17
+ if (eventType === "render") {
18
+ setTimeout(() => {
19
+ handleAction(element, stateName, fnName)
20
+ }, 100);
21
+ element.setAttribute("data-render-event-handler", "true");
22
+ return;
23
+ }
24
+
17
25
  if (bounceTime > 0) {
18
26
  element.addEventListener(eventType, debounce((event) =>
19
27
  handleAction(event.target as HTMLElement, stateName, fnName), bounceTime)
@@ -1,7 +1,7 @@
1
1
  import morphdom from "morphdom"
2
2
  import { createConsumer } from "@rails/actioncable"
3
3
  import { initializeActions } from "../actions/initialize-actions.ts"
4
-
4
+ import { initializeInputs } from "../inputs/initialize-inputs.ts"
5
5
  const consumer = createConsumer()
6
6
 
7
7
  export const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonChannel", {
@@ -19,6 +19,7 @@ export const claptonChannel = consumer.subscriptions.create("Clapton::ClaptonCha
19
19
  return true;
20
20
  }
21
21
  });
22
+
22
23
  initializeInputs();
23
24
  initializeActions();
24
25
  }
@@ -1,6 +1,3 @@
1
- import { splitActionAttribute } from "./html/split-action-attribute"
2
- import { updateComponent } from "./dom/update-component"
3
- import { handleAction } from "./actions/handle-action"
4
1
  import { initializeActions } from "actions/initialize-actions";
5
2
  import { initializeInputs } from "inputs/initialize-inputs";
6
3
 
@@ -17,4 +17,9 @@ export class Box {
17
17
  get render(): string {
18
18
  return `<div ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</div>`;
19
19
  }
20
+
21
+ add_action(eventType: string, stateName: string, fnName: string, options: Record<string, any> = {}): this {
22
+ this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${eventType}->${stateName}#${fnName}@${options.debounce || 0}`;
23
+ return this;
24
+ }
20
25
  }
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { Embed } from "./embed"
3
+
4
+ describe("Embed", () => {
5
+ it("returns empty string if no params", () => {
6
+ expect(new Embed("<blockquote></blockquote>").render).toBe("<blockquote></blockquote>")
7
+ })
8
+ })
@@ -0,0 +1,11 @@
1
+ export class Embed {
2
+ html: string;
3
+
4
+ constructor(html: string) {
5
+ this.html = html;
6
+ }
7
+
8
+ get render(): string {
9
+ return this.html;
10
+ }
11
+ }
@@ -5,6 +5,7 @@ import { Checkbox } from "./components/checkbox"
5
5
  import { Code } from "./components/code"
6
6
  import { DateTimeField } from "./components/datetime-field"
7
7
  import { Element } from "./components/element"
8
+ import { Embed } from "./components/embed"
8
9
  import { Emphasis } from "./components/emphasis"
9
10
  import { Form } from "./components/form"
10
11
  import { Heading } from "./components/heading"
@@ -22,4 +23,4 @@ import { Component } from "./components/component"
22
23
  import { TextField } from "./components/text-field"
23
24
  import { Text } from "./components/text"
24
25
 
25
- export { Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span };
26
+ export { Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed };
@@ -1,4 +1,4 @@
1
- import { updateComponent } from "dom/update-component";
1
+ import { updateComponent } from "../dom/update-component";
2
2
 
3
3
  export const initializeInputs = () => {
4
4
  const inputElements = document.querySelectorAll("[data-attribute]");
@@ -1,3 +1,4 @@
1
1
  module Clapton
2
- VERSION = '0.0.11'
2
+ VERSION = '0.0.13'
3
3
  end
4
+
@@ -1,4 +1,6 @@
1
- # desc "Explaining what the task does"
2
- # task :clapton do
3
- # # Task goes here
4
- # end
1
+ namespace :clapton do
2
+ task compile: :environment do
3
+ Clapton::Engine.compile_components
4
+ puts "Clapton components compiled"
5
+ end
6
+ end
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.11
4
+ version: 0.0.13
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-13 00:00:00.000000000 Z
11
+ date: 2024-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -6675,6 +6675,8 @@ files:
6675
6675
  - lib/clapton/javascripts/src/components/datetime-field.ts
6676
6676
  - lib/clapton/javascripts/src/components/element.spec.ts
6677
6677
  - lib/clapton/javascripts/src/components/element.ts
6678
+ - lib/clapton/javascripts/src/components/embed.spec.ts
6679
+ - lib/clapton/javascripts/src/components/embed.ts
6678
6680
  - lib/clapton/javascripts/src/components/emphasis.spec.ts
6679
6681
  - lib/clapton/javascripts/src/components/emphasis.ts
6680
6682
  - lib/clapton/javascripts/src/components/form.spec.ts