shimmer 0.0.4 → 0.0.5

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: 32060fa00134259da2eb2723aa0ba4e14a95bbd85f9b214797e76778f2c1500b
4
- data.tar.gz: 18c942b4f19f582e7ae45a89aad5b03d6192201ea0849e14bf9e8f567dccc069
3
+ metadata.gz: 419d4bba740070b286202f099a64f31929b029505bc3411a9db8b922d51e0eec
4
+ data.tar.gz: 5f75ac2e0c8ac3fc82335efe2dd8bd98270f9506433bfa0e894369e952fb6aef
5
5
  SHA512:
6
- metadata.gz: 752d5f326fcf637ec3fa4d37e3cd6c5a9df88d7c408697ad7c3153b65f75ef1c8fc7f6cbeb8e29d44efe25b314a209a3bf615bd30146f704beb05cd1b156ca52
7
- data.tar.gz: f8465c8a123e351a84e970fcbb6629e79a0b2759095c6378f248255cb41fdacdbb0dbce46e8751438c0737669d22e2eb8dfe8a9afb3574adfc0d0e031976b716
6
+ metadata.gz: ec203c95a4fc2088731aa269c0c27577fbd59fa4070d972d6ad066d469b6203b54fb8ffafedb4295d7387e981d6a50a87ca981f8c92c9f5c624d45aed851b906
7
+ data.tar.gz: cdae8b03095d4f3c7f38f5ccbe47864ab41ade50bf1e2d45fe3da2e112e2a11e3d733ed73d2b581a833be9d6f5f263795aff008ccfcd5da51d45c52227a560c9
@@ -6,7 +6,7 @@ module Shimmer
6
6
 
7
7
  included do
8
8
  def ui
9
- @ui ||= RemoteNavigator.new turbo_stream: turbo_stream
9
+ @ui ||= RemoteNavigator.new(self)
10
10
  end
11
11
 
12
12
  def default_render
@@ -17,7 +17,7 @@ module Shimmer
17
17
  end
18
18
 
19
19
  helper_method :modal_path
20
- def modal_path(url, id: nil, size: nil, close: false)
20
+ def modal_path(url, id: nil, size: nil, close: true)
21
21
  "javascript:ui.modal.open(#{{url: url, id: id, size: size, close: close}.to_json})"
22
22
  end
23
23
 
@@ -26,6 +26,11 @@ module Shimmer
26
26
  "javascript:ui.modal.close(#{{id: id}.to_json})"
27
27
  end
28
28
 
29
+ helper_method :popover_path
30
+ def popover_path(url, id: nil, selector: nil, placement: nil)
31
+ "javascript:ui.popover.open(#{{url: url, id: id, selector: selector, placement: placement}.compact.to_json})"
32
+ end
33
+
29
34
  def shimmer_request?
30
35
  request.headers["X-Shimmer"].present?
31
36
  end
@@ -42,10 +47,10 @@ module Shimmer
42
47
  end
43
48
 
44
49
  class RemoteNavigator
45
- attr_reader :turbo_stream
50
+ delegate :polymorphic_path, to: :@controller
46
51
 
47
- def initialize(turbo_stream:)
48
- @turbo_stream = turbo_stream
52
+ def initialize(controller)
53
+ @controller = controller
49
54
  end
50
55
 
51
56
  def queued_updates
@@ -76,18 +81,32 @@ module Shimmer
76
81
  queued_updates.push turbo_stream.remove(id)
77
82
  end
78
83
 
79
- def open_modal(path)
80
- run_javascript "ui.modal.open('#{path}')"
84
+ def open_modal(path, id: nil, size: nil, close: true)
85
+ run_javascript "ui.modal.open(#{{url: url, id: id, size: size, close: close}.to_json})"
81
86
  end
82
87
 
83
88
  def close_modal
84
89
  run_javascript "ui.modal.close()"
85
90
  end
86
91
 
92
+ def open_popover(path, selector:, placement: nil)
93
+ run_javascript "ui.popover.open(#{{url: url, selector: selector, placement: placement}.to_json})"
94
+ end
95
+
96
+ def close_popover
97
+ run_javascript "ui.popover.close()"
98
+ end
99
+
87
100
  def navigate_to(path)
