@contributte/datagrid 0.0.0-20250531-d4580b9

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.
Files changed (39) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +84 -0
  3. package/assets/ajax/index.ts +1 -0
  4. package/assets/ajax/naja.ts +154 -0
  5. package/assets/css/datagrid-all.css +6 -0
  6. package/assets/css/datagrid-full.css +7 -0
  7. package/assets/css/datagrid.css +651 -0
  8. package/assets/css/happy.css +463 -0
  9. package/assets/datagrid-all.ts +1 -0
  10. package/assets/datagrid-full.ts +53 -0
  11. package/assets/datagrid.ts +228 -0
  12. package/assets/index.ts +4 -0
  13. package/assets/integrations/happy.ts +217 -0
  14. package/assets/integrations/index.ts +4 -0
  15. package/assets/integrations/sortable-js.ts +58 -0
  16. package/assets/integrations/tom-select.ts +30 -0
  17. package/assets/integrations/vanilla-datepicker.ts +25 -0
  18. package/assets/plugins/features/autosubmit.ts +87 -0
  19. package/assets/plugins/features/checkboxes.ts +92 -0
  20. package/assets/plugins/features/confirm.ts +28 -0
  21. package/assets/plugins/features/editable.ts +118 -0
  22. package/assets/plugins/features/inline.ts +89 -0
  23. package/assets/plugins/features/item-detail.ts +48 -0
  24. package/assets/plugins/features/treeView.ts +44 -0
  25. package/assets/plugins/index.ts +13 -0
  26. package/assets/plugins/integrations/datepicker.ts +25 -0
  27. package/assets/plugins/integrations/happy.ts +19 -0
  28. package/assets/plugins/integrations/nette-forms.ts +18 -0
  29. package/assets/plugins/integrations/selectpicker.ts +25 -0
  30. package/assets/plugins/integrations/sortable.ts +61 -0
  31. package/assets/types/ajax.d.ts +143 -0
  32. package/assets/types/datagrid.d.ts +31 -0
  33. package/assets/types/index.d.ts +41 -0
  34. package/assets/types/integrations.d.ts +15 -0
  35. package/assets/utils.ts +179 -0
  36. package/dist/datagrid-full.css +1 -0
  37. package/dist/datagrid-full.js +20 -0
  38. package/dist/datagrid-full.js.map +1 -0
  39. package/package.json +55 -0
