keystone_ui 0.33.0 → 0.34.0

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: 599c6b4269b4ca049a5aefe106cb71dc6205554c49fb6e72824f25cc3b6f2597
4
- data.tar.gz: 390d82ea8082927248351ba74796fde3a05afe4994b151c79169e0cb190ecacd
3
+ metadata.gz: ac460729f806dac1343306018aab4560b2c75f39ad05ec5a775886c24f5084fb
4
+ data.tar.gz: 89e7107f63d2d0b6f4b2b27907c464b09cc44375d12fc59e7958bae8e99efbb7
5
5
  SHA512:
6
- metadata.gz: 2b44fd40afcc3d275dab96ab0401f2748a5555d5e38eb319af16d998454e30f094d9e799603658b0597b7a0e8688a9a8d864aa2e170c3b750d5351a37db123d5
7
- data.tar.gz: 65064fb15715bcebbe95efadb89b2a395d06889b6d4962467faeac520df18f88083acd7f5d23ffe8c6057f9434916906861a4c3f02515ae4306cf545bf9eba23
6
+ metadata.gz: '097bdb96a57af1b3b1f886b522fa4bd3a5be40f89b433be36df0c5c74ae895ba4c5dda568a6bbea8c5e32743ee6d072cd61ba553ee9d381fc90c8917399b768f'
7
+ data.tar.gz: e0b238491fc14b93e2cb32e67871290bb8a5a0d606f3663c3c839a62964ce2c8a954d1f6242a158da01713eb7e1b3565f2b3371b46c7e54607cc9e4aae77967a
@@ -1,7 +1,7 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["menu"]
4
+ static targets = ["menu", "option"]
5
5
  static values = { saveUrl: String }
6
6
 
7
7
  connect() {
@@ -25,23 +25,53 @@ export default class extends Controller {
25
25
  }
26
26
 
27
27
  save() {
28
- const allCheckboxes = this.element.querySelectorAll("input[type=checkbox]")
29
- const hiddenColumns = Array.from(allCheckboxes)
30
- .filter(cb => !cb.checked)
31
- .map(cb => cb.value)
32
-
33
- if (this.hasSaveUrlValue) {
34
- const token = document.querySelector('meta[name="csrf-token"]')?.content
35
- fetch(this.saveUrlValue, {
36
- method: "PATCH",
37
- headers: {
38
- "Content-Type": "application/json",
39
- "X-CSRF-Token": token
40
- },
41
- body: JSON.stringify({ hidden_columns: hiddenColumns })
42
- }).then(() => {
43
- Turbo.visit(window.location.href, { action: "replace" })
44
- })
45
- }
28
+ this.send(this.columnOrder())
29
+ }
30
+
31
+ moveUp(event) {
32
+ this.move(event, -1)
33
+ }
34
+
35
+ moveDown(event) {
36
+ this.move(event, 1)
37
+ }
38
+
39
+ move(event, step) {
40
+ const order = this.columnOrder()
41
+ const index = this.optionTargets.indexOf(event.currentTarget.closest('[data-column-picker-target="option"]'))
42
+ const [key] = order.splice(index, 1)
43
+ order.splice(index + step, 0, key)
44
+ this.send(order)
45
+ }
46
+
47
+ columnOrder() {
48
+ return this.optionTargets.map(option => this.checkboxIn(option).value)
49
+ }
50
+
51
+ hiddenColumns() {
52
+ return this.optionTargets
53
+ .map(option => this.checkboxIn(option))
54
+ .filter(checkbox => !checkbox.checked)
55
+ .map(checkbox => checkbox.value)
56
+ }
57
+
58
+ checkboxIn(option) {
59
+ return option.querySelector("input[type=checkbox]")
60
+ }
61
+
62
+ send(columnOrder) {
63
+ if (!this.hasSaveUrlValue) return
64
+
65
+ const token = document.querySelector('meta[name="csrf-token"]')?.content
66
+ fetch(this.saveUrlValue, {
67
+ method: "PATCH",
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ "X-CSRF-Token": token
71
+ },
72
+ body: JSON.stringify({ hidden_columns: this.hiddenColumns(), column_order: columnOrder })
73
+ }).then(() => {
74
+ Turbo.visit(window.location.href, { action: "replace" })
75
+ })
46
76
  }
47
77
  }
@@ -6,15 +6,27 @@
6
6
  Columns
7
7
  </button>
8
8
  <div data-column-picker-target="menu" class="<%= MENU_CLASSES %>">
