@ornery/ui-grid-vanilla 0.1.10 → 1.0.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.
Files changed (62) hide show
  1. package/README.md +196 -0
  2. package/demo/main.ts +0 -1
  3. package/dist/index.d.mts +575 -55
  4. package/dist/index.d.ts +575 -55
  5. package/dist/index.js +4894 -1503
  6. package/dist/index.mjs +4994 -1556
  7. package/package.json +11 -8
  8. package/scss-plugin.ts +65 -0
  9. package/src/adopt-styles.ts +39 -0
  10. package/src/attribute-bridge.ts +291 -0
  11. package/src/auto-height.test.ts +142 -0
  12. package/src/auto-resize.test.ts +206 -0
  13. package/src/cell-nav.test.ts +262 -0
  14. package/src/components/grid-body-cell/grid-body-cell.ts +23 -0
  15. package/src/components/grid-body-cell/index.ts +1 -0
  16. package/src/components/grid-cell-editor/grid-cell-editor.ts +104 -0
  17. package/src/components/grid-cell-editor/index.ts +1 -0
  18. package/src/components/grid-filter-cell/grid-filter-cell.scss +14 -0
  19. package/src/components/grid-filter-cell/grid-filter-cell.template.ts +20 -0
  20. package/src/components/grid-filter-cell/grid-filter-cell.ts +121 -0
  21. package/src/components/grid-filter-cell/index.ts +1 -0
  22. package/src/components/grid-group-row/grid-group-row.scss +24 -0
  23. package/src/components/grid-group-row/grid-group-row.template.ts +23 -0
  24. package/src/components/grid-group-row/grid-group-row.ts +94 -0
  25. package/src/components/grid-group-row/index.ts +1 -0
  26. package/src/components/grid-header-cell/grid-header-cell.ts +17 -0
  27. package/src/components/grid-header-cell/index.ts +1 -0
  28. package/src/components/grid-pagination/grid-pagination.scss +39 -0
  29. package/src/components/grid-pagination/grid-pagination.template.ts +40 -0
  30. package/src/components/grid-pagination/grid-pagination.ts +204 -0
  31. package/src/components/grid-pagination/index.ts +1 -0
  32. package/src/components/grid-template/grid-template.ts +166 -0
  33. package/src/components/grid-template/index.ts +1 -0
  34. package/src/events.ts +815 -0
  35. package/src/exporter.test.ts +186 -0
  36. package/src/focus.ts +353 -0
  37. package/src/framework-slots.ts +440 -0
  38. package/src/grid-controller.ts +1402 -18
  39. package/src/icons.ts +88 -0
  40. package/src/importer.test.ts +147 -0
  41. package/src/index.integration.test.ts +2 -2
  42. package/src/index.ts +9 -0
  43. package/src/infinite-scroll.test.ts +220 -0
  44. package/src/keyboard-navigation.test.ts +579 -0
  45. package/src/patch.ts +722 -0
  46. package/src/render.ts +465 -0
  47. package/src/row-edit.test.ts +191 -0
  48. package/src/row-selection.test.ts +550 -0
  49. package/src/save-state.test.ts +235 -0
  50. package/src/scss.d.ts +4 -0
  51. package/src/templates/index.ts +62 -0
  52. package/src/test-setup.ts +23 -0
  53. package/src/ui-grid-empty.template.ts +23 -0
  54. package/src/ui-grid-shell.scss +25 -0
  55. package/src/ui-grid-shell.template.ts +45 -0
  56. package/src/ui-grid-standalone.element.ts +741 -1112
  57. package/src/utils/cell-class.ts +70 -0
  58. package/src/utils/display-items.ts +15 -0
  59. package/src/utils/dom.ts +64 -0
  60. package/src/validate.test.ts +170 -0
  61. package/tsup.config.ts +10 -0
  62. package/vite.config.ts +10 -1
