mtrl-addons 0.1.1 → 0.2.1

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 (115) hide show
  1. package/build.js +139 -108
  2. package/package.json +13 -4
  3. package/scripts/debug/vlist-selection.ts +121 -0
  4. package/src/components/index.ts +5 -41
  5. package/src/components/{list → vlist}/config.ts +66 -95
  6. package/src/components/vlist/constants.ts +23 -0
  7. package/src/components/vlist/features/api.ts +322 -0
  8. package/src/components/vlist/features/index.ts +10 -0
  9. package/src/components/vlist/features/selection.ts +444 -0
  10. package/src/components/vlist/features/viewport.ts +65 -0
  11. package/src/components/vlist/index.ts +16 -0
  12. package/src/components/{list → vlist}/types.ts +104 -26
  13. package/src/components/vlist/vlist.ts +92 -0
  14. package/src/core/compose/features/gestures/index.ts +227 -0
  15. package/src/core/compose/features/gestures/longpress.ts +383 -0
  16. package/src/core/compose/features/gestures/pan.ts +424 -0
  17. package/src/core/compose/features/gestures/pinch.ts +475 -0
  18. package/src/core/compose/features/gestures/rotate.ts +485 -0
  19. package/src/core/compose/features/gestures/swipe.ts +492 -0
  20. package/src/core/compose/features/gestures/tap.ts +334 -0
  21. package/src/core/compose/features/index.ts +2 -38
  22. package/src/core/compose/index.ts +13 -29
  23. package/src/core/gestures/index.ts +31 -0
  24. package/src/core/gestures/longpress.ts +68 -0
  25. package/src/core/gestures/manager.ts +418 -0
  26. package/src/core/gestures/pan.ts +48 -0
  27. package/src/core/gestures/pinch.ts +58 -0
  28. package/src/core/gestures/rotate.ts +58 -0
  29. package/src/core/gestures/swipe.ts +66 -0
  30. package/src/core/gestures/tap.ts +45 -0
  31. package/src/core/gestures/types.ts +387 -0
  32. package/src/core/gestures/utils.ts +128 -0
  33. package/src/core/index.ts +27 -151
  34. package/src/core/layout/schema.ts +73 -35
  35. package/src/core/layout/types.ts +5 -2
  36. package/src/core/viewport/constants.ts +140 -0
  37. package/src/core/viewport/features/base.ts +73 -0
  38. package/src/core/viewport/features/collection.ts +882 -0
  39. package/src/core/viewport/features/events.ts +130 -0
  40. package/src/core/viewport/features/index.ts +20 -0
  41. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +27 -30
  42. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  43. package/src/core/viewport/features/momentum.ts +260 -0
  44. package/src/core/viewport/features/placeholders.ts +335 -0
  45. package/src/core/viewport/features/rendering.ts +568 -0
  46. package/src/core/viewport/features/scrollbar.ts +434 -0
  47. package/src/core/viewport/features/scrolling.ts +618 -0
  48. package/src/core/viewport/features/utils.ts +88 -0
  49. package/src/core/viewport/features/virtual.ts +384 -0
  50. package/src/core/viewport/index.ts +31 -0
  51. package/src/core/viewport/types.ts +133 -0
  52. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  53. package/src/core/viewport/viewport.ts +246 -0
  54. package/src/index.ts +0 -7
  55. package/src/styles/components/_vlist.scss +331 -0
  56. package/src/styles/index.scss +1 -1
  57. package/test/components/vlist-selection.test.ts +240 -0
  58. package/test/components/vlist.test.ts +63 -0
  59. package/test/core/collection/adapter.test.ts +161 -0
  60. package/bun.lock +0 -792
  61. package/src/components/list/api.ts +0 -314
  62. package/src/components/list/constants.ts +0 -56
  63. package/src/components/list/features/api.ts +0 -428
  64. package/src/components/list/features/index.ts +0 -31
  65. package/src/components/list/features/list-manager.ts +0 -502
  66. package/src/components/list/index.ts +0 -39
  67. package/src/components/list/list.ts +0 -234
  68. package/src/core/collection/base-collection.ts +0 -100
  69. package/src/core/collection/collection-composer.ts +0 -178
  70. package/src/core/collection/collection.ts +0 -745
  71. package/src/core/collection/constants.ts +0 -172
  72. package/src/core/collection/events.ts +0 -428
  73. package/src/core/collection/features/api/loading.ts +0 -279
  74. package/src/core/collection/features/operations/data-operations.ts +0 -147
  75. package/src/core/collection/index.ts +0 -104
  76. package/src/core/collection/state.ts +0 -497
  77. package/src/core/collection/types.ts +0 -404
  78. package/src/core/compose/features/collection.ts +0 -119
  79. package/src/core/compose/features/selection.ts +0 -213
  80. package/src/core/compose/features/styling.ts +0 -108
  81. package/src/core/list-manager/api.ts +0 -599
  82. package/src/core/list-manager/config.ts +0 -593
  83. package/src/core/list-manager/constants.ts +0 -268
  84. package/src/core/list-manager/features/api.ts +0 -58
  85. package/src/core/list-manager/features/collection/collection.ts +0 -705
  86. package/src/core/list-manager/features/collection/index.ts +0 -17
  87. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  88. package/src/core/list-manager/features/viewport/index.ts +0 -16
  89. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  90. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  91. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  92. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  93. package/src/core/list-manager/features/viewport/template.ts +0 -220
  94. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  95. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  96. package/src/core/list-manager/index.ts +0 -279
  97. package/src/core/list-manager/list-manager.ts +0 -206
  98. package/src/core/list-manager/types.ts +0 -439
  99. package/src/core/list-manager/utils/calculations.ts +0 -290
  100. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  101. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  102. package/src/styles/components/_list.scss +0 -244
  103. package/src/types/mtrl.d.ts +0 -6
  104. package/test/components/list.test.ts +0 -256
  105. package/test/core/collection/failed-ranges.test.ts +0 -270
  106. package/test/core/compose/features.test.ts +0 -183
  107. package/test/core/list-manager/features/collection.test.ts +0 -704
  108. package/test/core/list-manager/features/viewport.test.ts +0 -698
  109. package/test/core/list-manager/list-manager.test.ts +0 -593
  110. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  111. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  112. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  113. package/tsconfig.build.json +0 -14
  114. /package/src/components/{list → vlist}/features.ts +0 -0
  115. /package/src/core/{compose → viewport}/features/performance.ts +0 -0