9
- <% hideable_columns.each do |col| %>
10
- <label class="<%= OPTION_CLASSES %>">
11
- <input type="checkbox"
12
- value="<%= col.key %>"
13
- <%= "checked" unless hidden?(col.key) %>
14
- data-action="change->column-picker#save"
15
- class="<%= CHECKBOX_CLASSES %>">
16
- <%= col.header_text %>
17
- </label>
9
+ <% hideable_columns.each_with_index do |col, index| %>
10
+ <div class="<%= OPTION_ROW_CLASSES %>" data-column-picker-target="option">
11
+ <label class="<%= OPTION_CLASSES %>">
12
+ <input type="checkbox"
13
+ value="<%= col.key %>"
14
+ <%= "checked" unless hidden?(col.key) %>
15
+ data-action="change->column-picker#save"
16
+ class="<%= CHECKBOX_CLASSES %>">
17
+ <%= col.header_text %>
18
+ </label>
19
+ <button type="button"
20
+ data-action="click->column-picker#moveUp"
21
+ aria-label="Move up"
22
+ <%= "disabled" if index.zero? %>
23
+ class="<%= MOVE_BUTTON_CLASSES %>">&uarr;</button>
24
+ <button type="button"
25
+ data-action="click->column-picker#moveDown"
26
+ aria-label="Move down"
27
+ <%= "disabled" if index == hideable_columns.size - 1 %>
28
+ class="<%= MOVE_BUTTON_CLASSES %>">&darr;</button>
29
+ </div>
18
30
  <% end %>
19
31
  </div>
20
32
  </div>
@@ -8,6 +8,8 @@ module Keystone
8
8
  MENU_CLASSES = "ks-menu absolute right-0 z-10 w-56 hidden"
9
9
  OPTION_CLASSES = "ks-menu-option flex items-center text-sm cursor-pointer"
10
10
  CHECKBOX_CLASSES = "ks-menu-checkbox"
11
+ OPTION_ROW_CLASSES = "flex items-center"
12
+ MOVE_BUTTON_CLASSES = "ks-menu-move text-sm"
11
13
 
12
14
  COLUMNS_ICON = <<~SVG.freeze
13
15
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
@@ -146,11 +146,20 @@ module Keystone
146
146
  saved = KeystoneUi.configuration.supplied_preference(helpers, @key)
147
147
  return unless saved
148
148
 
149
- @hidden_columns = saved[:value]["hidden_columns"]
149
+ @hidden_columns = saved[:value].to_h.fetch("hidden_columns", @hidden_columns)
150
+ @all_columns = ordered_columns(saved[:value].to_h["column_order"])
150
151
  @columns = visible_columns(@hidden_columns)
151
152
  @save_url = saved[:save_url]
152
153
  end
153
154
 
155
+ def ordered_columns(column_order)
156
+ return @all_columns unless column_order
157
+
158
+ positions = Array(column_order).map(&:to_sym).each_with_index.to_h
159
+ hideable = @all_columns.select(&:hideable?).sort_by.with_index { |col, index| [ positions.fetch(col.key, positions.size + index) ] }
160
+ @all_columns.map { |col| col.hideable? ? hideable.shift : col }
161
+ end
162
+
154
163
  def visible_columns(hidden_columns)
155
164
  hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