@@ -0,0 +1,166 @@
1
+ const thisRegex = /^(this|props)\./i;
2
+ const nestedES6 = /\$\{.*(\$\{(.+?)\}).*\}/g;
3
+ const es6Regex = /\$\{(.+?)\}/g;
4
+
5
+ function getFromObj(path: string, obj: Record<string, unknown> = {}): unknown {
6
+ path = path?.trim();
7
+ if (path != null) {
8
+ if (obj[path] != null) return obj[path];
9
+ if (/^[\w-]+(\.[\w-]+)+$/.test(path)) {
10
+ return path.split('.').reduce<unknown>((res, key) => {
11
+ return (res as Record<string, unknown>)?.[key] ?? path;
12
+ }, obj);
13
+ }
14
+ }
15
+ return path;
16
+ }
17
+
18
+ function interpolate(expr: string, context: Record<string, unknown>): string {
19
+ if (typeof expr !== 'string') return expr;
20
+ es6Regex.lastIndex = 0;
21
+ nestedES6.lastIndex = 0;
22
+ let result = expr.replace(thisRegex, '');
23
+ let matchArr: RegExpExecArray | null;
24
+ while ((matchArr = nestedES6.exec(result))) {
25
+ const [, outerMatch, key] = matchArr;
26
+ const replacement = String(getFromObj(key.replace(thisRegex, ''), context));
27
+ result = interpolate(result.replace(outerMatch, replacement).trim(), context);
28
+ }
29
+ return result.replace(es6Regex, (_match, $1: string) =>
30
+ String(getFromObj($1.replace(thisRegex, ''), context))
31
+ );
32
+ }
33
+
34
+ function bindEvents(root: HTMLElement, context: Record<string, unknown>): HTMLElement {
35
+ const domElements = Array.from(root.querySelectorAll('*'));
36
+ domElements.forEach((el) => {
37
+ Array.from(el.attributes).forEach((attribute) => {
38
+ if (attribute.name.startsWith('on')) {
39
+ let fnOrName: unknown = interpolate(attribute.value, context);
40
+ if (typeof (context as Record<string, unknown>)[fnOrName as string] === 'function') {
41
+ fnOrName = (context as Record<string, unknown>)[fnOrName as string];
42
+ }
43
+ if (typeof fnOrName === 'function') {
44
+ const fn = fnOrName as (...args: unknown[]) => void;
45
+ el.addEventListener(attribute.name.replace('on', ''), function (...args: unknown[]) {
46
+ fn.apply(context, args);
47
+ });
48
+ }
49
+ el.removeAttribute(attribute.name);
50
+ }
51
+ });
52
+ });
53
+ return root;
54
+ }
55
+
56
+ type ConnectableNodeList = Node[] & {
57
+ connect(root: Element | ShadowRoot): void;
58
+ };
59
+
60
+ function setupConnect(nodeList: Node[], _context: Record<string, unknown>): ConnectableNodeList {
61
+ const result = nodeList as ConnectableNodeList;
62
+ result.connect = function (root: Element | ShadowRoot) {
63
+ if (typeof HTMLElement === 'undefined') return;
64
+ if (root) {
65
+ root.innerHTML = '';
66
+ result.forEach((node) => root.appendChild(node));
67
+ }
68
+ };
69
+ return result;
70
+ }
71
+
72
+ /**
73
+ * A declarative template element for defining reusable cell/row templates
74
+ * without writing JavaScript.
75
+ *
76
+ * Supports both `${this.prop}` and `{{prop}}` binding syntax.
77
+ *
78
+ * @example
79
+ * ```html
80
+ * <template is="ui-grid-template" name="ui-status-badge" status="unknown">
81
+ * <style>
82
+ * :host { display: inline-block; }
83
+ * .badge { padding: 2px 8px; border-radius: 4px; font-size: 0.8em; }
84
+ * .badge-active { background: #d1fae5; color: #065f46; }
85
+ * .badge-inactive { background: #fee2e2; color: #991b1b; }
86
+ * </style>
87
+ * <span class="badge badge-${this.status}">${this.status}</span>
88
+ * </template>
89
+ *
90
+ * <ui-grid-element>
91
+ * <template slot="cell-status">
92
+ * <ui-status-badge status="{{value}}"></ui-status-badge>
93
+ * </template>
94
+ * </ui-grid-element>
95
+ * ```
96
+ */
97
+ export class UIGridTemplate extends HTMLTemplateElement {
98
+ static readonly TAG = 'ui-grid-template';
99
+
100
+ connectedCallback(): void {
101
+ const componentName = this.getAttribute('name') || this.getAttribute('id');
102
+ if (!componentName) {
103
+ throw new Error(
104
+ 'ui-grid-template requires a "name" or "id" attribute to register the component'
105
+ );
106
+ }
107
+
108
+ const htmlTemplate = this.innerHTML;
109
+ const defaultAttrs = this;
110
+
111
+ const render = (props: Record<string, unknown> = {}) => {
112
+ const rendered = htmlTemplate.replace(/\$\{.+?}/gim, (s) => {
113
+ return interpolate(s, props) as string;
114
+ }).replace(/{{\s*([^}]+?)\s*}}/g, (_match, expression) => {
115
+ return interpolate('${' + String(expression).trim() + '}', props) as string;
116
+ });
117
+ const parsed = new DOMParser().parseFromString(rendered, 'text/html');
118
+ const elements = [...parsed.head.children, ...bindEvents(parsed.body, props).childNodes];
119
+ return setupConnect(elements, props);
120
+ };
121
+
122
+ if (!customElements.get(componentName)) {
123
+ customElements.define(
124
+ componentName,
125
+ class extends HTMLElement {
126
+ private _observer: MutationObserver | null = null;
127
+
128
+ constructor() {
129
+ super();
130
+ this.attachShadow({ mode: 'open' });
131
+ }
132
+
133
+ connectedCallback(): void {
134
+ this.applyAttributes();
135
+ this._observer = new MutationObserver(() => this.applyAttributes());
136
+ this._observer.observe(this, { attributes: true });
137
+ }
138
+
139
+ applyAttributes(): void {
140
+ const props: Record<string, string> = {};
141
+ Array.from(defaultAttrs.attributes).forEach((attr) => {
142
+ if (attr.name !== 'is' && attr.name !== 'name' && attr.name !== 'id') {
143
+ props[attr.name] = attr.value;
144
+ }
145
+ });
146
+ Array.from(this.attributes).forEach((attr) => {
147
+ props[attr.name] = attr.value;
148
+ });
149
+ render(props).connect(this.shadowRoot!);
150
+ }
151
+
152
+ disconnectedCallback(): void {
153
+ this._observer?.disconnect();
154
+ this._observer = null;
155
+ }
156
+ },
157
+ );
158
+ }
159
+ }
160
+
161
+ static define(tagName = UIGridTemplate.TAG): void {
162
+ if (!customElements.get(tagName)) {
163
+ customElements.define(tagName, UIGridTemplate, { extends: 'template' });
164
+ }
165
+ }
166
+ }
@@ -0,0 +1 @@
1
+ export { UIGridTemplate } from './grid-template';