@@ -180,45 +180,80 @@ function releaseFragment(fragment: DocumentFragment): void {
180
180
 
181
181
  /**
182
182
  * Optimized class processing with minimal string operations
183
- * Handles arrays, strings, and className aliases efficiently
183
+ * Handles arrays, strings, className aliases, and rawClass efficiently
184
184
  */
185
185
  function processClassNames(
186
186
  options: Record<string, any>,
187
187
  skipPrefix = false
188
188
  ): Record<string, any> {
189
- if (!options || skipPrefix) return { ...options };
189
+ if (!options) return options;
190
190
 
191
- const hasClassProps = options.class || options.className;
192
- if (!hasClassProps) return { ...options };
191
+ const hasRawClass = options.rawClass;
192
+ const hasRegularClass = options.class || options.className;
193
193
 
194
- const processed = { ...options };
194
+ // Fast path: no class properties at all
195
+ if (!hasRawClass && !hasRegularClass) return options;
196
+
197
+ // Fast path: only rawClass and skipping prefix (most common rawClass scenario)
198
+ if (hasRawClass && !hasRegularClass && skipPrefix) {
199
+ const processed = { ...options };
200
+ delete processed.rawClass;
195
201
 
196
- // Combine class sources efficiently
197
- let classString = "";
198
- if (processed.class) {
199
- // Handle arrays by joining first
200
- if (Array.isArray(processed.class)) {
201
- classString += processed.class.join(" ");
202
+ // Direct assignment for simple string
203
+ if (typeof hasRawClass === "string") {
204
+ processed.class = hasRawClass;
202
205
  } else {
203
- classString += processed.class;
206
+ // Handle array case
207
+ processed.class = hasRawClass.join(" ");
208
+ }
209
+ return processed;
210
+ }
211
+
212
+ // Full processing path (only when needed)
213
+ const processed = { ...options };
214
+ let finalClasses = "";
215
+
216
+ // Handle prefixed classes only if not skipping prefix
217
+ if (!skipPrefix && hasRegularClass) {
218
+ let prefixedString = "";
219
+
220
+ if (processed.class) {
221
+ prefixedString += Array.isArray(processed.class)
222
+ ? processed.class.join(" ")
223
+ : processed.class;
224
+ }
225
+ if (processed.className) {
226
+ prefixedString += (prefixedString ? " " : "") + processed.className;
227
+ }
228
+
229
+ if (prefixedString) {
230
+ finalClasses = prefixedString
231
+ .split(/\s+/)
232
+ .filter(Boolean)
233
+ .map((cls) =>
234
+ cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls
235
+ )
236
+ .join(" ");
204
237
  }
205
238
  }
206
- if (processed.className) {
207
- classString += (classString ? " " : "") + processed.className;
239
+
240
+ // Handle rawClass (always processed when present)
241
+ if (hasRawClass) {
242
+ const rawString = Array.isArray(hasRawClass)
243
+ ? hasRawClass.filter(Boolean).join(" ")
244
+ : hasRawClass;
245
+
246
+ finalClasses += (finalClasses ? " " : "") + rawString;
208
247
  }
209
248
 
210
- if (classString) {
211
- // Single pass prefix application
212
- processed.class = classString
213
- .split(/\s+/)
214
- .filter(Boolean)
215
- .map((cls) =>
216
- cls && !cls.startsWith(PREFIX_WITH_DASH) ? PREFIX_WITH_DASH + cls : cls
217
- )
218
- .join(" ");
249
+ if (finalClasses) {
250
+ processed.class = finalClasses;
219
251
  }
220
252
 
253
+ // Clean up in one operation
221
254
  delete processed.className;
255
+ delete processed.rawClass;
256
+
222
257
  return processed;
223
258
  }
224
259
 
@@ -272,7 +307,7 @@ function extractParameters(
272
307
  return {
273
308
  creator: creator || defaultCreator,
274
309
  name,
275
- options: options || {},
310
+ options: (options || {}) as Record<string, any>,
276
311
  consumed,
277
312
  };
278
313
  }
@@ -618,7 +653,7 @@ function getLayoutType(element: HTMLElement): string {
618
653
  * Optimized with parameter extraction and integrated configuration
619
654
  */
620
655
  function processArraySchema(
621
- schema: SchemaItem[],
656
+ schema: SchemaItem[] | any,
622
657
  parentElement: HTMLElement | null = null,
623
658
  level: number = 0,
624
659
  options: LayoutOptions = {}
@@ -633,7 +668,7 @@ function processArraySchema(
633
668
  return createLayoutResult(layout);
634
669
  }
635
670
 
636
- const defaultCreator = options.creator || createElement;
671
+ const defaultCreator = (options as any).creator || createElement;
637
672
 
638
673
  for (let i = 0; i < schema.length; i++) {
639
674
  const item = schema[i];
@@ -668,12 +703,15 @@ function processArraySchema(
668
703
  // Advance index by consumed items minus 1 (loop increment handles the +1)
669
704
  i += consumed - 1;
670
705
 
671
- // Process options with prefix
706
+ // Process options with prefix - optimized decision logic
672
707
  const shouldApplyPrefix =
673
708
  "prefix" in itemOptions ? itemOptions.prefix : options.prefix !== false;
674
- const processedOptions = shouldApplyPrefix
675
- ? processClassNames(itemOptions)
676
- : { ...itemOptions };
709
+
710
+ // Fast path: process only when needed
711
+ const processedOptions =
712
+ shouldApplyPrefix || itemOptions.rawClass
713
+ ? processClassNames(itemOptions, !shouldApplyPrefix)
714
+ : itemOptions; // No copy needed if no processing
677
715
 
678
716
  // Add name to options if needed
679
717
  if (
@@ -734,7 +772,7 @@ function processArraySchema(
734
772
  * Simplified and optimized for better performance
735
773
  */
736
774
  function processObjectSchema(
737
- schema: Record<string, any>,
775
+ schema: Record<string, any> | string,
738
776
  parentElement: HTMLElement | null = null,
739
777
  options: LayoutOptions = {}
740
778
  ): LayoutResult {
@@ -742,8 +780,8 @@ function processObjectSchema(
742
780
  const defaultCreator = options.creator || createElement;
743
781
 
744
782
  // Handle root element creation
745
- if (schema.element && !parentElement) {
746
- const elementDef = schema.element;
783
+ if ((schema as any).element && !parentElement) {
784
+ const elementDef = (schema as any).element;
747
785
  const createElementFn = elementDef.creator || defaultCreator;
748
786
 
749
787
  const elementOptions = elementDef.options || {};
@@ -778,8 +816,8 @@ function processObjectSchema(
778
816
  // Process normal schema elements
779
817
  const fragment = parentElement ? createFragment() : null;
780
818
 
781
- for (const key in schema) {
782
- const def = schema[key];
819
+ for (const key in schema as Record<string, any>) {
820
+ const def = (schema as Record<string, any>)[key];
783
821
  if (!def) continue;
784
822
 
785
823
  const elementCreator = def.creator || defaultCreator;
@@ -37,12 +37,15 @@ export interface ElementOptions extends Record<string, any> {
37
37
  /** Layout item configuration */
38
38
  layoutItem?: LayoutItemConfig;
39
39
 
40
- /** CSS classes to apply */
40
+ /** CSS classes to apply (with automatic mtrl- prefix) */
41
41
  class?: string;
42
42
 
43
43
  /** Additional CSS classes (alias for class) */
44
44
  className?: string;
45
45
 
46
+ /** CSS classes to apply without prefix */
47
+ rawClass?: string | string[];
48
+
46
49
  /** HTML tag name for createElement */
47
50
  tag?: string;
48
51
 
@@ -89,4 +92,4 @@ export interface Schema {
89
92
 
90
93
  /** Additional elements */
91
94
  [key: string]: ElementDefinition | undefined;
92
- }
95
+ }
@@ -0,0 +1,140 @@
1
+ // src/core/viewport/constants.ts
2
+
3
+ /**
4
+ * Viewport Constants
5
+ * Centralized constants for all viewport functionality
6
+ * Consolidated from viewport, viewport/features, and list-manager constants
7
+ */
8
+
9
+ export const VIEWPORT_CONSTANTS = {
10
+ // Virtual scrolling defaults
11
+ VIRTUAL_SCROLL: {
12
+ DEFAULT_ITEM_SIZE: 48,
13
+ OVERSCAN_BUFFER: 2,
14
+ SCROLL_SENSITIVITY: 0.7,
15
+ MAX_VIRTUAL_SIZE: 100 * 1000 * 1000, // 100M pixels - modern browsers can handle this
16
+ AUTO_DETECT_ITEM_SIZE: true, // Enable automatic item size detection
17
+ },
18
+
19
+ // Scrolling settings
20
+ SCROLLING: {
21
+ OVERSCAN: 1, // From features/constants
22
+ },
23
+
24
+ // Rendering settings
25
+ RENDERING: {
26
+ // Element recycling
27
+ DEFAULT_MAX_POOL_SIZE: 100,
28
+ CLASSES: {
29
+ ITEM: "viewport-item",
30
+ },
31
+ },
32
+
33
+ // Loading settings
34
+ LOADING: {
35
+ CANCEL_THRESHOLD: 2, // px/ms - velocity above which loads cancel
36
+ MAX_CONCURRENT_REQUESTS: 1, // Parallel requests allowed
37
+ DEFAULT_RANGE_SIZE: 20, // Items per request
38
+ DEBOUNCE_LOADING: 150, // Debounce delay (ms)
39
+ MIN_RANGE_SIZE: 10, // Minimum items per load
40
+ MAX_RANGE_SIZE: 100, // Maximum items per load
41
+ REQUEST_TIMEOUT: 5000, // Request timeout (ms)
42
+ RETRY_ATTEMPTS: 2, // Failed request retries
43
+ RETRY_DELAY: 1000, // Delay between retries (ms)
44
+ },
45
+
46
+ // Request queue configuration (from features/constants)
47
+ REQUEST_QUEUE: {
48
+ ENABLED: true, // Enable request queuing
49
+ MAX_QUEUE_SIZE: 1, // Max queued requests
50
+ MAX_ACTIVE_REQUESTS: 2, // Max concurrent active requests
51
+ },
52
+
53
+ // Placeholder settings
54
+ PLACEHOLDER: {
55
+ MASK_CHARACTER: "X", // Updated from list-manager
56
+ CLASS: "viewport-item--placeholder",
57
+ MAX_SAMPLE_SIZE: 20,
58
+ PLACEHOLDER_FLAG: "_placeholder",
59
+ RANDOM_LENGTH_VARIANCE: true,
60
+ },
61
+
62
+ // Speed tracking (from list-manager)
63
+ SPEED_TRACKING: {
64
+ // Velocity calculation
65
+ DECELERATION_FACTOR: 0.85, // velocity decay per frame
66
+ },
67
+
68
+ // Momentum settings
69
+ MOMENTUM: {
70
+ ENABLED: false, // Enable momentum by default
71
+ DECELERATION_FACTOR: 0.85, // How quickly velocity decreases per frame
72
+ MIN_VELOCITY: 0.1, // Minimum velocity before stopping (px/ms)
73
+ MIN_DURATION: 300, // Maximum gesture duration to trigger momentum (ms)
74
+ MIN_VELOCITY_THRESHOLD: 0.5, // Minimum velocity to trigger momentum (px/ms)
75
+ FRAME_TIME: 16, // Assumed frame time for calculations (ms)
76
+ },
77
+
78
+ // Initial load configuration (from list-manager)
79
+ INITIAL_LOAD: {
80
+ STRATEGY: "placeholders", // "placeholders" | "direct" | "progressive"
81
+ VIEWPORT_MULTIPLIER: 1.5, // load 1.5x viewport capacity
82
+ MIN_ITEMS: 10,
83
+ MAX_ITEMS: 100,
84
+ PLACEHOLDER_COUNT: 20, // default placeholder count
85
+ SHOW_LOADING_STATE: true,
86
+ LOADING_DELAY: 100, // ms - delay before showing loading state
87
+ },
88
+
89
+ // Scrollbar settings (from list-manager)
90
+ SCROLLBAR: {
91
+ // CSS classes
92
+ CLASSES: {
93
+ SCROLLBAR: "viewport__scrollbar",
94
+ SCROLLBAR_TRACK: "viewport__scrollbar-track",
95
+ SCROLLBAR_THUMB: "viewport__scrollbar-thumb",
96
+ SCROLLBAR_VISIBLE: "viewport__scrollbar--visible",
97
+ SCROLLBAR_DRAGGING: "viewport__scrollbar--dragging",
98
+ SCROLLBAR_THUMB_DRAGGING: "viewport__scrollbar-thumb--dragging",
99
+ },
100
+ },
101
+
102
+ // Orientation (from list-manager)
103
+ ORIENTATION: {
104
+ DEFAULT_ORIENTATION: "vertical",
105
+ DEFAULT_CROSS_AXIS_ALIGNMENT: "stretch",
106
+ REVERSE_DIRECTION: false,
107
+ },
108
+
109
+ PAGINATION: {
110
+ DEFAULT_STRATEGY: "offset" as "offset" | "page" | "cursor",
111
+ DEFAULT_LIMIT: 20,
112
+ STRATEGIES: {
113
+ PAGE: "page" as const,
114
+ OFFSET: "offset" as const,
115
+ CURSOR: "cursor" as const,
116
+ },
117
+ CURSOR_CLEANUP_INTERVAL: 60000, // Clean up old cursors every minute
118
+ MAX_CURSOR_MAP_SIZE: 1000, // Maximum number of cursors to keep in memory
119
+ // Cursor-specific virtual sizing
120
+ CURSOR_SCROLL_MARGIN_MULTIPLIER: 3, // Multiply rangeSize by this for scroll margin
121
+ CURSOR_MIN_VIRTUAL_SIZE_MULTIPLIER: 3, // Minimum virtual size as multiplier of rangeSize
122
+ },
123
+ };
124
+
125
+ /**
126
+ * Type for overriding constants at runtime
127
+ */
128
+ export type ViewportConstants = typeof VIEWPORT_CONSTANTS;
129
+
130
+ /**
131
+ * Helper function to merge user constants with defaults
132
+ */
133
+ export function mergeConstants(
134
+ userConstants: Partial<ViewportConstants> = {}
135
+ ): ViewportConstants {
136
+ return {
137
+ ...VIEWPORT_CONSTANTS,
138
+ ...userConstants,
139
+ };
140
+ }
@@ -0,0 +1,73 @@
1
+ // src/core/viewport/features/base.ts
2
+
3
+ /**
4
+ * Base Viewport Feature - Sets up core viewport structure
5
+ * Provides the foundation for other viewport features
6
+ */
7
+
8
+ import type { ViewportContext, ViewportComponent } from "../types";
9
+ import { wrapInitialize, getViewportState } from "./utils";
10
+
11
+ export interface BaseConfig {
12
+ className?: string;
13
+ orientation?: "vertical" | "horizontal";
14
+ }
15
+
16
+ /**
17
+ * Base feature for viewport
18
+ * Sets up the DOM structure and core styles
19
+ */
20
+ export const withBase = (config: BaseConfig = {}) => {
21
+ return <T extends ViewportContext & ViewportComponent>(component: T): T => {
22
+ const { className = "mtrl-viewport", orientation = "vertical" } = config;
23
+
24
+ // Use the shared wrapper utility
25
+ wrapInitialize(component, () => {
26
+ const element = component.element;
27
+ if (!element) return;
28
+
29
+ // Create viewport structure
30
+ let viewportElement = element.querySelector(
31
+ ".mtrl-viewport"
32
+ ) as HTMLElement;
33
+
34
+ if (!viewportElement) {
35
+ viewportElement = document.createElement("div");
36
+ viewportElement.className = className;
37
+ viewportElement.style.cssText = `
38
+ position: relative;
39
+ width: 100%;
40
+ height: 100%;
41
+ overflow: hidden;
42
+ `;
43
+
44
+ // Set orientation
45
+ viewportElement.setAttribute("data-orientation", orientation);
46
+
47
+ // Create items container
48
+ const itemsContainer = document.createElement("div");
49
+ itemsContainer.className = "mtrl-viewport-items";
50
+ itemsContainer.style.cssText = `
51
+ position: relative;
52
+ width: 100%;
53
+ `;
54
+
55
+ viewportElement.appendChild(itemsContainer);
56
+ element.appendChild(viewportElement);
57
+
58
+ // Update viewport state with containers using utility
59
+ const state = getViewportState(component);
60
+ if (state) {
61
+ state.viewportElement = viewportElement;
62
+ state.itemsContainer = itemsContainer;
63
+ }
64
+
65
+ // Store references on component
66
+ (component as any).viewportElement = viewportElement;
67
+ (component as any).itemsContainer = itemsContainer;
68
+ }
69
+ });
70
+
71
+ return component;
72
+ };
73
+ };