mithril-materialized 3.4.5 → 3.5.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.
package/dist/index.esm.js CHANGED
@@ -963,6 +963,18 @@ const iconPaths = {
963
963
  'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
964
964
  'M0 0h24v24H0z', // background
965
965
  ],
966
+ chevron_left: [
967
+ 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z', // chevron left
968
+ 'M0 0h24v24H0z', // background
969
+ ],
970
+ chevron_right: [
971
+ 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z', // chevron right
972
+ 'M0 0h24v24H0z', // background
973
+ ],
974
+ menu: [
975
+ 'M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z', // hamburger menu
976
+ 'M0 0h24v24H0z', // background
977
+ ],
966
978
  expand: [
967
979
  'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
968
980
  'M0 0h24v24H0z', // background
@@ -971,6 +983,18 @@ const iconPaths = {
971
983
  'M19 13H5v-2h14v2z', // minus
972
984
  'M0 0h24v24H0z', // background
973
985
  ],
986
+ check: [
987
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z', // checkmark
988
+ 'M0 0h24v24H0z', // background
989
+ ],
990
+ radio_checked: [
991
+ 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button checked
992
+ 'M0 0h24v24H0z', // background
993
+ ],
994
+ radio_unchecked: [
995
+ 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button unchecked
996
+ 'M0 0h24v24H0z', // background
997
+ ],
974
998
  light_mode: [
975
999
  'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5M2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1m18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1M11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1m0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1M5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41zm12.4 12.4a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41zm1.06-11a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0zM7.05 18.4a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0z',
976
1000
  'M0 0h24v24H0z', // background
@@ -7797,12 +7821,27 @@ const Sidenav = () => {
7797
7821
  document.body.style.overflow = isOpen && mode === 'overlay' ? 'hidden' : '';
7798
7822
  }
7799
7823
  };
7824
+ const toggleExpanded = (attrs) => {
7825
+ const newExpandedState = !(attrs.isExpanded !== false);
7826
+ if (attrs.onExpandChange) {
7827
+ attrs.onExpandChange(newExpandedState);
7828
+ }
7829
+ };
7830
+ const toggleHamburger = (attrs) => {
7831
+ const newOpenState = !state.isOpen;
7832
+ if (attrs.onToggle) {
7833
+ attrs.onToggle(newOpenState);
7834
+ }
7835
+ };
7800
7836
  return {
7801
7837
  oninit: ({ attrs }) => {
7802
7838
  state = {
7803
7839
  id: attrs.id || uniqueId(),
7804
7840
  isOpen: attrs.isOpen || false,
7805
7841
  isAnimating: false,
7842
+ isExpanded: attrs.isExpanded !== false,
7843
+ activeItemIndex: null,
7844
+ selectedSubmenuItems: new Map(),
7806
7845
  };
7807
7846
  // Set up keyboard listener
7808
7847
  if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
@@ -7831,12 +7870,16 @@ const Sidenav = () => {
7831
7870
  }
7832
7871
  },
7833
7872
  view: ({ attrs, children }) => {
7834
- const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
7873
+ const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, showHamburger = false, expandable = false, } = attrs;
7835
7874
  const isOpen = state.isOpen;
7875
+ const collapsedWidth = 60;
7876
+ const isExpanded = attrs.isExpanded !== false;
7877
+ const currentWidth = expandable && !isExpanded ? collapsedWidth : width;
7836
7878
  return [
7837
- // Backdrop (using existing materialize class)
7879
+ // Backdrop (using existing materialize class) - only for overlay mode
7838
7880
  showBackdrop &&
7839
7881
  mode === 'overlay' &&
7882
+ !fixed &&
7840
7883
  m('.sidenav-overlay', {
7841
7884
  style: {
7842
7885
  display: isOpen ? 'block' : 'none',
@@ -7847,49 +7890,206 @@ const Sidenav = () => {
7847
7890
  // Sidenav (using existing materialize structure)
7848
7891
  m('ul.sidenav', {
7849
7892
  id: state.id,
7850
- class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
7893
+ class: [
7894
+ position === 'right' ? 'right-aligned' : '',
7895
+ fixed ? 'sidenav-fixed' : '',
7896
+ expandable && !isExpanded ? 'sidenav-collapsed' : '',
7897
+ className,
7898
+ ]
7851
7899
  .filter(Boolean)
7852
7900
  .join(' ') || undefined,
7853
7901
  style: {
7854
- width: `${width}px`,
7902
+ width: `${currentWidth}px`,
7855
7903
  transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
7856
7904
  'transition-duration': `${animationDuration}ms`,
7905
+ 'transition-property': 'transform, width',
7857
7906
  },
7858
- }, children),
7907
+ }, [
7908
+ // Hamburger toggle button (inside sidenav, at the top)
7909
+ showHamburger &&
7910
+ m('li.sidenav-hamburger-item', {
7911
+ style: {
7912
+ display: 'flex',
7913
+ 'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
7914
+ 'align-items': 'center',
7915
+ padding: '12px 16px',
7916
+ cursor: 'pointer',
7917
+ 'border-bottom': '1px solid rgba(0,0,0,0.1)',
7918
+ },
7919
+ onclick: () => toggleHamburger(attrs),
7920
+ }, m(MaterialIcon, {
7921
+ name: 'menu',
7922
+ style: { width: '24px', height: '24px' },
7923
+ })),
7924
+ // Expand/collapse toggle button (if expandable, right below hamburger)
7925
+ expandable &&
7926
+ m('li.sidenav-expand-toggle', {
7927
+ style: {
7928
+ display: 'flex',
7929
+ 'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
7930
+ 'align-items': 'center',
7931
+ padding: '12px 16px',
7932
+ cursor: 'pointer',
7933
+ 'border-bottom': '1px solid rgba(0,0,0,0.1)',
7934
+ },
7935
+ onclick: () => toggleExpanded(attrs),
7936
+ }, m(MaterialIcon, {
7937
+ name: position === 'right'
7938
+ ? (isExpanded ? 'chevron_right' : 'chevron_left')
7939
+ : (isExpanded ? 'chevron_left' : 'chevron_right'),
7940
+ style: { width: '24px', height: '24px' },
7941
+ })),
7942
+ // Children (menu items) - inject internal props
7943
+ Array.isArray(children)
7944
+ ? children.map((child) => {
7945
+ if (child && typeof child === 'object' && 'tag' in child) {
7946
+ // Clone the vnode and add internal props
7947
+ return Object.assign(Object.assign({}, child), { attrs: Object.assign(Object.assign({}, child.attrs), { _isExpanded: isExpanded, _position: position }) });
7948
+ }
7949
+ return child;
7950
+ })
7951
+ : children,
7952
+ ]),
7859
7953
  ];
7860
7954
  },
7861
7955
  };
7862
7956
  };
7957
+ /**
7958
+ * Sidenav Submenu Item Component
7959
+ */
7960
+ const NavbarSubItem = () => {
7961
+ return {
7962
+ view: ({ attrs }) => {
7963
+ const { text, icon, selected = false, value, onSelect, mode, isExpanded, position = 'left' } = attrs;
7964
+ const handleClick = () => {
7965
+ if (onSelect) {
7966
+ onSelect(value !== undefined ? value : text, !selected);
7967
+ }
7968
+ };
7969
+ const isRightAligned = position === 'right';
7970
+ const submenuContent = isRightAligned
7971
+ ? [
7972
+ // Right-aligned: text on left, icons on right
7973
+ isExpanded && m('span', { style: { 'flex': '1', 'text-align': 'left' } }, text),
7974
+ icon && isExpanded && m('i.material-icons', { style: { 'font-size': '18px' } }, icon),
7975
+ m(MaterialIcon, {
7976
+ name: mode === 'checkbox' ? (selected ? 'check' : 'close') : selected ? 'radio_checked' : 'radio_unchecked',
7977
+ style: {
7978
+ width: '18px',
7979
+ height: '18px',
7980
+ opacity: mode === 'checkbox' && !selected ? '0.3' : '1',
7981
+ },
7982
+ }),
7983
+ ]
7984
+ : [
7985
+ // Left-aligned: indicator on left, text and icon on right
7986
+ m(MaterialIcon, {
7987
+ name: mode === 'checkbox' ? (selected ? 'check' : 'close') : selected ? 'radio_checked' : 'radio_unchecked',
7988
+ style: {
7989
+ width: '18px',
7990
+ height: '18px',
7991
+ opacity: mode === 'checkbox' && !selected ? '0.3' : '1',
7992
+ },
7993
+ }),
7994
+ icon && isExpanded && m('i.material-icons', { style: { 'font-size': '18px', 'margin-left': '8px' } }, icon),
7995
+ isExpanded && m('span', { style: { 'margin-left': icon ? '8px' : '8px' } }, text),
7996
+ ];
7997
+ return m('li.sidenav-subitem', {
7998
+ class: selected ? 'selected' : '',
7999
+ style: {
8000
+ padding: isExpanded ? '8px 16px 8px 48px' : '8px 16px',
8001
+ cursor: 'pointer',
8002
+ display: 'flex',
8003
+ 'align-items': 'center',
8004
+ gap: '8px',
8005
+ 'font-size': '0.9em',
8006
+ 'justify-content': isRightAligned ? 'space-between' : 'flex-start',
8007
+ },
8008
+ onclick: handleClick,
8009
+ }, submenuContent);
8010
+ },
8011
+ };
8012
+ };
7863
8013
  /**
7864
8014
  * Sidenav Item Component
7865
8015
  * Individual items for the sidenav menu
7866
8016
  */
7867
8017
  const SidenavItem = () => {
8018
+ let isSubmenuOpen = false;
7868
8019
  return {
7869
8020
  view: ({ attrs, children }) => {
7870
- const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
8021
+ const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', } = attrs;
7871
8022
  if (divider) {
7872
8023
  return m('li.divider');
7873
8024
  }
7874
8025
  if (subheader) {
7875
8026
  return m('li.subheader', text || children);
7876
8027
  }
7877
- const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
7878
- const content = [icon && m('i.material-icons', icon), text || children];
7879
- if (href && !disabled) {
7880
- return m('li', { class: itemClasses }, [
8028
+ const hasSubmenu = submenu && submenu.length > 0;
8029
+ const itemClasses = [
8030
+ active ? 'active' : '',
8031
+ disabled ? 'disabled' : '',
8032
+ hasSubmenu ? 'has-submenu' : '',
8033
+ className,
8034
+ ]
8035
+ .filter(Boolean)
8036
+ .join(' ') || undefined;
8037
+ const handleMainClick = (e) => {
8038
+ e.preventDefault();
8039
+ if (hasSubmenu) {
8040
+ isSubmenuOpen = active ? !isSubmenuOpen : true;
8041
+ }
8042
+ if (onclick && !disabled) {
8043
+ onclick(e);
8044
+ }
8045
+ };
8046
+ // Get internal props passed from parent Sidenav
8047
+ const isExpanded = attrs._isExpanded !== false;
8048
+ const position = attrs._position || 'left';
8049
+ const isRightAligned = position === 'right';
8050
+ // In expanded mode, icons are at the outside edge
8051
+ // In collapsed mode, icons are centered
8052
+ const content = isRightAligned
8053
+ ? [
8054
+ // Right-aligned: text on left, icon on right
8055
+ isExpanded && m('span.sidenav-item-text', { style: { 'flex': '1', 'text-align': 'left', 'margin-right': '8px' } }, text || children),
8056
+ m('i.material-icons', { style: { 'min-width': '24px', 'width': '24px' } }, icon || ''),
8057
+ ]
8058
+ : [
8059
+ // Left-aligned: icon on left, text on right
8060
+ m('i.material-icons', { style: { 'min-width': '24px', 'width': '24px' } }, icon || ''),
8061
+ isExpanded && m('span.sidenav-item-text', { style: { 'margin-left': '8px', 'flex': '1' } }, text || children),
8062
+ ];
8063
+ const linkStyle = {
8064
+ display: 'flex',
8065
+ 'align-items': 'center',
8066
+ padding: isExpanded ? '12px 16px' : '12px 18px',
8067
+ 'justify-content': isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
8068
+ };
8069
+ const mainItem = href && !disabled
8070
+ ? m('li', { class: itemClasses }, [
7881
8071
  m('a', {
7882
8072
  href,
7883
- onclick: disabled ? undefined : onclick,
8073
+ onclick: handleMainClick,
8074
+ style: linkStyle,
8075
+ }, content),
8076
+ ])
8077
+ : m('li', { class: itemClasses }, [
8078
+ m('a', {
8079
+ onclick: handleMainClick,
8080
+ href: '#!',
8081
+ style: linkStyle,
7884
8082
  }, content),
7885
8083
  ]);
8084
+ // Return main item with submenu if applicable
8085
+ if (hasSubmenu && active && isSubmenuOpen) {
8086
+ return [
8087
+ mainItem,
8088
+ submenu.map((subItem) => m(NavbarSubItem, Object.assign(Object.assign({}, subItem), { mode: submenuMode, isExpanded,
8089
+ position }))),
8090
+ ];
7886
8091
  }
7887
- return m('li', { class: itemClasses }, [
7888
- m('a', {
7889
- onclick: disabled ? undefined : onclick,
7890
- href: '#!',
7891
- }, content),
7892
- ]);
8092
+ return mainItem;
7893
8093
  },
7894
8094
  };
7895
8095
  };
package/dist/index.js CHANGED
@@ -965,6 +965,18 @@ const iconPaths = {
965
965
  'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
966
966
  'M0 0h24v24H0z', // background
967
967
  ],
968
+ chevron_left: [
969
+ 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z', // chevron left
970
+ 'M0 0h24v24H0z', // background
971
+ ],
972
+ chevron_right: [
973
+ 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z', // chevron right
974
+ 'M0 0h24v24H0z', // background
975
+ ],
976
+ menu: [
977
+ 'M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z', // hamburger menu
978
+ 'M0 0h24v24H0z', // background
979
+ ],
968
980
  expand: [
969
981
  'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
970
982
  'M0 0h24v24H0z', // background
@@ -973,6 +985,18 @@ const iconPaths = {
973
985
  'M19 13H5v-2h14v2z', // minus
974
986
  'M0 0h24v24H0z', // background
975
987
  ],
988
+ check: [
989
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z', // checkmark
990
+ 'M0 0h24v24H0z', // background
991
+ ],
992
+ radio_checked: [
993
+ 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button checked
994
+ 'M0 0h24v24H0z', // background
995
+ ],
996
+ radio_unchecked: [
997
+ 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z', // radio button unchecked
998
+ 'M0 0h24v24H0z', // background
999
+ ],
976
1000
  light_mode: [
977
1001
  'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5M2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1m18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1M11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1m0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1M5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41zm12.4 12.4a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41zm1.06-11a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0zM7.05 18.4a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0z',
978
1002
  'M0 0h24v24H0z', // background
@@ -7799,12 +7823,27 @@ const Sidenav = () => {
7799
7823
  document.body.style.overflow = isOpen && mode === 'overlay' ? 'hidden' : '';
7800
7824
  }
7801
7825
  };
7826
+ const toggleExpanded = (attrs) => {
7827
+ const newExpandedState = !(attrs.isExpanded !== false);
7828
+ if (attrs.onExpandChange) {
7829
+ attrs.onExpandChange(newExpandedState);
7830
+ }
7831
+ };
7832
+ const toggleHamburger = (attrs) => {
7833
+ const newOpenState = !state.isOpen;
7834
+ if (attrs.onToggle) {
7835
+ attrs.onToggle(newOpenState);
7836
+ }
7837
+ };
7802
7838
  return {
7803
7839
  oninit: ({ attrs }) => {
7804
7840
  state = {
7805
7841
  id: attrs.id || uniqueId(),
7806
7842
  isOpen: attrs.isOpen || false,
7807
7843
  isAnimating: false,
7844
+ isExpanded: attrs.isExpanded !== false,
7845
+ activeItemIndex: null,
7846
+ selectedSubmenuItems: new Map(),
7808
7847
  };
7809
7848
  // Set up keyboard listener
7810
7849
  if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
@@ -7833,12 +7872,16 @@ const Sidenav = () => {
7833
7872
  }
7834
7873
  },
7835
7874
  view: ({ attrs, children }) => {
7836
- const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
7875
+ const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, showHamburger = false, expandable = false, } = attrs;
7837
7876
  const isOpen = state.isOpen;
7877
+ const collapsedWidth = 60;
7878
+ const isExpanded = attrs.isExpanded !== false;
7879
+ const currentWidth = expandable && !isExpanded ? collapsedWidth : width;
7838
7880
  return [
7839
- // Backdrop (using existing materialize class)
7881
+ // Backdrop (using existing materialize class) - only for overlay mode
7840
7882
  showBackdrop &&
7841
7883
  mode === 'overlay' &&
7884
+ !fixed &&
7842
7885
  m('.sidenav-overlay', {
7843
7886
  style: {
7844
7887
  display: isOpen ? 'block' : 'none',
@@ -7849,49 +7892,206 @@ const Sidenav = () => {
7849
7892
  // Sidenav (using existing materialize structure)
7850
7893
  m('ul.sidenav', {
7851
7894
  id: state.id,
7852
- class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
7895
+ class: [
7896
+ position === 'right' ? 'right-aligned' : '',
7897
+ fixed ? 'sidenav-fixed' : '',
7898
+ expandable && !isExpanded ? 'sidenav-collapsed' : '',
7899
+ className,
7900
+ ]
7853
7901
  .filter(Boolean)
7854
7902
  .join(' ') || undefined,
7855
7903
  style: {
7856
- width: `${width}px`,
7904
+ width: `${currentWidth}px`,
7857
7905
  transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
7858
7906
  'transition-duration': `${animationDuration}ms`,
7907
+ 'transition-property': 'transform, width',
7859
7908
  },
7860
- }, children),
7909
+ }, [
7910
+ // Hamburger toggle button (inside sidenav, at the top)
7911
+ showHamburger &&
7912
+ m('li.sidenav-hamburger-item', {
7913
+ style: {
7914
+ display: 'flex',
7915
+ 'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
7916
+ 'align-items': 'center',
7917
+ padding: '12px 16px',
7918
+ cursor: 'pointer',
7919
+ 'border-bottom': '1px solid rgba(0,0,0,0.1)',
7920
+ },
7921
+ onclick: () => toggleHamburger(attrs),
7922
+ }, m(MaterialIcon, {
7923
+ name: 'menu',
7924
+ style: { width: '24px', height: '24px' },
7925
+ })),
7926
+ // Expand/collapse toggle button (if expandable, right below hamburger)
7927
+ expandable &&
7928
+ m('li.sidenav-expand-toggle', {
7929
+ style: {
7930
+ display: 'flex',
7931
+ 'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
7932
+ 'align-items': 'center',
7933
+ padding: '12px 16px',
7934
+ cursor: 'pointer',
7935
+ 'border-bottom': '1px solid rgba(0,0,0,0.1)',
7936
+ },
7937
+ onclick: () => toggleExpanded(attrs),
7938
+ }, m(MaterialIcon, {
7939
+ name: position === 'right'
7940
+ ? (isExpanded ? 'chevron_right' : 'chevron_left')
7941
+ : (isExpanded ? 'chevron_left' : 'chevron_right'),
7942
+ style: { width: '24px', height: '24px' },
7943
+ })),
7944
+ // Children (menu items) - inject internal props
7945
+ Array.isArray(children)
7946
+ ? children.map((child) => {
7947
+ if (child && typeof child === 'object' && 'tag' in child) {
7948
+ // Clone the vnode and add internal props
7949
+ return Object.assign(Object.assign({}, child), { attrs: Object.assign(Object.assign({}, child.attrs), { _isExpanded: isExpanded, _position: position }) });
7950
+ }
7951
+ return child;
7952
+ })
7953
+ : children,
7954
+ ]),
7861
7955
  ];
7862
7956
  },
7863
7957
  };
7864
7958
  };
7959
+ /**
7960
+ * Sidenav Submenu Item Component
7961
+ */
7962
+ const NavbarSubItem = () => {
7963
+ return {
7964
+ view: ({ attrs }) => {
7965
+ const { text, icon, selected = false, value, onSelect, mode, isExpanded, position = 'left' } = attrs;
7966
+ const handleClick = () => {
7967
+ if (onSelect) {
7968
+ onSelect(value !== undefined ? value : text, !selected);
7969
+ }
7970
+ };
7971
+ const isRightAligned = position === 'right';
7972
+ const submenuContent = isRightAligned
7973
+ ? [
7974
+ // Right-aligned: text on left, icons on right
7975
+ isExpanded && m('span', { style: { 'flex': '1', 'text-align': 'left' } }, text),
7976
+ icon && isExpanded && m('i.material-icons', { style: { 'font-size': '18px' } }, icon),
7977
+ m(MaterialIcon, {
7978
+ name: mode === 'checkbox' ? (selected ? 'check' : 'close') : selected ? 'radio_checked' : 'radio_unchecked',
7979
+ style: {
7980
+ width: '18px',
7981
+ height: '18px',
7982
+ opacity: mode === 'checkbox' && !selected ? '0.3' : '1',
7983
+ },
7984
+ }),
7985
+ ]
7986
+ : [
7987
+ // Left-aligned: indicator on left, text and icon on right
7988
+ m(MaterialIcon, {
7989
+ name: mode === 'checkbox' ? (selected ? 'check' : 'close') : selected ? 'radio_checked' : 'radio_unchecked',
7990
+ style: {
7991
+ width: '18px',
7992
+ height: '18px',
7993
+ opacity: mode === 'checkbox' && !selected ? '0.3' : '1',
7994
+ },
7995
+ }),
7996
+ icon && isExpanded && m('i.material-icons', { style: { 'font-size': '18px', 'margin-left': '8px' } }, icon),
7997
+ isExpanded && m('span', { style: { 'margin-left': icon ? '8px' : '8px' } }, text),
7998
+ ];
7999
+ return m('li.sidenav-subitem', {
8000
+ class: selected ? 'selected' : '',
8001
+ style: {
8002
+ padding: isExpanded ? '8px 16px 8px 48px' : '8px 16px',
8003
+ cursor: 'pointer',
8004
+ display: 'flex',
8005
+ 'align-items': 'center',
8006
+ gap: '8px',
8007
+ 'font-size': '0.9em',
8008
+ 'justify-content': isRightAligned ? 'space-between' : 'flex-start',
8009
+ },
8010
+ onclick: handleClick,
8011
+ }, submenuContent);
8012
+ },
8013
+ };
8014
+ };
7865
8015
  /**
7866
8016
  * Sidenav Item Component
7867
8017
  * Individual items for the sidenav menu
7868
8018
  */
7869
8019
  const SidenavItem = () => {
8020
+ let isSubmenuOpen = false;
7870
8021
  return {
7871
8022
  view: ({ attrs, children }) => {
7872
- const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
8023
+ const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', } = attrs;
7873
8024
  if (divider) {
7874
8025
  return m('li.divider');
7875
8026
  }
7876
8027
  if (subheader) {
7877
8028
  return m('li.subheader', text || children);
7878
8029
  }
7879
- const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
7880
- const content = [icon && m('i.material-icons', icon), text || children];
7881
- if (href && !disabled) {
7882
- return m('li', { class: itemClasses }, [
8030
+ const hasSubmenu = submenu && submenu.length > 0;
8031
+ const itemClasses = [
8032
+ active ? 'active' : '',
8033
+ disabled ? 'disabled' : '',
8034
+ hasSubmenu ? 'has-submenu' : '',
8035
+ className,
8036
+ ]
8037
+ .filter(Boolean)
8038
+ .join(' ') || undefined;
8039
+ const handleMainClick = (e) => {
8040
+ e.preventDefault();
8041
+ if (hasSubmenu) {
8042
+ isSubmenuOpen = active ? !isSubmenuOpen : true;
8043
+ }
8044
+ if (onclick && !disabled) {
8045
+ onclick(e);
8046
+ }
8047
+ };
8048
+ // Get internal props passed from parent Sidenav
8049
+ const isExpanded = attrs._isExpanded !== false;
8050
+ const position = attrs._position || 'left';
8051
+ const isRightAligned = position === 'right';
8052
+ // In expanded mode, icons are at the outside edge
8053
+ // In collapsed mode, icons are centered
8054
+ const content = isRightAligned
8055
+ ? [
8056
+ // Right-aligned: text on left, icon on right
8057
+ isExpanded && m('span.sidenav-item-text', { style: { 'flex': '1', 'text-align': 'left', 'margin-right': '8px' } }, text || children),
8058
+ m('i.material-icons', { style: { 'min-width': '24px', 'width': '24px' } }, icon || ''),
8059
+ ]
8060
+ : [
8061
+ // Left-aligned: icon on left, text on right
8062
+ m('i.material-icons', { style: { 'min-width': '24px', 'width': '24px' } }, icon || ''),
8063
+ isExpanded && m('span.sidenav-item-text', { style: { 'margin-left': '8px', 'flex': '1' } }, text || children),
8064
+ ];
8065
+ const linkStyle = {
8066
+ display: 'flex',
8067
+ 'align-items': 'center',
8068
+ padding: isExpanded ? '12px 16px' : '12px 18px',
8069
+ 'justify-content': isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
8070
+ };
8071
+ const mainItem = href && !disabled
8072
+ ? m('li', { class: itemClasses }, [
7883
8073
  m('a', {
7884
8074
  href,
7885
- onclick: disabled ? undefined : onclick,
8075
+ onclick: handleMainClick,
8076
+ style: linkStyle,
8077
+ }, content),
8078
+ ])
8079
+ : m('li', { class: itemClasses }, [
8080
+ m('a', {
8081
+ onclick: handleMainClick,
8082
+ href: '#!',
8083
+ style: linkStyle,
7886
8084
  }, content),
7887
8085
  ]);
8086
+ // Return main item with submenu if applicable
8087
+ if (hasSubmenu && active && isSubmenuOpen) {
8088
+ return [
8089
+ mainItem,
8090
+ submenu.map((subItem) => m(NavbarSubItem, Object.assign(Object.assign({}, subItem), { mode: submenuMode, isExpanded,
8091
+ position }))),
8092
+ ];
7888
8093
  }
7889
- return m('li', { class: itemClasses }, [
7890
- m('a', {
7891
- onclick: disabled ? undefined : onclick,
7892
- href: '#!',
7893
- }, content),
7894
- ]);
8094
+ return mainItem;
7895
8095
  },
7896
8096
  };
7897
8097
  };