@@ -0,0 +1,228 @@
1
+ import { defaultDatagridNameResolver, isEnter } from "./utils";
2
+ import type { Ajax, DatagridEventMap, DatagridOptions, DatagridsOptions, EventDetail, EventListener, } from "./types";
3
+ import Select from "tom-select";
4
+ import {
5
+ AutosubmitPlugin,
6
+ CheckboxPlugin,
7
+ ConfirmPlugin,
8
+ HappyPlugin,
9
+ InlinePlugin,
10
+ NetteFormsPlugin,
11
+ SelectpickerPlugin,
12
+ SortablePlugin
13
+ } from "./plugins";
14
+ import { SortableJS } from "./integrations";
15
+ import { DatepickerPlugin } from "./plugins";
16
+ import { Happy, TomSelect, VanillaDatepicker } from "./integrations";
17
+
18
+ export class Datagrid extends EventTarget {
19
+ private static readonly defaultOptions: DatagridOptions = {
20
+ confirm: confirm,
21
+ resolveDatagridName: defaultDatagridNameResolver,
22
+ plugins: [],
23
+ };
24
+
25
+ public readonly name: string;
26
+
27
+ public readonly ajax: Ajax;
28
+
29
+ private readonly options: DatagridOptions;
30
+
31
+ constructor(
32
+ public readonly el: HTMLElement,
33
+ ajax: Ajax | ((grid: Datagrid) => Ajax),
34
+ options: Partial<DatagridOptions>
35
+ ) {
36
+ super();
37
+
38
+ this.options = {
39
+ ...Datagrid.defaultOptions,
40
+ ...options,
41
+ };
42
+
43
+ const name = this.resolveDatagridName();
44
+
45
+ if (!name) {
46
+ throw new Error("Cannot resolve name of a datagrid!");
47
+ }
48
+
49
+ this.name = name;
50
+
51
+ this.ajax = typeof ajax === "function" ? ajax(this) : ajax;
52
+
53
+ this.ajax.addEventListener("success", e => {
54
+ if (e.detail.payload?._datagrid_name === this.name && e.detail.payload?._datagrid_init) {
55
+ this.init();
56
+ }
57
+ });
58
+
59
+ this.init();
60
+ }
61
+
62
+ public init() {
63
+ let cancelled = !this.dispatch('beforeInit', { datagrid: this })
64
+ if (!cancelled) {
65
+ this.options.plugins.forEach((plugin) => {
66
+ plugin.onDatagridInit?.(this)
67
+ })
68
+ }
69
+
70
+ // Uncheck toggle-all
71
+ const checkedRows = this.el.querySelectorAll<HTMLInputElement>("input[data-check]:checked");
72
+ if (checkedRows.length === 1 && checkedRows[0].getAttribute("name") === "toggle-all") {
73
+ const input = checkedRows[0];
74
+ if (input) {
75
+ input.checked = false;
76
+ }
77
+ }
78
+
79
+ this.el.querySelectorAll<HTMLInputElement>("input[data-datagrid-manualsubmit]").forEach(inputEl => {
80
+ const form = inputEl.closest("form");
81
+ if (!form) return;
82
+
83
+ inputEl.addEventListener("keydown", e => {
84
+ if (!isEnter(e)) return;
85
+
86
+ e.stopPropagation();
87
+ e.preventDefault();
88
+ return this.ajax.submitForm(form);
89
+ });
90
+ });
91
+
92
+ this.ajax.addEventListener("success", ({ detail: { payload } }) => {
93
+ // todo: maybe move?
94
+ if (payload._datagrid_name && payload._datagrid_name === this.name) {
95
+ const getColumnName = (el: HTMLElement) => el.getAttribute(
96
+ "data-datagrid-reset-filter-by-column"
97
+ )
98
+
99
+ const resets = Array.from<HTMLElement>(this.el.querySelectorAll(
100
+ `[data-datagrid-reset-filter-by-column]`
101
+ ));
102
+
103
+ const nonEmptyFilters = payload.non_empty_filters ? payload.non_empty_filters : [] as string[];
104
+
105
+ resets.forEach((el) => {
106
+ const columnName = getColumnName(el);
107
+
108
+ if (columnName && nonEmptyFilters.includes(columnName)) {
109
+ el.classList.remove("hidden");
110
+ } else {
111
+ el.classList.add("hidden");
112
+ }
113
+ });
114
+
115
+ if (nonEmptyFilters.length > 0) {
116
+ const href = this.el.querySelector(".reset-filter")
117
+ ?.getAttribute("href");
118
+
119
+ if (href) {
120
+ resets.forEach((el) => {
121
+ const columnName = getColumnName(el);
122
+
123
+ const newHref = href.replace("-resetFilter", "-resetColumnFilter");
124
+ el.setAttribute("href", `${newHref}&${this.name}-key=${columnName}`);
125
+ })
126
+ }
127
+ }
128
+ }
129
+ })
130
+
131
+ this.dispatch('afterInit', { datagrid: this });
132
+ }
133
+
134
+ public confirm(message: string): boolean {
135
+ return this.options.confirm.bind(this)(message);
136
+ }
137
+
138
+ public resolveDatagridName(): string | null {
139
+ return this.options.resolveDatagridName.bind(this)(this.el);
140
+ }
141
+
142
+ dispatch<
143
+ K extends string, M extends DatagridEventMap = DatagridEventMap
144
+ >(type: K, detail: K extends keyof M ? EventDetail<M[K]> : any, options?: boolean): boolean {
145
+ return this.dispatchEvent(new CustomEvent(type, { detail }));
146
+ }
147
+
148
+ declare addEventListener: <K extends keyof M, M extends DatagridEventMap = DatagridEventMap>(
149
+ type: K,
150
+ listener: EventListener<this, M[K]>,
151
+ options?: boolean | AddEventListenerOptions
152
+ ) => void;
153
+
154
+ declare removeEventListener: <K extends keyof M, M extends DatagridEventMap = DatagridEventMap>(
155
+ type: K,
156
+ listener: EventListener<this, M[K]>,
157
+ options?: boolean | AddEventListenerOptions
158
+ ) => void;
159
+
160
+ declare dispatchEvent: <K extends string, M extends DatagridEventMap = DatagridEventMap>(
161
+ event: K extends keyof M ? M[K] : CustomEvent
162
+ ) => boolean;
163
+ }
164
+
165
+ export class Datagrids {
166
+ private datagrids: Datagrid[] = [];
167
+
168
+ readonly options: DatagridsOptions;
169
+
170
+ readonly root: HTMLElement;
171
+
172
+ constructor(readonly ajax: Ajax, options: Partial<DatagridsOptions> = {}) {
173
+ this.options = {
174
+ selector: "div[data-datagrid-name]",
175
+ datagrid: {},
176
+ root: document.body,
177
+ ...options,
178
+ };
179
+
180
+ const root = typeof this.options.root === "string"
181
+ ? document.querySelector(this.options.root)
182
+ : this.options.root;
183
+
184
+ if (!root || !(root instanceof HTMLElement)) {
185
+ throw new Error("Root element not found or is not an HTMLElement");
186
+ }
187
+
188
+ this.root = root;
189
+
190
+ this.init();
191
+ }
192
+
193
+ init() {
194
+ this.ajax.onInit();
195
+ (this.options.datagrid?.plugins ?? []).forEach((plugin) => plugin.onInit?.(this));
196
+
197
+ this.initDatagrids();
198
+ }
199
+
200
+ initDatagrids() {
201
+ this.datagrids = Array.from(this.root.querySelectorAll<HTMLElement>(this.options.selector)).map(
202
+ datagrid => new Datagrid(datagrid, this.ajax, this.options.datagrid)
203
+ );
204
+ }
205
+ }
206
+
207
+ export const createDatagrids = (ajax: Ajax, _options: Partial<DatagridsOptions> = {}) => {
208
+ return new Datagrids(ajax, _options);
209
+ };
210
+
211
+ export const createFullDatagrids = (ajax: Ajax, _options: Partial<DatagridsOptions> = {}) => {
212
+ return createDatagrids(ajax, {
213
+ datagrid: {
214
+ plugins: [
215
+ new AutosubmitPlugin(),
216
+ new CheckboxPlugin(),
217
+ new ConfirmPlugin(),
218
+ new InlinePlugin(),
219
+ new NetteFormsPlugin(),
220
+ new HappyPlugin(new Happy()),
221
+ new SortablePlugin(new SortableJS()),
222
+ new DatepickerPlugin(new VanillaDatepicker()),
223
+ new SelectpickerPlugin(new TomSelect(Select))
224
+ ],
225
+ },
226
+ ..._options,
227
+ })
228
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./datagrid";
2
+ export * from "./plugins";
3
+ export * from "./integrations";
4
+ export * from "./datagrid";
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Slightly cleaned up & typed version of happy-inputs by paveljanda.
3
+ */
4
+ export class Happy {
5
+ private colors: string[] = ["primary", "success", "info", "warning", "danger", "white", "gray"];
6
+
7
+ private templates = {
8
+ radio: '<div class="happy-radio"><b></b></div>',
9
+ checkbox:
10
+ '<div class="happy-checkbox"><svg viewBox="0 0 30 30"><rect class="mark-storke" x="15" y="3" rx="1" ry="1" width="10" height="4"/><rect class="mark-storke" x="-7" y="21" rx="1" ry="1" width="19" height="4"/></svg></div>',
11
+ text: "",
12
+ textarea: "",
13
+ };
14
+
15
+ init() {
16
+ this.removeBySelector(".happy-radio");
17
+ this.removeBySelector(".happy-checkbox");
18
+
19
+ this.initRadio();
20
+ this.initCheckbox();
21
+ }
22
+
23
+ /**
24
+ * @deprecated
25
+ */
26
+ reset() {
27
+ this.init();
28
+ }
29
+
30
+ addColorToInput(input: HTMLElement, happyInput: HTMLElement, classString: string) {
31
+ if (input.classList.contains(classString)) {
32
+ happyInput.classList.add(classString);
33
+ }
34
+
35
+ classString = `${classString}-border`;
36
+
37
+ if (input.classList.contains(classString)) {
38
+ happyInput.classList.add(classString);
39
+ }
40
+ }
41
+
42
+ // i... you know what, no, let "thinkess" be "thinkess"
43
+ addThinkessToInput(input: HTMLElement, happyInput: HTMLElement) {
44
+ if (input.classList.contains("thin")) {
45
+ happyInput.classList.add("thin");
46
+ }
47
+ }
48
+
49
+ setNames(input: HTMLElement, happyInput: HTMLElement) {
50
+ happyInput.setAttribute("data-name", input.getAttribute("name") ?? "");
51
+
52
+ var value = input.getAttribute("value");
53
+
54
+ if (value !== "undefined" && value !== null) {
55
+ happyInput.setAttribute("data-value", input.getAttribute("value") ?? "");
56
+ }
57
+ }
58
+
59
+ removeBySelector(selector: string) {
60
+ document.querySelectorAll(selector).forEach(el => el.parentNode?.removeChild(el));
61
+ }
62
+
63
+ initRadio() {
64
+ document.querySelectorAll<HTMLInputElement>("input[type=radio].happy").forEach(input => {
65
+ /**
66
+ * Paste happy component into html
67
+ */
68
+ input.insertAdjacentHTML("afterend", this.templates.radio);
69
+
70
+ const happyInput = input.nextElementSibling;
71
+
72
+ if (happyInput instanceof HTMLElement) {
73
+ /**
74
+ * Add optional colors
75
+ */
76
+ this.colors.forEach(color => {
77
+ this.addColorToInput(input, happyInput, color);
78
+ this.setNames(input, happyInput);
79
+ });
80
+
81
+ this.addThinkessToInput(input, happyInput);
82
+ }
83
+
84
+ /**
85
+ * Init state
86
+ */
87
+ this.checkRadioState(input);
88
+
89
+ /**
90
+ * Set aciton functionality for native change
91
+ */
92
+ document.addEventListener("change", this.radioOnChange.bind(this));
93
+ });
94
+ }
95
+
96
+ initCheckbox() {
97
+ document.querySelectorAll<HTMLInputElement>("input[type=checkbox].happy").forEach(input => {
98
+ /**
99
+ * Paste happy component into html
100
+ */
101
+ input.insertAdjacentHTML("afterend", this.templates.checkbox);
102
+
103
+ const happyInput = input.nextElementSibling;
104
+
105
+ /**
106
+ * Add optional colors
107
+ */
108
+ if (happyInput instanceof HTMLElement) {
109
+ this.colors.forEach(color => {
110
+ this.addColorToInput(input, happyInput, color);
111
+ this.setNames(input, happyInput);
112
+ });
113
+
114
+ this.addThinkessToInput(input, happyInput);
115
+ }
116
+
117
+ /**
118
+ * Init state
119
+ */
120
+ this.checkCheckboxState(input);
121
+
122
+ /**
123
+ * Set action functionality for click || native change
124
+ */
125
+ document.addEventListener("click", this.checkCheckboxStateOnClick.bind(this));
126
+ document.addEventListener("change", this.checkCheckboxStateOnChange.bind(this));
127
+ });
128
+ }
129
+
130
+ checkCheckboxStateOnClick(event: Event) {
131
+ const target = event.target;
132
+
133
+ // When target is SVGSVGElement (<svg>), return parentNode,
134
+ // When target is a SVGGraphicsElement (<rect>,...), find <svg> and return it's parent node
135
+ // otherwise return target itself.
136
+ const happyInput =
137
+ target instanceof SVGSVGElement
138
+ ? target.parentNode
139
+ : target instanceof SVGGraphicsElement
140
+ ? target.closest("svg")?.parentNode
141
+ : target;
142
+
143
+ if (!(happyInput instanceof HTMLElement) || !happyInput.classList.contains("happy-checkbox")) {
144
+ return;
145
+ }
146
+
147
+ event.preventDefault();
148
+
149
+ const name = happyInput.getAttribute("data-name");
150
+ const value = happyInput.getAttribute("data-value");
151
+
152
+ const input = document.querySelector(
153
+ `.happy-checkbox[data-name="${name}"]` + (!!value ? `[value="${value}"]` : "")
154
+ );
155
+ if (!(input instanceof HTMLInputElement)) return;
156
+
157
+ const checked = happyInput.classList.contains("active");
158
+
159
+ input.checked = !checked;
160
+ checked ? happyInput.classList.remove("active") : happyInput.classList.add("active");
161
+ }
162
+
163
+ checkCheckboxStateOnChange({target}: Event) {
164
+ if (!(target instanceof HTMLInputElement)) return;
165
+
166
+ if (target.classList.contains("happy")) {
167
+ this.checkCheckboxState(target);
168
+ }
169
+ }
170
+
171
+ checkRadioState(input: HTMLInputElement) {
172
+ if (!input.checked || !input.hasAttribute("name")) return;
173
+
174
+ const name = input.getAttribute("name");
175
+ const value = input.getAttribute("value");
176
+
177
+ const element = document.querySelector(
178
+ `.happy-checkbox[data-name="${name}"]` + (!!value ? `[data-value="${value}"]` : "")
179
+ );
180
+
181
+ if (element) {
182
+ element.classList.add("active");
183
+ }
184
+ }
185
+
186
+ checkCheckboxState(input: HTMLInputElement) {
187
+ const name = input.getAttribute("name");
188
+ if (!name) return;
189
+
190
+ const value = input.getAttribute("value");
191
+ const element = document.querySelector(
192
+ `.happy-checkbox[data-name="${name}"]` + (!!value ? `[data-value="${value}"]` : "")
193
+ );
194
+
195
+ if (!element) return;
196
+
197
+ input.checked ? element.classList.add("active") : element.classList.remove("active");
198
+ }
199
+
200
+ radioOnChange({target}: Event) {
201
+ // Check whether target is <input>, is a happy input (.happy) & has the name attribute
202
+ if (
203
+ !(target instanceof HTMLInputElement) ||
204
+ !target.classList.contains("happy") ||
205
+ !target.hasAttribute("name")
206
+ )
207
+ return;
208
+
209
+ const name = target.getAttribute("name")!;
210
+
211
+ document
212
+ .querySelectorAll(`.happy-radio[data-name="${name}"]`)
213
+ .forEach(happyRadio => happyRadio.classList.remove("active"));
214
+
215
+ this.checkRadioState(target);
216
+ }
217
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./happy";
2
+ export * from "./sortable-js";
3
+ export * from "./tom-select";
4
+ export * from "./vanilla-datepicker";
@@ -0,0 +1,58 @@
1
+ import { Datagrid } from "..";
2
+ import { Sortable as SortableInterface } from "../types";
3
+ import Sortable from "sortablejs"
4
+
5
+ export class SortableJS implements SortableInterface {
6
+ initSortable(datagrid: Datagrid): void {
7
+ const sortable = datagrid.el.querySelector<HTMLElement>("[data-sortable]");
8
+ if (sortable) {
9
+ new Sortable(sortable, {
10
+ handle: '.handle-sort',
11
+ draggable: 'tr',
12
+ sort: true,
13
+ direction: 'vertical',
14
+ async onEnd({item}) {
15
+ const itemId = item.getAttribute("data-id");
16
+ if (itemId) {
17
+ const prevId = item.previousElementSibling?.getAttribute("data-id") ?? null;
18
+ const nextId = item.nextElementSibling?.getAttribute("data-id") ?? null;
19
+
20
+ const tbody = datagrid.el.querySelector("tbody");
21
+
22
+ if (tbody) {
23
+ let componentPrefix = tbody.getAttribute("data-sortable-parent-path") ?? '';
24
+ if (componentPrefix.length) componentPrefix = `${componentPrefix}-`;
25
+
26
+ const url = tbody.getAttribute("data-sortable-url") ?? "?do=sort";
27
+
28
+ const data = {
29
+ [`${componentPrefix}item_id`]: itemId,
30
+ ...(prevId ? {[`${componentPrefix}prev_id`]: prevId} : {}),
31
+ ...(nextId ? {[`${componentPrefix}next_id`]: nextId} : {}),
32
+ };
33
+
34
+ return await datagrid.ajax.request({
35
+ method: "GET",
36
+ url,
37
+ data,
38
+ })
39
+ }
40
+ }
41
+ },
42
+ })
43
+ }
44
+ }
45
+
46
+ initSortableTree(datagrid: Datagrid): void {
47
+ datagrid.el.querySelectorAll<HTMLElement>(".datagrid-tree-item-children").forEach((el) => {
48
+ new Sortable(el, {
49
+ handle: '.handle-sort',
50
+ draggable: '.datagrid-tree-item:not(.datagrid-tree-header)',
51
+ async onEnd({item}) {
52
+ // TODO
53
+ },
54
+ })
55
+ })
56
+ }
57
+
58
+ }
@@ -0,0 +1,30 @@
1
+ import { Constructor, Selectpicker } from "../types";
2
+ import { RecursivePartial, TomInput, TomSettings } from "tom-select/dist/types/types";
3
+ import type TomSelectType from "tom-select";
4
+ import { window } from "../utils";
5
+
6
+ export class TomSelect implements Selectpicker {
7
+ constructor(
8
+ private select?: Constructor<TomSelectType>,
9
+ private opts: RecursivePartial<TomSettings> | ((input: HTMLElement | TomInput) => RecursivePartial<TomSettings>) = {}
10
+ ) {
11
+ }
12
+
13
+ initSelectpickers(elements: HTMLElement[]): void {
14
+ const Select = this.select ?? window()?.TomSelect ?? null;
15
+
16
+ if (Select) {
17
+ for (const element of elements) {
18
+ // Check if TomSelect is already initialized on the element
19
+ if (element.tomselect) {
20
+ continue;
21
+ }
22
+
23
+ new Select(
24
+ element as TomInput,
25
+ typeof this.opts === "function" ? this.opts(element) : this.opts
26
+ )
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ import { Datepicker as DatepickerInterface } from "../types";
2
+ import { Datepicker } from "vanillajs-datepicker";
3
+ import { DatepickerOptions } from "vanillajs-datepicker/Datepicker";
4
+
5
+ export class VanillaDatepicker implements DatepickerInterface {
6
+ constructor(private opts: DatepickerOptions | ((input: HTMLInputElement) => DatepickerOptions) = {}) {
7
+ }
8
+
9
+ initDatepickers(elements: HTMLInputElement[]): void {
10
+ elements.forEach((element) => {
11
+ const options = typeof this.opts === "function" ? this.opts(element) : this.opts;
12
+ const picker = new Datepicker(element, {
13
+ ...options,
14
+ updateOnBlur: false
15
+ });
16
+
17
+ element.addEventListener('changeDate', () => {
18
+ const form = element.closest('form');
19
+ if (form) {
20
+ form.submit();
21
+ }
22
+ });
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,87 @@
1
+ import { DatagridPlugin } from "../../types";
2
+ import { debounce, isEnter, isFunctionKey, isInKeyRange } from "../../utils";
3
+ import { Datagrid } from "../..";
4
+
5
+ export const AutosubmitAttribute = "data-autosubmit";
6
+
7
+ export const AutosubmitPerPageAttribute = "data-autosubmit-per-page";
8
+
9
+ export const AutosubmitChangeAttribute = "data-autosubmit-change";
10
+
11
+ export class AutosubmitPlugin implements DatagridPlugin {
12
+ onDatagridInit(datagrid: Datagrid): boolean {
13
+ datagrid.ajax.addEventListener('complete', (event) => {
14
+ this.initPerPage(datagrid);
15
+ this.initChange(datagrid);
16
+ });
17
+
18
+ this.initPerPage(datagrid);
19
+ this.initChange(datagrid);
20
+
21
+ return true;
22
+ };
23
+
24
+ // Auto-submit perPage
25
+ initPerPage(datagrid: Datagrid) {
26
+ datagrid.el.querySelectorAll<HTMLSelectElement>(`select[${AutosubmitPerPageAttribute}]`)
27
+ .forEach(pageSelectEl => {
28
+ pageSelectEl.addEventListener("change", () => {
29
+ let inputEl = pageSelectEl.parentElement?.querySelector("input[type=submit]");
30
+ if (!inputEl) {
31
+ inputEl = pageSelectEl.parentElement?.querySelector("button[type=submit]");
32
+ }
33
+ console.log({ inputEl });
34
+ if (!(inputEl instanceof HTMLElement)) return;
35
+ const form = inputEl.closest('form');
36
+ console.log({ form });
37
+ form && datagrid.ajax.submitForm(form);
38
+ });
39
+ });
40
+ }
41
+
42
+ // Auto-submit change
43
+ initChange(datagrid: Datagrid) {
44
+ datagrid.el.querySelectorAll<HTMLSelectElement | HTMLInputElement>(`[${AutosubmitAttribute}]`)
45
+ .forEach(submitEl => {
46
+ const form = submitEl.closest("form");
47
+ if (!form) return;
48
+
49
+ if (submitEl.dataset.listenersAttached === "true") {
50
+ return; // Skip if listeners are already attached
51
+ }
52
+ submitEl.dataset.listenersAttached = "true";
53
+
54
+ // Select auto-submit
55
+ if (submitEl instanceof HTMLSelectElement) {
56
+ submitEl.addEventListener("change", () => datagrid.ajax.submitForm(form));
57
+
58
+ return;
59
+ }
60
+
61
+ // Input auto-submit
62
+ if (submitEl instanceof HTMLInputElement) {
63
+ // Handle change events
64
+ if (submitEl.hasAttribute(AutosubmitChangeAttribute)) {
65
+ submitEl.addEventListener(
66
+ "change",
67
+ debounce(() => datagrid.ajax.submitForm(form))
68
+ );
69
+ }
70
+
71
+ submitEl.addEventListener(
72
+ "keyup",
73
+ debounce(e => {
74
+ // Ignore keys such as alt, ctrl, etc, F-keys... (when enter is not pressed)
75
+ if (!isEnter(e) && (isInKeyRange(e, 9, 40) || isFunctionKey(e))) {
76
+ return;
77
+ }
78
+
79
+ return datagrid.ajax.submitForm(form);
80
+ })
81
+ );
82
+ }
83
+ });
84
+ }
85
+ }
86
+
87
+