156
165
  @all_columns.reject { |col| col.hideable? && hidden_keys.include?(col.key) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.33.0"
4
+ VERSION = "0.34.0"
5
5
  end
@@ -224,21 +224,39 @@ outer element. See Conventions before using it.
224
224
  server-side and only affects columns declared `hideable: true`. `key:` (a
225
225
  symbol or string) names the table so it looks up a saved layout through the
226
226
  app's preference supplier. When the supplier returns a saved value for the
227
- key, the hideable columns listed under its `"hidden_columns"` replace the
228
- ones passed in `hidden_columns:`, and when it also returns a save address the
229
- table renders a "Columns" menu above itself that saves to it. When the
227
+ key that lists `"hidden_columns"`, the hideable columns in that list replace
228
+ the ones passed in `hidden_columns:`, and a saved empty list shows every
229
+ column. When the saved value is `nil` or lists no `"hidden_columns"`, the
230
+ table keeps the columns `hidden_columns:` hides. When the saved value lists
231
+ `"column_order"`, an array of column keys, the hideable columns render in
232
+ that order, followed by any hideable columns the list leaves out in the order
233
+ they were declared. Columns that are not hideable keep their declared place,
234
+ and the hideable ones fill the remaining places. With no `"column_order"` the
235
+ columns keep their declared order. Whenever the supplier
236
+ returns a save address, the table renders a "Columns" menu above itself that
237
+ saves to it, including for a person with nothing saved yet. When the
230
238
  supplier returns nothing, when no supplier is set, or when no `key:` is
231
- passed, the table renders from `hidden_columns:` with no Columns menu, so
232
- `hidden_columns:` is the table's default layout.
239
+ passed, the table renders from `hidden_columns:` with no Columns menu. In
240
+ every case `hidden_columns:` is the table's default layout.
233
241
  - `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false)`
234
242
  — a column with per-column options, for when a `{ key: "Label" }` hash is not
235
243
  enough. `mobile_hidden:` hides the column below `sm:`; `sortable:` opts it into
236
- sort headers; `hideable:` lets the column picker hide it.
244
+ sort headers; `hideable:` lets the Columns menu hide it and move it, and a
245
+ saved `"column_order"` place it.
237
246
  - `ui_column_picker(columns:, hidden_columns: [], save_url: nil)` — a "Columns"
238
- dropdown of checkboxes for every `hideable` column. Pass it the same columns
239
- and hidden keys as the table. On toggle it sends `PATCH save_url` with JSON
240
- `{ "hidden_columns": ["key", ...] }` and a `X-CSRF-Token` header, then reloads
241
- the page. The app must provide that endpoint and persist the list. A table
247
+ dropdown with one row per `hideable` column, in the order `columns:` lists
248
+ them. Each row has a checkbox and an up and a down button that move the
249
+ column one place; the first row's up button and the last row's down button
250
+ are disabled. Pass it the same columns, in the order the table shows them,
251
+ and the same hidden keys as the table. On a checkbox change or a move it
252
+ sends `PATCH save_url` with JSON
253
+ `{ "hidden_columns": ["key", ...], "column_order": ["key", ...] }` and a
254
+ `X-CSRF-Token` header, then reloads the page. `column_order` lists every
255
+ hideable column's key in the menu's order after the move. With no
256
+ `save_url:` it sends nothing. The app must provide that endpoint and persist
257
+ both lists. The picker does not reorder the table: beside a table without
258
+ `key:`, the app must pass the table and the picker its columns in the saved
259
+ order itself. A table
242
260
  given `key:` renders its own Columns menu when the supplier gives a save
243
261
  address, so never add `ui_column_picker` beside such a table.
244
262
 
@@ -515,9 +533,11 @@ outer element. See Conventions before using it.
515
533
  For a table whose hideable columns a user should be able to choose and keep,
516
534
  check whether the app's Keystone UI initializer sets a
517
535
  `preference_supplier`. If it does, pass `key:` and the default
518
- `hidden_columns:`, and add no `ui_column_picker`. Which key names the table
519
- and which columns it hides by default are the developer's choice, so ask
520
- rather than pick. If the app sets no supplier, either use `ui_column_picker`
536
+ `hidden_columns:`, and add no `ui_column_picker`. The order the columns are
537
+ declared in is the default order, and only columns declared
538
+ `hideable: true` can be hidden or moved from the Columns menu. Which key
539
+ names the table, which columns are hideable, and which it hides by default
540
+ are the developer's choice, so ask rather than pick. If the app sets no supplier, either use `ui_column_picker`
521
541
  with an endpoint the app owns, as in step 7, or hand setting up a supplier to
522
542
  `keystone_ui-install`, and ask the developer which.
523
543
 
@@ -127,11 +127,21 @@ holds the catalog.
127
127
  - **Saved table layouts.** A data table can be given a key naming it. A table
128
128
  with a key asks the app, or another gem such as keystone_ui-preferences, for
129
129
  the layout saved under that key for the request. A saved layout lists the
130
- columns to hide, and only columns marked hideable can be hidden. When a
131
- layout is saved, it replaces the columns the table hides by default. When
132
- nothing is saved, the table hides the columns its own call names, so those
133
- are its default layout. When the supplier also gives an address to save to,
134
- a "Columns" menu appears above the table and saves the user's choice there.
130
+ columns to hide, and only columns marked hideable can be hidden. The columns
131
+ the table's own call hides are its default layout. A saved layout that lists
132
+ columns to hide replaces that default, and a saved empty list shows every
133
+ column. When nothing is saved, or the saved layout lists no columns to hide,
134
+ the table keeps its default. A saved layout can also list an order for the
135
+ columns. Only hideable columns move: they fill the places hideable columns
136
+ held in the table's own call, in the saved order, and any the order leaves
137
+ out follow the ones it names. Every other column keeps its place, and a
138
+ layout with no order keeps the order the columns were declared in. When the
139
+ supplier gives an address to save to, a "Columns" menu appears above the
140
+ table and saves the user's choice there, even before anything has been
141
+ saved. The menu lists the hideable columns in the order the table shows
142
+ them, each with a box to show or hide it and buttons to move it up or down.
143
+ Ticking a box or moving a column saves the hidden columns and the order
144
+ together, then reloads the page.
135
145
  A table with no key, or an app with no supplier, renders from its own call
136
146
  alone. Setting up the supplier belongs to the install local.
137
147
  - **Suggestions.** A form field can carry a list of suggested values. The
@@ -26,8 +26,9 @@ built on ViewComponent; hook it in before building any screen with those helpers
26
26
  `look_supplier` (which registered look a page gets),
27
27
  `trail_supplier` (a callable that supplies the breadcrumb trail for form and
28
28
  show pages that pass none, and their Back link when they pass none either),
29
- `preference_supplier` (a callable that supplies the saved column layout of a
30
- data table given a `key:`, and the address its Columns menu saves to),
29
+ `preference_supplier` (a callable that supplies the saved hidden columns and
30
+ column order of a data table given a `key:`, and the address its Columns menu
31
+ saves to),
31
32
  and the `tailwind_imports` and `tailwind_sources` lists (extra CSS files and
32
33
  scan paths added to the Tailwind build).
33
34
  - `keystone_theme_attributes` — a layout helper placed inside the `<html>` tag.
@@ -233,19 +234,40 @@ built on ViewComponent; hook it in before building any screen with those helpers
233
234
  where their Back link should come from before enabling the supplier or
234
235
  leaving it out.
235
236
  - `preference_supplier` — a callable that receives the view and a data
236
- table's key and returns `{ value:, save_url: }`, or `nil` when nothing is
237
- saved for that key. It is asked only for tables rendered with a `key:`. A
238
- table with no `key:`, a supplier returning `nil`, or no supplier at all
239
- renders with the hidden columns its own call passes.
240
- - `value` must hold the hidden column names under the string key
241
- `"hidden_columns"`, such as `{ "hidden_columns" => ["sku"] }`. A symbol key
242
- is not read, and the table then hides no columns. Only columns marked
243
- hideable are hidden.
237
+ table's key and returns `{ value:, save_url: }`, or `nil` when the person
238
+ can neither see nor save a layout for that key. It is asked only for
239
+ tables rendered with a `key:`. A table with no `key:`, a supplier returning
240
+ `nil`, or no supplier at all renders with the hidden columns its own call
241
+ passes and shows no Columns menu.
242
+ - `value` holds the hidden column names under the string key
243
+ `"hidden_columns"`, such as `{ "hidden_columns" => ["sku"] }`. That list
244
+ replaces the hidden columns the table's own call passes, and an empty list
245
+ shows every column. Only columns marked hideable are hidden.
246
+ - When `value` is `nil`, or has no `"hidden_columns"` string key, the table
247
+ keeps the hidden columns its own call passes. A symbol key is not read, so
248
+ `{ hidden_columns: [...] }` also keeps them.
249
+ - `value` may also hold a list of hideable column names under the string
250
+ key `"column_order"`, such as
251
+ `{ "hidden_columns" => ["sku"], "column_order" => ["price", "sku"] }`.
252
+ The table renders its hideable columns in that order, in the places
253
+ hideable columns hold in its own call, then any hideable columns the list
254
+ leaves out in the order they were declared. Columns that are not hideable
255
+ keep their place, and names that match no hideable column are ignored.
256
+ With no `"column_order"` string key the columns keep their declared order.
257
+ - When nothing is saved yet for a person who may save a layout, return
258
+ `{ value: nil, save_url: }`. The table then shows its own default layout
259
+ with the Columns menu, so the person can save a first layout.
244
260
  - When `save_url` is present, the table shows a Columns menu above itself.
245
- Toggling a column sends a `PATCH` to `save_url` with the JSON body
246
- `{"hidden_columns": [...]}` and the page's CSRF token, then reloads the
247
- page. The host must have a route and action at that address that store the
248
- list for that key. With no `save_url`, the saved layout applies and no menu
261
+ It lists the hideable columns in the order the table shows them, with the
262
+ currently hidden columns unchecked, and gives each column an up and a down
263
+ button. Ticking or unticking a column, or moving one, sends a `PATCH` to
264
+ `save_url` with the page's CSRF token and the JSON body
265
+ `{"hidden_columns": [...], "column_order": [...]}`, then reloads the page.
266
+ `column_order` lists every hideable column's name in the menu's order.
267
+ - The host must have a route and action at `save_url` that store both lists
268
+ for that key. The supplier must return them in `value` under the same
269
+ string keys, `"hidden_columns"` and `"column_order"`, or the saved order
270
+ is not applied. With no `save_url`, the saved layout applies and no menu
249
271
  is shown.
250
272
  - A companion preferences gem may set this supplier for the app. Ask the
251
273
  developer whether the app uses one, or which code stores each user's table
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.33.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider