fh-matui 0.9.9__py3-none-any.whl → 0.9.10__py3-none-any.whl

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.
fh_matui/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.9.8"
1
+ __version__ = "0.9.9"
fh_matui/components.py CHANGED
@@ -331,9 +331,14 @@ def NavBar(*children, brand=None, sticky=False, cls='', **kwargs):
331
331
  return Nav(*children, cls=f"padding {nav_cls}", **kwargs)
332
332
 
333
333
  # %% ../nbs/02_components.ipynb 35
334
- def Modal(*c, id=None, footer=None, active=False, overlay=True, cls=(), **kwargs):
335
- """BeerCSS modal dialog with optional overlay and footer."""
334
+ def Modal(*c, id=None, footer=None, active=False, overlay='default', position=None, cls=(), **kwargs):
335
+ """BeerCSS modal dialog with position and overlay options."""
336
336
  modal_cls = normalize_tokens(cls)
337
+
338
+ # Add position class if specified
339
+ if position in ['left', 'right', 'top', 'bottom']:
340
+ modal_cls.append(position)
341
+
337
342
  if active:
338
343
  modal_cls.append('active')
339
344
 
@@ -349,9 +354,22 @@ def Modal(*c, id=None, footer=None, active=False, overlay=True, cls=(), **kwargs
349
354
  # Create the dialog
350
355
  dialog = Dialog(*children, id=id, cls=cls_str, **kwargs)
351
356
 
352
- if overlay:
353
- # Return overlay + dialog as separate elements that BeerCSS can manage
354
- overlay_cls = "overlay blur"
357
+ # Handle overlay
358
+ if overlay and overlay not in [False, None]:
359
+ overlay_classes = ['overlay']
360
+
361
+ # Map overlay parameter to BeerCSS classes
362
+ if overlay == 'blur':
363
+ overlay_classes.append('blur')
364
+ elif overlay == 'small-blur':
365
+ overlay_classes.append('small-blur')
366
+ elif overlay == 'medium-blur':
367
+ overlay_classes.append('medium-blur')
368
+ elif overlay == 'large-blur':
369
+ overlay_classes.append('large-blur')
370
+ # 'default' means plain overlay with no blur class
371
+
372
+ overlay_cls = ' '.join(overlay_classes)
355
373
  if active:
356
374
  overlay_cls += " active"
357
375
 
@@ -717,15 +735,14 @@ def Toolbar(*items, cls='', elevate='large', fill=True, **kwargs):
717
735
  if cls: classes.append(cls)
718
736
  return Nav(*items, cls=' '.join(classes), **kwargs)
719
737
 
720
- # %% ../nbs/02_components.ipynb 92
738
+ # %% ../nbs/02_components.ipynb 93
721
739
  #| code-fold: true
722
- def Toast(*c, cls='', position='top', variant='', action=None, dur=5.0, active=False, **kwargs):
723
- """BeerCSS snackbar/toast notification with position and variant options."""
740
+ def Toast(*c, cls='', position='top', variant='', action=None, active=False, dur=None, **kwargs):
741
+ """BeerCSS snackbar/toast notification with position ('top' or 'bottom') and variant options."""
724
742
  classes = ['snackbar']
725
743
  if variant: classes.append(variant)
726
- if position:
727
- position_map = {'top': 'bottom', 'bottom': 'top', 'left': 'right', 'right': 'left'}
728
- classes.append(position_map.get(position, position))
744
+ if position in ['top', 'bottom']:
745
+ classes.append(position)
729
746
  if active: classes.append('active')
730
747
  if cls: classes.append(cls)
731
748
 
@@ -735,13 +752,34 @@ def Toast(*c, cls='', position='top', variant='', action=None, dur=5.0, active=F
735
752
  if isinstance(action, str): content.append(A(action, cls='inverse-link'))
736
753
  else: content.append(action)
737
754
  else: content.extend(c)
738
- return Div(*content, cls=' '.join(classes), **kwargs)
755
+
756
+ snackbar = Div(*content, cls=' '.join(classes), **kwargs)
757
+
758
+ # If duration is specified and there's an id, generate auto-hide script
759
+ if dur and 'id' in kwargs:
760
+ timeout_ms = int(dur * 1000)
761
+ toast_id = kwargs['id']
762
+ # Wait for Beer CSS module to load before calling ui()
763
+ script = Script(f"""
764
+ window.addEventListener('load', function() {{
765
+ function showToast() {{
766
+ if (typeof ui !== 'undefined') {{
767
+ try {{ ui('#{toast_id}', {timeout_ms}); }}
768
+ catch (e) {{ console.error('Toast error:', e.message); }}
769
+ }}
770
+ }}
771
+ setTimeout(showToast, 50);
772
+ }});
773
+ """)
774
+ return (snackbar, script)
775
+
776
+ return snackbar
739
777
 
740
778
  def Snackbar(*c, **kwargs):
741
779
  """Alias for Toast component."""
742
780
  return Toast(*c, **kwargs)
743
781
 
744
- # %% ../nbs/02_components.ipynb 94
782
+ # %% ../nbs/02_components.ipynb 96
745
783
  #| code-fold: true
746
784
  class ContainerT(VEnum):
747
785
  """Container size options (BeerCSS). Most alias to 'responsive'; use 'expand' for full-width."""
@@ -752,7 +790,7 @@ class ContainerT(VEnum):
752
790
  xl = 'responsive'
753
791
  expand = 'responsive max'
754
792
 
755
- # %% ../nbs/02_components.ipynb 96
793
+ # %% ../nbs/02_components.ipynb 98
756
794
  #| code-fold: true
757
795
  def _get_form_config(col: dict) -> dict:
758
796
  """Extract form config from column, with sensible defaults."""
@@ -828,7 +866,7 @@ def FormField(
828
866
  **attrs
829
867
  )
830
868
 
831
- # %% ../nbs/02_components.ipynb 98
869
+ # %% ../nbs/02_components.ipynb 100
832
870
  #| code-fold: true
833
871
  from typing import Callable, Any
834
872
 
@@ -947,7 +985,7 @@ def FormModal(
947
985
  cls="large-width"
948
986
  )
949
987
 
950
- # %% ../nbs/02_components.ipynb 100
988
+ # %% ../nbs/02_components.ipynb 102
951
989
  #| code-fold: true
952
990
  def NavContainer(*li, title=None, brand=None, position='left', close_button=True, cls='active', id=None, **kwargs):
953
991
  """Slide-out navigation drawer with header and close button."""
@@ -1004,7 +1042,7 @@ def BottomNav(*c, cls='bottom', size='s', **kwargs):
1004
1042
  final_cls = f"{cls} {size_cls}".strip()
1005
1043
  return Nav(*c, cls=final_cls, **kwargs)
1006
1044
 
1007
- # %% ../nbs/02_components.ipynb 103
1045
+ # %% ../nbs/02_components.ipynb 105
1008
1046
  #| code-fold: true
1009
1047
  def NavSideBarHeader(*c, cls='', **kwargs):
1010
1048
  """Sidebar header section for menu buttons and branding."""
@@ -1024,7 +1062,7 @@ def NavSideBarContainer(*children, position='left', size='m', cls='', active=Fal
1024
1062
  nav_cls = f"{base_cls} {cls}".strip()
1025
1063
  return Nav(*children, cls=nav_cls, **kwargs)
1026
1064
 
1027
- # %% ../nbs/02_components.ipynb 105
1065
+ # %% ../nbs/02_components.ipynb 107
1028
1066
  #| code-fold: true
1029
1067
  def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_size=ContainerT.expand,
1030
1068
  main_bg='surface', sidebar_id='app-sidebar', cls='', **kwargs):
@@ -1069,7 +1107,7 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
1069
1107
  final_cls = f"surface-container {cls}".strip() if cls else "surface-container"
1070
1108
  return Div(*layout_children, cls=final_cls, **kwargs)
1071
1109
 
1072
- # %% ../nbs/02_components.ipynb 109
1110
+ # %% ../nbs/02_components.ipynb 111
1073
1111
  #| code-fold: true
1074
1112
  class TextT(VEnum):
1075
1113
  """Text styles using BeerCSS typography classes."""
@@ -1101,7 +1139,7 @@ class TextPresets(VEnum):
1101
1139
  primary_link = 'link primary-text'
1102
1140
  muted_link = 'link secondary-text'
1103
1141
 
1104
- # %% ../nbs/02_components.ipynb 110
1142
+ # %% ../nbs/02_components.ipynb 112
1105
1143
  #| code-fold: true
1106
1144
  def CodeSpan(*c, cls=(), **kwargs):
1107
1145
  """Inline code snippet."""
@@ -1157,7 +1195,7 @@ def Sup(*c, cls=(), **kwargs):
1157
1195
  cls_str = stringify(cls) if cls else None
1158
1196
  return fc.Sup(*c, cls=cls_str, **kwargs) if cls_str else fc.Sup(*c, **kwargs)
1159
1197
 
1160
- # %% ../nbs/02_components.ipynb 112
1198
+ # %% ../nbs/02_components.ipynb 114
1161
1199
  #| code-fold: true
1162
1200
  def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str = ''):
1163
1201
  """Collapsible FAQ item using details/summary.
@@ -1175,7 +1213,7 @@ def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str
1175
1213
  Summary(Article(Nav(Div(question, cls=f"max bold {question_cls}".strip()), I("expand_more")), cls="round surface-variant border no-elevate")),
1176
1214
  Article(P(answer, cls=f"secondary-text {answer_cls}".strip()), cls="round border padding"))
1177
1215
 
1178
- # %% ../nbs/02_components.ipynb 116
1216
+ # %% ../nbs/02_components.ipynb 118
1179
1217
  #| code-fold: true
1180
1218
  def CookiesBanner(message='We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.',
1181
1219
  accept_text='Accept', decline_text='Decline', settings_text=None, policy_link='/cookies', policy_text='Learn more',
fh_matui/datatable.py CHANGED
@@ -123,6 +123,7 @@ def _action_menu(
123
123
  row: dict,
124
124
  row_id: Any,
125
125
  crud_ops: dict,
126
+ crud_enabled: dict,
126
127
  base_route: str,
127
128
  search: str,
128
129
  page: int,
@@ -153,14 +154,14 @@ def _action_menu(
153
154
  )
154
155
 
155
156
  items = []
156
- # View is conditional (read operation)
157
- if crud_ops.get("view", True):
157
+ # View is conditional (read operation) - use crud_enabled for callable support
158
+ if crud_enabled.get("view", True):
158
159
  items.append(action_item("View", "visibility", "view"))
159
160
 
160
- if crud_ops.get("update", False):
161
+ if crud_enabled.get("update", False):
161
162
  items.append(action_item("Edit", "edit", "edit"))
162
163
 
163
- if crud_ops.get("delete", False):
164
+ if crud_enabled.get("delete", False):
164
165
  items.append(action_item("Delete", "delete", "delete", confirm="Delete this record?"))
165
166
 
166
167
  # Custom actions
@@ -197,6 +198,7 @@ def DataTable(
197
198
  search: str = '',
198
199
  columns: list[dict] = None,
199
200
  crud_ops: dict = None,
201
+ crud_enabled: dict = None,
200
202
  base_route: str = '',
201
203
  row_id_field: str = 'id',
202
204
  title: str = 'Records',
@@ -209,6 +211,7 @@ def DataTable(
209
211
  "Generic data table with server-side pagination, search, and row actions."
210
212
  # Defaults
211
213
  crud_ops = crud_ops or {"create": False, "update": False, "delete": False}
214
+ crud_enabled = crud_enabled or {k: bool(v) for k, v in crud_ops.items() if k != 'custom_actions'}
212
215
  page_sizes = page_sizes or PAGE_SIZES
213
216
  container_id = container_id or f"crud-table-{base_route.replace('/', '-').strip('-')}"
214
217
  feedback_id = f"{container_id}-feedback"
@@ -227,7 +230,6 @@ def DataTable(
227
230
  start_index = (page - 1) * page_size + 1 if total else 0
228
231
  end_index = min(start_index + page_size - 1, total) if total else 0
229
232
  summary = f"{start_index}-{end_index} of {total} records" if total else "No matching records"
230
-
231
233
  base_query = urlencode({"search": search, "page_size": page_size})
232
234
 
233
235
  # Build table header keys and labels
@@ -255,6 +257,7 @@ def DataTable(
255
257
  row=row,
256
258
  row_id=row_id,
257
259
  crud_ops=crud_ops,
260
+ crud_enabled=crud_enabled,
258
261
  base_route=base_route,
259
262
  search=search,
260
263
  page=page,
@@ -264,9 +267,9 @@ def DataTable(
264
267
  )
265
268
  table_rows.append(row_dict)
266
269
 
267
- # Create button
270
+ # Create button - use crud_enabled for callable support
268
271
  create_button = None
269
- if crud_ops.get("create", False):
272
+ if crud_enabled.get("create", False):
270
273
  query = urlencode({"action": "create", "search": search, "page": page, "page_size": page_size})
271
274
  create_button = Button(
272
275
  Icon("add"),
@@ -335,6 +338,24 @@ def DataTable(
335
338
  cls="border"
336
339
  ) if data else Div(empty_message, cls="center-align padding")
337
340
 
341
+ # Footer section with page size selector and pagination
342
+ footer = None
343
+ if total > 0:
344
+ page_info_row = Div(
345
+ _page_size_select(page_size, search, base_route, container_id, page_sizes),
346
+ Span(summary, cls="small-text grey-text"),
347
+ cls="row"
348
+ )
349
+ if pagination:
350
+ # Two-row layout: info on top, centered pagination below
351
+ footer = Div(
352
+ page_info_row,
353
+ Div(pagination, cls="row center-align"),
354
+ )
355
+ else:
356
+ # Single row when no pagination needed
357
+ footer = page_info_row
358
+
338
359
  return Article(
339
360
  Div(id=feedback_id), # Feedback container for modals/toasts
340
361
  Div(
@@ -343,15 +364,7 @@ def DataTable(
343
364
  cls="row"
344
365
  ),
345
366
  data_table,
346
- Div(
347
- Div(
348
- _page_size_select(page_size, search, base_route, container_id, page_sizes),
349
- Span(summary, cls="small-text grey-text"),
350
- cls="row"
351
- ),
352
- pagination,
353
- cls="grid"
354
- ) if total > 0 else None,
367
+ footer,
355
368
  id=container_id,
356
369
  hx_trigger=f"{container_id}-refresh from:body",
357
370
  hx_get=f"{base_route}?{base_query}",
@@ -453,6 +466,7 @@ class CrudContext:
453
466
  tbl: Optional[Any] = None # request.state.tables[table_name] (if available)
454
467
  record: dict = None # Form data dict
455
468
  record_id: Optional[Any] = None # ID for update/delete (None for create)
469
+ feedback_id: Optional[str] = None # Target div ID for HTMX swap (for override handlers)
456
470
 
457
471
  # %% ../nbs/05_datatable.ipynb 13
458
472
  from typing import Callable, Optional, Any, Union
@@ -574,6 +588,20 @@ class DataTableResource:
574
588
  if not callable(action["handler"]):
575
589
  raise ValueError(f"Custom action '{action['name']}' handler must be callable")
576
590
 
591
+ # Parse crud_ops for callable overrides vs boolean enable/disable
592
+ # Callable = override handler (action is enabled)
593
+ # True = use default handler (action is enabled)
594
+ # False = action is disabled
595
+ self.crud_overrides = {}
596
+ self.crud_enabled = {}
597
+ for action_name in ['create', 'update', 'delete', 'view']:
598
+ value = self.crud_ops.get(action_name, action_name == 'view') # view defaults to True
599
+ if callable(value):
600
+ self.crud_overrides[action_name] = value
601
+ self.crud_enabled[action_name] = True # Callable means action is enabled
602
+ else:
603
+ self.crud_enabled[action_name] = bool(value)
604
+
577
605
  # CRUD hooks
578
606
  self.on_create_hook = on_create
579
607
  self.on_update_hook = on_update
@@ -603,7 +631,7 @@ class DataTableResource:
603
631
  return await hook(ctx)
604
632
  return hook(ctx)
605
633
 
606
- def _build_context(self, req, record: dict = None, record_id: Any = None) -> CrudContext:
634
+ def _build_context(self, req, record: dict = None, record_id: Any = None, include_feedback_id: bool = False) -> CrudContext:
607
635
  """🏗️ Build CrudContext from request."""
608
636
  user = None
609
637
  db = None
@@ -629,7 +657,8 @@ class DataTableResource:
629
657
  db=db,
630
658
  tbl=tbl,
631
659
  record=record or {},
632
- record_id=record_id
660
+ record_id=record_id,
661
+ feedback_id=self.feedback_id if include_feedback_id else None
633
662
  )
634
663
 
635
664
  def _wrap_response(self, content, req):
@@ -647,8 +676,8 @@ class DataTableResource:
647
676
  return self._handle_table(req)
648
677
 
649
678
  @rt(f"{self.base_route}/action")
650
- def _action_handler(req):
651
- return self._handle_action(req)
679
+ async def _action_handler(req):
680
+ return await self._handle_action(req)
652
681
 
653
682
  @rt(f"{self.base_route}/save")
654
683
  async def _save_handler(req):
@@ -704,6 +733,7 @@ class DataTableResource:
704
733
  search=search,
705
734
  columns=self.columns,
706
735
  crud_ops=self.crud_ops,
736
+ crud_enabled=self.crud_enabled,
707
737
  base_route=self.base_route,
708
738
  row_id_field=self.row_id_field,
709
739
  title=self.title,
@@ -731,8 +761,8 @@ class DataTableResource:
731
761
  # Wrap with layout if full-page request
732
762
  return self._wrap_response(content, req)
733
763
 
734
- def _handle_action(self, req):
735
- """Handle action route (view/edit/create/delete)."""
764
+ async def _handle_action(self, req):
765
+ """Handle action route (view/edit/create/delete) with override support."""
736
766
  params = getattr(req, "query_params", {})
737
767
  getter = params.get if hasattr(params, "get") else (lambda k, d=None: params[k] if k in params else d)
738
768
 
@@ -755,9 +785,47 @@ class DataTableResource:
755
785
  cancel_url = f"{self.base_route}/action?dismiss=1"
756
786
  save_url = f"{self.base_route}/save?{return_params}"
757
787
 
758
- # Handle CREATE
788
+ # Get record for actions that need it (not create)
789
+ record = None
790
+ if record_id:
791
+ raw_record = self.get_by_id(req, record_id)
792
+ record = _to_dict(raw_record) if raw_record else None
793
+
794
+ # Map action names to internal action names for overrides
795
+ action_map = {'edit': 'update'} # 'edit' action uses 'update' override
796
+ override_key = action_map.get(action, action)
797
+
798
+ # Check for CRUD override FIRST (callable in crud_ops)
799
+ if override_key in self.crud_overrides:
800
+ try:
801
+ ctx = self._build_context(req, record=record, record_id=record_id, include_feedback_id=True)
802
+ handler = self.crud_overrides[override_key]
803
+
804
+ # Execute handler (sync or async)
805
+ if asyncio.iscoroutinefunction(handler):
806
+ result = await handler(ctx)
807
+ else:
808
+ result = handler(ctx)
809
+
810
+ # Handle return value:
811
+ # - FT component: Return directly (full control to user)
812
+ # - str: Wrap in success toast
813
+ # - None: Default success message
814
+ if result is None:
815
+ return self._success_toast(f"{action.title()} completed successfully.")
816
+ elif isinstance(result, str):
817
+ return self._success_toast(result)
818
+ else:
819
+ # FT component - wrap in feedback div
820
+ return Div(result, id=self.feedback_id)
821
+
822
+ except Exception as e:
823
+ logger.error(f"CRUD override '{action}' failed: {e}", exc_info=True)
824
+ return self._error_toast(f"{action.title()} failed: {str(e)}")
825
+
826
+ # Handle CREATE (default behavior)
759
827
  if action == "create":
760
- if not self.crud_ops.get("create"):
828
+ if not self.crud_enabled.get("create"):
761
829
  return self._error_toast("Create operation not enabled.")
762
830
 
763
831
  modal = FormModal(
@@ -773,16 +841,11 @@ class DataTableResource:
773
841
  )
774
842
  return self._wrap_modal(modal)
775
843
 
776
- # Get record for view/edit/delete
777
- record = None
778
- if record_id:
779
- raw_record = self.get_by_id(req, record_id)
780
- record = _to_dict(raw_record) if raw_record else None
781
-
844
+ # Record required for remaining actions
782
845
  if not record:
783
846
  return self._error_toast("Record not found.")
784
847
 
785
- # Handle VIEW
848
+ # Handle VIEW (default behavior)
786
849
  if action == "view":
787
850
  modal = FormModal(
788
851
  columns=self.columns,
@@ -795,9 +858,9 @@ class DataTableResource:
795
858
  )
796
859
  return self._wrap_modal(modal)
797
860
 
798
- # Handle EDIT
861
+ # Handle EDIT (default behavior)
799
862
  if action == "edit":
800
- if not self.crud_ops.get("update"):
863
+ if not self.crud_enabled.get("update"):
801
864
  return self._error_toast("Update operation not enabled.")
802
865
 
803
866
  modal = FormModal(
@@ -813,9 +876,9 @@ class DataTableResource:
813
876
  )
814
877
  return self._wrap_modal(modal)
815
878
 
816
- # Handle DELETE
879
+ # Handle DELETE (default behavior)
817
880
  if action == "delete":
818
- if not self.crud_ops.get("delete"):
881
+ if not self.crud_enabled.get("delete"):
819
882
  return self._error_toast("Delete operation not enabled.")
820
883
 
821
884
  try:
@@ -823,11 +886,7 @@ class DataTableResource:
823
886
 
824
887
  # Use on_delete hook if provided
825
888
  if self.on_delete_hook:
826
- if asyncio.iscoroutinefunction(self.on_delete_hook):
827
- loop = asyncio.get_event_loop()
828
- loop.run_until_complete(self.on_delete_hook(ctx))
829
- else:
830
- self.on_delete_hook(ctx)
889
+ await self._call_hook(self.on_delete_hook, ctx)
831
890
  else:
832
891
  # Default: use delete_fn
833
892
  self.delete_fn(req, record_id)
@@ -842,19 +901,22 @@ class DataTableResource:
842
901
  for custom_action in custom_actions:
843
902
  if action == custom_action["name"]:
844
903
  try:
845
- ctx = self._build_context(req, record=record, record_id=record_id)
904
+ ctx = self._build_context(req, record=record, record_id=record_id, include_feedback_id=True)
846
905
  handler = custom_action["handler"]
847
906
 
848
907
  # Execute handler (sync or async)
849
908
  if asyncio.iscoroutinefunction(handler):
850
- loop = asyncio.get_event_loop()
851
- result = loop.run_until_complete(handler(ctx))
909
+ result = await handler(ctx)
852
910
  else:
853
911
  result = handler(ctx)
854
912
 
855
- # Return success toast with refresh trigger
856
- message = result if isinstance(result, str) else f"{custom_action['label']} completed successfully."
857
- return self._success_toast(message)
913
+ # Handle return value same as overrides
914
+ if result is None:
915
+ return self._success_toast(f"{custom_action['label']} completed successfully.")
916
+ elif isinstance(result, str):
917
+ return self._success_toast(result)
918
+ else:
919
+ return Div(result, id=self.feedback_id)
858
920
 
859
921
  except Exception as e:
860
922
  logger.error(f"Custom action '{action}' failed: {e}", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fh-matui
3
- Version: 0.9.9
3
+ Version: 0.9.10
4
4
  Summary: material-ui for fasthtml
5
5
  Home-page: https://github.com/abhisheksreesaila/fh-matui
6
6
  Author: abhishek sreesaila
@@ -0,0 +1,14 @@
1
+ fh_matui/__init__.py,sha256=u_KIWOO-wJql3tmQezwk81ys0s3hKSCZ705RpLSz6vI,23
2
+ fh_matui/_modidx.py,sha256=naHwPQ4kCo-5saE_uozmdPA881kp1gnqvKhmaG-Ya-4,23914
3
+ fh_matui/app_pages.py,sha256=Sn9-tgBpaPNbR-0nZtPLoSCmAWLOGB4UQ88IkFvzBRY,10361
4
+ fh_matui/components.py,sha256=Q-koxO-BCxG9Dse2QUpP5WrGSrbDyzBSLoqIGatUAg8,49503
5
+ fh_matui/core.py,sha256=xtVBN8CtC50ZJ4Iu7o-mUhaA87tWdnz8gBfKRk63Zhs,10680
6
+ fh_matui/datatable.py,sha256=vtkk7D8GrlDFCHilKxLi8SrncqxPW5n-ErrCJcI7OS4,39875
7
+ fh_matui/foundations.py,sha256=b7PnObJpKN8ZAU9NzCm9xpfnHzFjjAROU7E2YvA_tj4,1820
8
+ fh_matui/web_pages.py,sha256=4mF-jpfVcZTVepfQ-aMGgIUp-nBp0YCkvcdsWhUYeaA,34879
9
+ fh_matui-0.9.10.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
+ fh_matui-0.9.10.dist-info/METADATA,sha256=Cpydwe7SXoRz7urHUG1fxM70zskIs9F_cvBGdoB76_c,10491
11
+ fh_matui-0.9.10.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
12
+ fh_matui-0.9.10.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
+ fh_matui-0.9.10.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
+ fh_matui-0.9.10.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- fh_matui/__init__.py,sha256=4eQCGJV2-GRHX8FlvA_4etK0E4LFj2or592J4aePZoA,23
2
- fh_matui/_modidx.py,sha256=naHwPQ4kCo-5saE_uozmdPA881kp1gnqvKhmaG-Ya-4,23914
3
- fh_matui/app_pages.py,sha256=Sn9-tgBpaPNbR-0nZtPLoSCmAWLOGB4UQ88IkFvzBRY,10361
4
- fh_matui/components.py,sha256=KjdTHzWRXpVWBEIGskW1HfhjPpzRYzi6UA_yRjZyMWM,48254
5
- fh_matui/core.py,sha256=xtVBN8CtC50ZJ4Iu7o-mUhaA87tWdnz8gBfKRk63Zhs,10680
6
- fh_matui/datatable.py,sha256=NNb6WvGZ0QY13md4itwYH4a9yKZrOdwcaDW7PGo-vQM,36558
7
- fh_matui/foundations.py,sha256=b7PnObJpKN8ZAU9NzCm9xpfnHzFjjAROU7E2YvA_tj4,1820
8
- fh_matui/web_pages.py,sha256=4mF-jpfVcZTVepfQ-aMGgIUp-nBp0YCkvcdsWhUYeaA,34879
9
- fh_matui-0.9.9.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
- fh_matui-0.9.9.dist-info/METADATA,sha256=OGoxCFS3_qRnEJe2EkD-dMDl21lhYt5ecds1B6f5xpU,10490
11
- fh_matui-0.9.9.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
12
- fh_matui-0.9.9.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
- fh_matui-0.9.9.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
- fh_matui-0.9.9.dist-info/RECORD,,