88
101
  close_modal
89
102
  path = polymorphic_path(path) unless path.is_a?(String)
90
103
  run_javascript "Turbo.visit('#{path}')"
91
104
  end
105
+
106
+ private
107
+
108
+ def turbo_stream
109
+ @controller.send(:turbo_stream)
110
+ end
92
111
  end
93
112
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shimmer
4
- VERSION = "0.0.4"
4
+ VERSION = "0.0.5"
5
5
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerdgeschoss/shimmer",
3
- "version": "0.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "Simple application development in Rails",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -22,6 +22,8 @@
22
22
  ],
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
+ "@hotwired/stimulus": "^3.0.1",
26
+ "@popperjs/core": "^2.11.0",
25
27
  "@rails/request.js": "^0.0.6"
26
28
  },
27
29
  "devDependencies": {
data/rollup.config.js CHANGED
@@ -4,7 +4,7 @@ import pkg from "./package.json";
4
4
 
5
5
  export default {
6
6
  input: "./src/index.ts",
7
- external: ["@rails/request.js"],
7
+ external: ["@rails/request.js", "@hotwired/stimulus", "@popperjs/core"],
8
8
  plugins: [
9
9
  cleaner({
10
10
  targets: ["./dist/"],
@@ -0,0 +1,9 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect(): void {
5
+ const script = (this.element as HTMLDivElement).innerText;
6
+ (0, eval)(script);
7
+ this.element.remove();
8
+ }
9
+ }
data/src/index.ts CHANGED
@@ -1,17 +1,32 @@
1
- declare global {
2
- interface Window {
3
- ui: typeof ui;
4
- }
5
- }
6
-
1
+ import type { Application } from "@hotwired/stimulus";
7
2
  import { ModalPresenter } from "./modal";
3
+ import { PopoverPresenter } from "./popover";
4
+ import RemoteNavigationController from "./controllers/remote-navigation";
8
5
  import "./touch";
9
6
 
10
- export const ui = {
11
- modal: new ModalPresenter(),
12
- };
13
-
14
- window.ui = ui;
15
-
16
7
  export { registerServiceWorker } from "./serviceworker";
17
8
  export { currentLocale } from "./locale";
9
+
10
+ declare global {
11
+ interface Window {
12
+ ui?: {
13
+ modal: ModalPresenter;
14
+ popover: PopoverPresenter;
15
+ };
16
+ }
17
+ }
18
+
19
+ export async function start({
20
+ application,
21
+ }: {
22
+ application: Application;
23
+ }): Promise<void> {
24
+ const root = document.createElement("div");
25
+ root.id = "shimmer";
26
+ document.body.append(root);
27
+ application.register("remote-navigation", RemoteNavigationController);
28
+ window.ui = {
29
+ modal: new ModalPresenter(),
30
+ popover: new PopoverPresenter(),
31
+ };
32
+ }
data/src/modal.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { get } from "@rails/request.js";
1
+ import { loaded, createElement, nextFrame, getHTML } from "./util";
2
2
 
3
3
  export interface ModalOptions {
4
4
  id?: string;
@@ -7,33 +7,6 @@ export interface ModalOptions {
7
7
  close?: boolean;
8
8
  }
9
9
 
10
- const loaded: Promise<void> = new Promise((res) => {
11
- document.addEventListener("DOMContentLoaded", () => {
12
- res();
13
- });
14
- });
15
-
16
- async function nextFrame(): Promise<void> {
17
- return new Promise((res) => {
18
- setTimeout(res, 10);
19
- });
20
- }
21
-
22
- async function getHTML(url: string): Promise<string> {
23
- const response = await get(url, { headers: { "X-Shimmer": "true" } });
24
- if (response.ok) {
25
- return await response.response.text();
26
- }
27
- return "";
28
- }
29
-
30
- function createElement(parent: HTMLElement, className: string): HTMLDivElement {
31
- const element = document.createElement("div");
32
- element.className = className;
33
- parent.append(element);
34
- return element;
35
- }
36
-
37
10
  export class ModalPresenter {
38
11
  private modals: Record<string, Modal> = {};
39
12
 
data/src/popover.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { Instance as Popper, createPopper, Placement } from "@popperjs/core";
2
+ import { createElement, getHTML } from "./util";
3
+
4
+ export interface PopoverOptions {
5
+ id?: string;
6
+ url: string;
7
+ selector?: HTMLElement | string;
8
+ placement?: Placement;
9
+ }
10
+
11
+ export class PopoverPresenter {
12
+ private popovers: Record<string, Popover> = {};
13
+ private lastClickedElement?: HTMLElement;
14
+
15
+ constructor() {
16
+ document.addEventListener("click", this.trackElement);
17
+ }
18
+
19
+ async open(options: PopoverOptions): Promise<void> {
20
+ const id = (options.id = options.id ?? "default-popover");
21
+ options.selector = options.selector ?? this.lastClickedElement;
22
+ (this.popovers[id] = new Popover()).open(options);
23
+ }
24
+
25
+ async close({ id }: { id?: string } = {}): Promise<void> {
26
+ let promise: Promise<unknown> | null = null;
27
+ if (id) {
28
+ promise = this.popovers[id]?.close();
29
+ delete this.popovers[id];
30
+ } else {
31
+ promise = Promise.all(Object.values(this.popovers).map((e) => e.close()));
32
+ this.popovers = {};
33
+ }
34
+ await promise;
35
+ }
36
+
37
+ private trackElement = (event: MouseEvent): void => {
38
+ this.lastClickedElement = event.target as HTMLElement;
39
+ };
40
+ }
41
+
42
+ export class Popover {
43
+ private popper?: Popper;
44
+ private popoverDiv?: HTMLDivElement;
45
+
46
+ async open({ url, selector, placement }: PopoverOptions): Promise<void> {
47
+ const root =
48
+ typeof selector === "string"
49
+ ? document.querySelector(selector)
50
+ : selector;
51
+ if (!root) {
52
+ return;
53
+ }
54
+ const popoverDiv = createElement(document.body, "popover");
55
+ const arrow = createElement(popoverDiv, "popover__arrow");
56
+ arrow.setAttribute("data-popper-arrow", "true");
57
+ const content = createElement(popoverDiv, "popover__content");
58
+ content.innerHTML = await getHTML(url);
59
+ this.popper = createPopper(root, popoverDiv, {
60
+ placement: placement ?? "auto",
61
+ modifiers: [
62
+ {
63
+ name: "offset",
64
+ options: {
65
+ offset: [0, 8],
66
+ },
67
+ },
68
+ ],
69
+ });
70
+ this.popoverDiv = popoverDiv;
71
+ document.addEventListener("click", this.clickOutside);
72
+ }
73
+
74
+ async close(): Promise<void> {
75
+ this.popper?.destroy();
76
+ this.popper = undefined;
77
+ document.removeEventListener("click", this.clickOutside);
78
+ this.popoverDiv?.remove();
79
+ this.popoverDiv = undefined;
80
+ }
81
+
82
+ private clickOutside = (event: MouseEvent): void => {
83
+ if (this.popoverDiv?.contains(event.target as HTMLElement)) {
84
+ return;
85
+ }
86
+ event.preventDefault();
87
+ this.close();
88
+ };
89
+ }
data/src/util.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { get } from "@rails/request.js";
2
+
3
+ export async function getHTML(url: string): Promise<string> {
4
+ const response = await get(url, { headers: { "X-Shimmer": "true" } });
5
+ if (response.ok) {
6
+ return await response.response.text();
7
+ }
8
+ return "";
9
+ }
10
+
11
+ export const loaded: Promise<void> = new Promise((res) => {
12
+ document.addEventListener("DOMContentLoaded", () => {
13
+ res();
14
+ });
15
+ });
16
+
17
+ export async function nextFrame(): Promise<void> {
18
+ return new Promise((res) => {
19
+ setTimeout(res, 10);
20
+ });
21
+ }
22
+
23
+ export function createElement(
24
+ parent: HTMLElement,
25
+ className: string
26
+ ): HTMLDivElement {
27
+ const element = document.createElement("div");
28
+ element.className = className;
29
+ parent.append(element);
30
+ return element;
31
+ }
data/yarn.lock CHANGED
@@ -17,6 +17,11 @@
17
17
  minimatch "^3.0.4"
18
18
  strip-json-comments "^3.1.1"
19
19
 
20
+ "@hotwired/stimulus@^3.0.1":
21
+ version "3.0.1"
22
+ resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.0.1.tgz#141f15645acaa3b133b7c247cad58ae252ffae85"
23
+ integrity sha512-oHsJhgY2cip+K2ED7vKUNd2P+BEswVhrCYcJ802DSsblJFv7mPFVk3cQKvm2vHgHeDVdnj7oOKrBbzp1u8D+KA==
24
+
20
25
  "@humanwhocodes/config-array@^0.9.2":
21
26
  version "0.9.2"
22
27
  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914"
@@ -52,6 +57,11 @@
52
57
  "@nodelib/fs.scandir" "2.1.5"
53
58
  fastq "^1.6.0"
54
59
 
60
+ "@popperjs/core@^2.11.0":
61
+ version "2.11.0"
62
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
63
+ integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
64
+
55
65
  "@rails/request.js@^0.0.6":
56
66
  version "0.0.6"
57
67
  resolved "https://registry.yarnpkg.com/@rails/request.js/-/request.js-0.0.6.tgz#5f0347a9f363e50ec45118c7134080490cda81d8"
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Ravens
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-21 00:00:00.000000000 Z
11
+ date: 2021-12-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description:
13
+ description:
14
14
  email:
15
15
  - jens@nerdgeschoss.de
16
16
  executables: []
@@ -48,12 +48,14 @@ files:
48
48
  - lib/shimmer/version.rb
49
49
  - package.json
50
50
  - rollup.config.js
51
- - shimmer.gemspec
51
+ - src/controllers/remote-navigation.ts
52
52
  - src/index.ts
53
53
  - src/locale.ts
54
54
  - src/modal.ts
55
+ - src/popover.ts
55
56
  - src/serviceworker.ts
56
57
  - src/touch.ts
58
+ - src/util.ts
57
59
  - tsconfig.json
58
60
  - typings.d.ts
59
61
  - yarn.lock
@@ -63,7 +65,7 @@ licenses:
63
65
  metadata:
64
66
  homepage_uri: https://github.com/nerdgeschoss/shimmer
65
67
  source_code_uri: https://github.com/nerdgeschoss/shimmer
66
- post_install_message:
68
+ post_install_message:
67
69
  rdoc_options: []
68
70
  require_paths:
69
71
  - lib
@@ -79,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
81
  version: '0'
80
82
  requirements: []
81
83
  rubygems_version: 3.2.32
82
- signing_key:
84
+ signing_key:
83
85
  specification_version: 4
84
86
  summary: Shimmer brings all the bells and whistles of a hotwired application, right
85
87
  from the start.
data/shimmer.gemspec DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/shimmer/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "shimmer"
7
- spec.version = Shimmer::VERSION
8
- spec.authors = ["Jens Ravens"]
9
- spec.email = ["jens@nerdgeschoss.de"]
10
-
11
- spec.summary = "Shimmer brings all the bells and whistles of a hotwired application, right from the start."
12
- spec.homepage = "https://github.com/nerdgeschoss/shimmer"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = ">= 2.6.0"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = spec.homepage
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject do |f|
23
- (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
- end
25
- end
26
- spec.bindir = "exe"
27
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
- spec.require_paths = ["lib"]
29
-
30
- # Uncomment to register a new dependency of your gem
31
- # spec.add_dependency "example-gem", "~> 1.0"
32
-
33
- # For more information and examples about making a new gem, checkout our
34
- # guide at: https://bundler.io/guides/creating_gem.html
35
- end