mini_tree 0.0.4

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cfcc12efe875d58a64a005b8bcf01c4287521f929dafe941e55abfe0774bd64e
4
+ data.tar.gz: 6314c4eecfbe0c7cf0df0b2db8e952515bb3bba14158c94aeb250eb89f5763e4
5
+ SHA512:
6
+ metadata.gz: 264d652696e5c73cfda4b8b6e7094e9b8596d9ba30bf71be3361712845a41929fab44017fda6d6b9834b7c8eecf73ab3c5ca74469def6cb35119dcd1698a21d5
7
+ data.tar.gz: f3139e7a2cd685933b07341c2503aa3ba553c0279cfd6e52d41fed823f1d3eb7e8a0351f83bd7a29c6aa40280e2494133e6f74a9fab2c9aa0c77b03c01b71b0f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2025 Dittmar Krall (www.matiq.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # UNDER CONSTRUCTION
2
+
3
+ # MiniTree
4
+
5
+ [![Gem Version](https://img.shields.io/gem/v/mini_tree?color=168AFE&logo=rubygems&logoColor=FE1616)](https://rubygems.org/gems/mini_tree)
6
+ [![Downloads](https://img.shields.io/gem/dt/mini_tree?color=168AFE&logo=rubygems&logoColor=FE1616)](https://rubygems.org/gems/mini_tree)
7
+ [![GitHub Build](https://img.shields.io/github/actions/workflow/status/matique/mini_tree/rake.yml?logo=github)](https://github.com/matique/mini_tree/actions/workflows/rake.yml)
8
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-168AFE.svg)](https://github.com/standardrb/standard)
9
+ [![MIT License](https://img.shields.io/badge/license-MIT-168AFE.svg)](http://choosealicense.com/licenses/mit/)
10
+
11
+ _MiniTree_ is a Rails gem to display and handle a treeview.
12
+ It supports moving/reordering items in the treeview,
13
+ collapsing/expanding of a subtree
14
+ and creating/deleting them.
15
+
16
+ Items in the treeview can be enhanced with links
17
+ to trigger actions.
18
+
19
+ _MiniTree_ requires just a basic Rails system.
20
+ Specifically, besides Stimulus no other Javascript package
21
+ (i.e jQuery) is expected.
22
+ Configuration is absent.
23
+
24
+ _MiniTree_ includes a javascript component to handle
25
+ the view on the client side
26
+ as well as code for the server side.
27
+
28
+
29
+ ## Prerequisites
30
+
31
+ Some preparation for the usage is required:
32
+ - an additional database table storing the tree structure
33
+ - a _legend_ method in the model
34
+ - a call to _MiniTree_ to initialize the tree structure
35
+ - calls in the model to _miniTree_ during creation, update
36
+ and deletion of an item
37
+
38
+ ~~~Ruby
39
+ # ./app/models/<model>.rb
40
+ class <model> < ApplicationRecord
41
+ def legend = "#{name} #{id}" # an example
42
+ end
43
+
44
+ # ./app/models/<model>_tree.rb
45
+ class <model>Tree < ApplicationRecord
46
+ include MiniTree::Utils
47
+ end
48
+
49
+ # ./db/migrate/<nnn>_create_<model>_trees.rb
50
+ class Create<Model>Trees < ActiveRecord::Migration[8.0]
51
+ def change
52
+ create_table :<model>_trees do |t|
53
+ t.string :legend
54
+ t.integer :parent_id, index: true
55
+ t.integer :position, null: false, default: 0
56
+ t.boolean :collapsed, default: false
57
+ t.string :kind
58
+
59
+ t.timestamps
60
+ end
61
+ # add_foreign_key :items, :items, column: :parent_id
62
+ end
63
+ end
64
+ ~~~
65
+
66
+ You may specify your view of an item in the treeview:
67
+ ~~~Ruby
68
+ # ./app/views/mini_trees/_mini_tree_title.html.erb
69
+ # id and legend are defined
70
+ <%= link_to "action", edit_<model>(id:), class: 'button' %>
71
+ <%= legend %>
72
+ ~~~
73
+
74
+ ## Refresh
75
+
76
+ ~~~Ruby
77
+ <Model>Tree.refresh
78
+ <Model>Tree.refresh_item(<id>, <legend>)
79
+ <Model>Tree.create_item(<id>, <legend>)
80
+ <Model>Tree.del_item(<id>)
81
+ ~~~
82
+
83
+ ## Usage
84
+
85
+ ~~~Ruby
86
+ # Example
87
+ <% list = <Model>Tree.all %>
88
+ <%= render "mini_trees/index", locals: {list:} %>
89
+ ~~~
90
+
91
+
92
+ ## Installation
93
+
94
+ As usual:
95
+
96
+ ~~~~ruby
97
+ # Gemfile
98
+ ...
99
+ gem "mini_tree"
100
+ ...
101
+ ~~~~
102
+
103
+ and run "bundle install".
104
+
105
+ Furthermore, copy manually *app/javascript/controllers/tree_controller.js*
106
+ from the _gem mini-tree_
107
+ into your own _app/javascript/controllers/_ directory.
108
+
109
+
110
+ ## System dependencies
111
+
112
+ This software has been developed and tested with:
113
+ - Ubuntu 24.04
114
+ - Ruby 3.4.7
115
+ - Rails 8.1.0
116
+
117
+ See also:
118
+ - ./.github/workflows/rake.yml
119
+
120
+ No particular system dependency is known,
121
+ i.e. _mini_tree_ is expected to run on other systems without trouble.
122
+
123
+
124
+ ## Curious
125
+
126
+ There are quite a lot of TreeViews available.
127
+ If you are curious you may search in particular for:
128
+ - jqTree
129
+ - sortableJS
130
+
131
+
132
+ ## License
133
+
134
+ Copyright (c) 2025 Dittmar Krall (www.matiq.com),
135
+ released as open source under the terms of the
136
+ [MIT license](https://opensource.org/licenses/MIT).
@@ -0,0 +1,40 @@
1
+ class MiniTreesController < ApplicationController
2
+ def sync
3
+ init_klasses(params[:owner])
4
+ id = params[:id]
5
+ value = params[:value]
6
+
7
+ case params[:function]
8
+ when "order"
9
+ client_order = JSON.parse(params[:order])
10
+ client_order.each_with_index do |pair, index|
11
+ id, parent_id = pair
12
+ row = @tree.find(id.to_i)
13
+ row.update!(position: index, parent_id: parent_id.to_i)
14
+ end
15
+ when "leaf2node"
16
+ row = @tree.find_by(id: value.to_i)
17
+ row.update! kind: "node"
18
+ when "node2leaf"
19
+ row = @tree.find_by(id: value.to_i)
20
+ row.update! kind: "leaf"
21
+ when "toggle"
22
+ id = id.to_i
23
+ row = @tree.find_by(id:)
24
+ row.update! collapsed: value
25
+ else
26
+ raise "MiniTreeView: unknown function <#{params}>"
27
+ end
28
+ # head :ok
29
+ render json: {status: "ok"}
30
+ rescue RuntimeError => e
31
+ # head :bad_request
32
+ render json: {status: "error", message: e.message}, status: :unprocessable_entity
33
+ end
34
+
35
+ private
36
+
37
+ def init_klasses(name)
38
+ @tree = Kernel.const_get("#{name}Tree")
39
+ end
40
+ end
@@ -0,0 +1,252 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["item", "top"]
5
+ static values = {owner: String}
6
+
7
+ connect() {
8
+ this.expanded = "\u25BE" // down triangle
9
+ this.collapsed = "\u25b8" // right triangle
10
+ this.itemTargets.forEach(item => this.attach(item))
11
+ }
12
+
13
+ attach(item) {
14
+ item.addEventListener("dragstart", e => this.dragStart(e, item))
15
+ item.addEventListener("dragend", e => this.dragEnd(e, item))
16
+ item.addEventListener("dragover", e => this.dragOver(e, item))
17
+ item.addEventListener("drop", e => this.drop(e, item))
18
+ }
19
+
20
+ dragStart(e, el) {
21
+ e.stopPropagation()
22
+
23
+ // ev.dataTransfer.setData("text/plain", "")
24
+ this.dragged = el
25
+ e.dataTransfer.effectAllowed = "move"
26
+ el.classList.add("dragging")
27
+ }
28
+
29
+ dragEnd(e) {
30
+ e.stopPropagation()
31
+ this.dragged.classList.remove("dragging")
32
+ this.dragged = null
33
+ }
34
+
35
+ dragOver(e, el) {
36
+ e.preventDefault()
37
+ }
38
+
39
+ drop(e) {
40
+ e.stopPropagation()
41
+
42
+ const dragged = this.dragged
43
+ const target = e.currentTarget
44
+ // this.testUtilities(target)
45
+
46
+ if (dragged == target) {
47
+ this.doGrouping(target)
48
+ } else {
49
+ this.drop2(e, dragged, target)
50
+ this.sendOrder(target)
51
+ }
52
+ }
53
+
54
+ drop2(e, dragged, target) {
55
+ const children = target.lastElementChild
56
+ if (this.isNode(target)) {
57
+ if (this.isNodeEmpty(target)) {
58
+ children.appendChild(dragged)
59
+ return
60
+ }
61
+ }
62
+
63
+ const rect = target.getBoundingClientRect()
64
+ const offset = e.clientY - rect.top
65
+ const threshold = (rect.height / 3) * 2
66
+
67
+ const target2 = (offset > threshold) ? target.nextSibling : target
68
+ target.parentNode.insertBefore(dragged, target2)
69
+ }
70
+
71
+ doGrouping(elem) {
72
+ if (this.isNode(elem)) {
73
+ if (!this.isNodeEmpty(elem)) {
74
+ throw "MiniTreeView: Node has children; can't be converted to leaf"
75
+ }
76
+
77
+ this.toLeaf(elem)
78
+ } else {
79
+ this.toNode(elem)
80
+ }
81
+ }
82
+
83
+ toLeaf(elem) {
84
+ var row = elem.firstElementChild
85
+ const newRow = this.create("span", {className: "toggle-space"})
86
+
87
+ this.removeUl(elem)
88
+ row.removeChild(row.firstElementChild)
89
+ row.insertBefore(newRow, row.children[0])
90
+
91
+ this.sync({owner: this.ownerValue, function: "node2leaf",
92
+ value: elem.dataset.itemId})
93
+ }
94
+
95
+ toNode(elem) {
96
+ var row = elem.firstElementChild
97
+ const newRow = this.create("button", {className: "toggle-btn",
98
+ type: "button", textContent: this.expanded})
99
+
100
+ this.removeUl(elem)
101
+ const ulRow2 = this.create("ul", {className: "nested"})
102
+ elem.insertBefore(ulRow2, elem.children[1])
103
+ row.removeChild(row.firstElementChild)
104
+ row.insertBefore(newRow, row.children[0])
105
+
106
+ this.sync({owner: this.ownerValue, function: "leaf2node",
107
+ value: elem.dataset.itemId})
108
+ }
109
+
110
+ removeUl(elem) {
111
+ const ulRow = elem.children[1]
112
+ if (ulRow) elem.removeChild(ulRow)
113
+ }
114
+
115
+ toggle(e) {
116
+ const li = e.currentTarget.closest(".tree-item")
117
+ const id = li.dataset.itemId
118
+ const list = li.querySelector(".nested")
119
+ if (!list) return
120
+
121
+ const btn = li.querySelector(".toggle-btn")
122
+ const hide = list.classList.toggle("hidden")
123
+ if (btn) btn.textContent = hide ? this.collapsed : this.expanded
124
+ this.sync({owner: this.ownerValue, function: "toggle",
125
+ id: id, value: hide})
126
+ }
127
+
128
+ ///////////////////////// Utilities ////////////////////////////////
129
+
130
+ testUtilities(el) {
131
+ console.log("***** testUtilities *****", el)
132
+ console.log("isLeaf", this.isLeaf(el))
133
+ console.log("isNode", this.isNode(el))
134
+ console.log("isNodeEmpty", this.isNodeEmpty(el))
135
+ console.log("parent", this.parent(el))
136
+ console.log("parent_id", this.parent_id(el))
137
+ }
138
+
139
+ create(tag, attrs) {
140
+ var elem = document.createElement(tag)
141
+ Object.assign(elem, attrs)
142
+ return elem
143
+ }
144
+
145
+ isLeaf(elem) {
146
+ const el = elem.firstElementChild.firstElementChild
147
+ return el.tagName == "SPAN"
148
+ }
149
+
150
+ isNode(elem) {
151
+ return !this.isLeaf(elem)
152
+ }
153
+
154
+ isNodeEmpty(elem) {
155
+ const el = elem.lastElementChild
156
+ if (el.childElementCount == 0) return true
157
+ return false
158
+ }
159
+
160
+ parent(elem) {
161
+ return elem.parentElement.closest("li")
162
+ }
163
+
164
+ parent_id(elem) {
165
+ elem = this.parent(elem)
166
+ if (!elem) return null
167
+ return elem.dataset.itemId
168
+ }
169
+
170
+ ///////////////////////// AJAX ////////////////////////////////
171
+
172
+ flatted = (elem) => {
173
+ const result = []
174
+ var id = elem.dataset.itemId
175
+ var parent_id = this.parent_id(elem.parentElement)
176
+
177
+ if (id && id != 0) { result.push([id, parent_id]) }
178
+ for (var elem2 of elem.children) {
179
+ result.push(...this.flatted(elem2))
180
+ }
181
+ return result
182
+ }
183
+
184
+ sendOrder(el) {
185
+ const flat = this.flatted(this.topTarget.parentElement)
186
+ this.sync({owner: this.ownerValue, function: "order",
187
+ order: JSON.stringify(flat)})
188
+ }
189
+
190
+ async sync(params) {
191
+ const url = "/mini_trees/sync"
192
+ const token = document.querySelector("meta[name=csrf-token]").content
193
+ try {
194
+ const response = await fetch(url, {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ "X-CSRF-Token": token
199
+ },
200
+ body: JSON.stringify(params)
201
+ })
202
+
203
+ if (!response.ok) {
204
+ throw new Error(`Response status: ${response.status}`);
205
+ }
206
+
207
+ // const result = await response.json();
208
+ // console.log(result);
209
+ } catch (error) {
210
+ console.error(error.message);
211
+ }
212
+ }
213
+ }
214
+
215
+
216
+
217
+ // over(e, el) {
218
+ //console.log("over")
219
+ // e.preventDefault()
220
+ // if (this.dragged === el) return
221
+ // const rect = el.getBoundingClientRect()
222
+ // const offset = e.clientY - rect.top
223
+ // const third = rect.height / 3
224
+ //
225
+ // if (offset < third) {
226
+ // el.parentNode.insertBefore(this.placeholder, el)
227
+ // } else if (offset > 2 * third) {
228
+ // el.parentNode.insertBefore(this.placeholder, el.nextSibling)
229
+ // } else {
230
+ // let ul = el.querySelector(".nested")
231
+ // if (!ul) {
232
+ // ul = document.createElement("ul")
233
+ // ul.className = "nested"
234
+ // el.appendChild(ul)
235
+ // }
236
+ // ul.classList.remove("hidden")
237
+ // ul.appendChild(this.placeholder)
238
+ // }
239
+ // }
240
+ //
241
+ // removePlaceholder() {
242
+ // this.placeholder?.remove()
243
+ // }
244
+ //
245
+ //
246
+ // // rails javascript no jquery no sortablejs use stimulus sortable treeview
247
+ // // expand collapse support
248
+ // // nested reordering
249
+ //
250
+ //
251
+ // // https://wiki.selfhtml.org/wiki/JavaScript/Drag_%26_Drop
252
+ // // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
@@ -0,0 +1 @@
1
+ <%= legend %>
@@ -0,0 +1,34 @@
1
+ <style>
2
+ .mini-tree {
3
+ xbackground: yellow;
4
+ .mini-tree-view, .nested {
5
+ list-style: none;
6
+ padding-left: 1rem;
7
+ }
8
+ .tree-row {
9
+ display: flex;
10
+ gap: 0.5rem;
11
+ }
12
+ .toggle-btn {
13
+ background: transparent;
14
+ border: none;
15
+ }
16
+ .dragging { opacity: 0.5; }
17
+ .hidden { display: none; }
18
+ .toggle-btn, .toggle-space { width: 1rem; }
19
+ .tree-row:hover { background: rgba(0,0,0,0.05); }
20
+ }
21
+ </style>
22
+
23
+ <%
24
+ list = locals[:list]
25
+ owner = list.first.class.name[0..-5]
26
+ %>
27
+ <div class="mini-tree" data-item-id="0"
28
+ data-controller="tree" data-tree-owner-value="<%= owner %>">
29
+ <ul class="mini-tree-view" data-tree-target="top">
30
+ <%= render partial: "mini_trees/item",
31
+ collection: list.where(parent_id: 0).order(:position)
32
+ %>
33
+ </ul>
34
+ </div>
@@ -0,0 +1,27 @@
1
+ <%
2
+ icon = (item.collapsed ? "\u25b8" : "\u25be").html_safe
3
+ klass = item.collapsed ? "nested hidden" : "nested"
4
+ %>
5
+
6
+ <li class="tree-item" data-tree-target="item"
7
+ data-item-id="<%= item.id %>" draggable="true">
8
+ <div class="tree-row" data-action="click-&gt;tree#toggle">
9
+ <% if item.kind == "node" %>
10
+ <button class="toggle-btn" type="button"><%= icon %></button>
11
+ <% else %>
12
+ <span class="toggle-space"></span>
13
+ <% end %>
14
+ <span class="title">
15
+ <%= render partial: "mini_tree_title",
16
+ locals: {id: item.id, legend: item.legend}
17
+ %>
18
+ </span>
19
+ </div>
20
+
21
+ <% if item.kind == "node" %>
22
+ <ul class="<%= klass %>">
23
+ <%= render partial: "mini_trees/item", collection: item.children %>
24
+ </ul>
25
+ <% end %>
26
+
27
+ </li>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ post "mini_trees/sync", as: :sync
3
+ end
@@ -0,0 +1,4 @@
1
+ module MiniTree
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,69 @@
1
+ module MiniTree::Utils
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ def children
7
+ self.class.where(parent_id: id).order(:position)
8
+ end
9
+
10
+ def to_s
11
+ "position <#{position}> parent_id <#{parent_id}> #{kind} \"#{legend}\""
12
+ end
13
+
14
+ module ClassMethods
15
+ def refresh
16
+ missing_cnt = delete_cnt = refresh_cnt = 0
17
+ owner_class = Kernel.const_get(name[0..-5])
18
+ owner_ids = owner_class.all.pluck(:id)
19
+ ids = all.pluck(:id)
20
+
21
+ (owner_ids - ids).each { |id|
22
+ legend = owner_class.find_by(id:).legend
23
+ create_item(id, legend)
24
+ missing_cnt += 1
25
+ }
26
+ (ids - owner_ids).each { |id|
27
+ del_item(id)
28
+ delete_cnt += 1
29
+ }
30
+ (owner_ids & ids).each { |id|
31
+ refresh_item(id, owner_class.find_by(id:).legend)
32
+ refresh_cnt += 1
33
+ }
34
+ [missing_cnt, delete_cnt, refresh_cnt]
35
+ end
36
+
37
+ def refresh_item(id, legend)
38
+ find_by(id:).update!(legend:)
39
+ end
40
+
41
+ def create_item(id, legend)
42
+ create! id:, legend:, parent_id: 0, position: id, kind: "leaf"
43
+ end
44
+
45
+ def del_item(id)
46
+ where(id:).delete_all
47
+ end
48
+
49
+ # used by tests
50
+ def flat
51
+ res = []
52
+ sorted.each { |item|
53
+ res << [item.id, item.parent_id]
54
+ }
55
+ res
56
+ end
57
+
58
+ def print
59
+ puts "*** #{name}.all ***"
60
+ sorted.each_with_index { |item, index|
61
+ puts "#{index}: #{item}"
62
+ }
63
+ end
64
+
65
+ def sorted
66
+ all.order(:position)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,6 @@
1
+ module MiniTree
2
+ VERSION = "0.0.4" # 2025-10-29
3
+ # VERSION = "0.0.3" # 2025-10-28
4
+ # VERSION = "0.0.2" # 2025-10-27
5
+ # VERSION = "0.0.1" # 2025-10-26
6
+ end
@@ -0,0 +1,5 @@
1
+ module MiniTree
2
+ VERSION = "0.0.3" # 2025-10-28
3
+ # VERSION = "0.0.2" # 2025-10-27
4
+ # VERSION = "0.0.1" # 2025-10-26
5
+ end
data/lib/mini_tree.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "mini_tree/engine"
2
+ require "mini_tree/utils"
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Dittmar Krall
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.0.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: stimulus-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: combustion
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: minitest-spec-rails
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ description: |2
83
+ A simple TreeView Rails 8+ gem based on Stimulus
84
+ (no jQuery is required).
85
+ A server side handling of the tree is included.
86
+ The client side display of the usual legend can be
87
+ adapted to the user's requirements to include links.
88
+ email:
89
+ - dittmar.krall@gmail.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files:
93
+ - MIT-LICENSE
94
+ - README.md
95
+ files:
96
+ - MIT-LICENSE
97
+ - README.md
98
+ - app/controllers/mini_trees_controller.rb
99
+ - app/javascript/controllers/tree_controller.js
100
+ - app/views/application/_mini_tree_title.html.erb
101
+ - app/views/mini_trees/_index.html.erb
102
+ - app/views/mini_trees/_item.html.erb
103
+ - config/routes.rb
104
+ - lib/mini_tree.rb
105
+ - lib/mini_tree/engine.rb
106
+ - lib/mini_tree/utils.rb
107
+ - lib/mini_tree/version.rb
108
+ - lib/mini_tree/version.rb.bak
109
+ homepage: https://github.com/matique/mini_tree
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: '3'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.6.9
128
+ specification_version: 4
129
+ summary: MiniTree a simple TreeView Rails 8+ gem.
130
+ test_files: []