shimmer 0.0.4 → 0.0.5

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 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