material 0.8.4 → 0.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "material",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "A lightweight implementation of Material Design Components for the web - ES6",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/card.js CHANGED
@@ -1,36 +1,28 @@
1
- // import modules
2
- import create from './mixin/create'
3
- import insert from './mixin/insert'
4
- // import components
5
- import Layout from './layout'
6
-
7
- const defaults = {
8
- prefix: 'material',
9
- class: 'card',
10
- tag: 'div'
11
- }
1
+ import build from './module/build'
2
+ import display from './mixin/display'
3
+ import position from './mixin/position'
12
4
 
13
5
  class Card {
6
+ static defaults = {
7
+ class: 'card',
8
+ position: {
9
+ align: 'center',
10
+ vAlign: 'dynamic',
11
+ offsetX: 10,
12
+ offsetY: 10
13
+ },
14
+ close: true,
15
+ layout: []
16
+ }
17
+
14
18
  constructor (options) {
15
19
  this.init(options)
16
20
  this.build()
17
21
  }
18
22
 
19
23
  init (options) {
20
- this.options = Object.assign({}, defaults, options || {})
21
- Object.assign(this, insert)
22
- }
23
-
24
- /**
25
- * build the component using the super method
26
- * @return {Object} The class instance
27
- */
28
- build () {
29
- this.element = create(this.options)
30
-
31
- if (this.options.layout) {
32
- this.layout = new Layout(this.options.layout, this.element)
33
- }
24
+ this.options = Object.assign({}, Card.defaults, options || {})
25
+ Object.assign(this, build, display, position)
34
26
  }
35
27
  }
36
28
 
package/src/element.js CHANGED
@@ -7,6 +7,9 @@ class Element {
7
7
  this.options = Object.assign({}, options || {})
8
8
  // console.log('element options', options)
9
9
  const element = document.createElement(this.options.tag || 'div')
10
+
11
+ const container = this.options.container
12
+ delete this.options.container
10
13
  delete this.options.tag
11
14
 
12
15
  if (options.html) element.innerHTML = this.options.html
@@ -17,14 +20,12 @@ class Element {
17
20
 
18
21
  if (options.id) element.setAttribute('id', options.id)
19
22
 
20
- for (const property in this.options) {
21
- if (this.options.hasOwnProperty(property)) {
22
- element.setAttribute(property, this.options[property])
23
- }
23
+ for (const [property, value] of Object.entries(this.options)) {
24
+ element.setAttribute(property, value)
24
25
  }
25
26
 
26
- if (this.options.container) {
27
- this.options.container.appendChild(element)
27
+ if (container) {
28
+ container.appendChild(element)
28
29
  }
29
30
 
30
31
  return element
package/src/menu.js CHANGED
@@ -3,6 +3,7 @@ import EventEmitter from './mixin/emitter'
3
3
  import build from './module/build'
4
4
  import events from './module/events'
5
5
  import display from './mixin/display'
6
+ import position from './mixin/position'
6
7
 
7
8
  import Element from './element'
8
9
  import Button from './button'
@@ -10,15 +11,22 @@ import Button from './button'
10
11
  class Menu extends EventEmitter {
11
12
  static defaults = {
12
13
  class: 'menu',
13
- offset: 8,
14
14
  modal: false,
15
+ position: {
16
+ align: 'right',
17
+ vAlign: 'bottom',
18
+ offsetX: 10,
19
+ offsetY: 10
20
+ },
15
21
  layout: [
16
- [Element, 'surface', { class: 'surface' },
17
- [Element, 'list', { tag: 'ul', class: 'items' }]
18
- ]
22
+ [Element, 'list', { tag: 'ul', class: 'items' }]
19
23
  ],
20
24
  events: [
21
- ['element.click', 'close']
25
+ ['element.click', 'onClick'],
26
+ ['element.show', 'close'],
27
+ ['element.mouseenter', 'onMouseEnter'],
28
+ ['element.mouseleave', 'close'],
29
+ ['options.target.mouseleave', 'close']
22
30
  ]
23
31
  }
24
32
 
@@ -30,15 +38,18 @@ class Menu extends EventEmitter {
30
38
  this.setup()
31
39
 
32
40
  this.render(this.options.items)
41
+
42
+ return this
33
43
  }
34
44
 
35
45
  init (options) {
36
46
  this.options = Object.assign({}, Menu.defaults, options || {})
37
- Object.assign(this, build, display)
47
+ Object.assign(this, build, display, position)
38
48
  }
39
49
 
40
50
  setup () {
41
51
  events.attach(this.options.events, this)
52
+ this.closeTimeout = null
42
53
  }
43
54
 
44
55
  render (items) {
@@ -58,42 +69,29 @@ class Menu extends EventEmitter {
58
69
  item = new Element({ tag: 'li', class: 'divider' })
59
70
  } else {
60
71
  obj.container = this.ui.list
61
- obj.class = 'item'
62
- item = new Button(obj).on('click', (ev) => {
63
- // console.log('select', ev.target.name)
64
- this.emit('select', ev.target.name)
65
- })
72
+ item = new Element(obj).classList.add('item')
66
73
  }
67
74
 
68
75
  return this
69
76
  }
70
77
 
71
- position (target) {
72
- if (!target) return
73
- const caller = target.getBoundingClientRect()
74
- const screen = this.ui.surface.getBoundingClientRect()
75
- const menu = this.ui.list.getBoundingClientRect()
76
-
77
- // Calculate top position
78
- let top = caller.top + caller.height
79
- if (top + menu.height > window.innerHeight) {
80
- top = window.innerHeight - menu.height - this.options.offset
81
- }
82
- this.ui.surface.style.top = top + 'px'
78
+ onMouseEnter () {
79
+ clearTimeout(this.closeTimeout)
80
+ }
83
81
 
84
- // Calculate left position
85
- let left = caller.left - caller.width + menu.width - screen.width + caller.width
86
- if (left + menu.width > window.innerWidth) {
87
- left = window.innerWidth - menu.width - this.options.offset
82
+ onClick (ev) {
83
+ // console.log('onClick', ev.target, ev.target.getAttribute('name'))
84
+ const name = ev.target.getAttribute('name')
85
+ if (name) {
86
+ this.emit('select', name)
88
87
  }
89
- this.ui.surface.style.left = left + 'px'
90
-
91
- return this
88
+ this.destroy()
92
89
  }
93
90
 
94
91
  close () {
95
- // console.log('close')
96
- this.destroy()
92
+ this.closeTimeout = setTimeout(() => {
93
+ this.destroy()
94
+ }, 200)
97
95
  }
98
96
  }
99
97
 
@@ -0,0 +1,91 @@
1
+ export default {
2
+ position (target, position) {
3
+ target = target || this.options.target
4
+ position = position || this.options.position
5
+ const { offsetX = 0, offsetY = 0, align = 'center', vAlign = 'middle' } = position
6
+
7
+ const name = this.constructor.name
8
+ console.log('constructor', name)
9
+
10
+ if (!target) return
11
+
12
+ const caller = target.getBoundingClientRect()
13
+ const screen = this.element.parentNode.getBoundingClientRect()
14
+ const element = this.element.getBoundingClientRect()
15
+
16
+ console.log('caller', caller)
17
+ console.log('screen', screen)
18
+ console.log('element', element)
19
+
20
+ let left, top
21
+
22
+ console.log('align', align)
23
+ console.log('target left', caller.left)
24
+
25
+ switch (align) {
26
+ case 'left':
27
+ left = caller.left + caller.width
28
+ break
29
+ case 'right':
30
+ left = caller.left
31
+ if (left + element.width + offsetX > screen.width) {
32
+ console.log('out boundaries')
33
+ left = caller.left - element.width + caller.width
34
+ }
35
+ break
36
+ case 'center':
37
+ left = caller.left + (caller.width / 2) - (element.width / 2)
38
+
39
+ default:
40
+ left = caller.left + (caller.width / 2) - (element.width / 2)
41
+ }
42
+
43
+ if (left < 0) left = offsetX
44
+
45
+ if (left + element.width + offsetX > screen.width) {
46
+ left = caller.left - element.width + caller.width - offsetX
47
+ }
48
+
49
+ console.log('left', left)
50
+
51
+ // left = Math.max(offsetX, Math.min(left, screen.width - element.width - offsetX))
52
+
53
+ const scrollY = window.scrollY
54
+
55
+ // console.log('class', name)
56
+ // console.log('scrollY', scrollY)
57
+ // console.log('vAlign', vAlign)
58
+ // console.log('target top', caller.top)
59
+
60
+ switch (vAlign) {
61
+ case 'top':
62
+ top = caller.top + scrollY - element.height
63
+ break
64
+ case 'bottom':
65
+ top = caller.top + caller.height + scrollY
66
+ break
67
+ case 'middle':
68
+ case 'dynamic':
69
+ if (caller.top + caller.height / 2 < screen.height / 2) {
70
+ top = caller.bottom + scrollY // Positionner en dessous si dans la moitié supérieure
71
+ } else {
72
+ top = caller.top + scrollY - element.height // Positionner au-dessus si dans la moitié inférieure
73
+ }
74
+ break
75
+
76
+ default:
77
+ top = caller.top + scrollY + (caller.height / 2) - (element.height / 2)
78
+ }
79
+
80
+ // if (this.options.class !== 'menu') {
81
+ // top = Math.max(offsetY, Math.min(top, screen.height - element.height - offsetY))
82
+ // }
83
+
84
+ console.log('top', top)
85
+
86
+ this.element.style.left = `${left}px`
87
+ this.element.style.top = `${top}px`
88
+
89
+ return this
90
+ }
91
+ }
@@ -1,11 +1,21 @@
1
+ /**
2
+ * Sets attributes on a DOM element based on provided options.
3
+ *
4
+ * This function iterates over a list of attributes and sets them on the specified element.
5
+ * Special attributes such as 'required', 'disabled', 'multiple', 'checked' are set as boolean attributes,
6
+ * while others are set with their corresponding values from the options.
7
+ *
8
+ * @param {Element} element - The DOM element to which attributes will be applied.
9
+ * @param {Object} options - An object containing the attributes and their values.
10
+ */
1
11
  const special = ['required', 'disabled', 'multiple', 'checked']
2
12
 
3
13
  const attributes = (element, options) => {
4
- if (!element || !options.attributes) return
14
+ if (!element || !options?.attributes) return
5
15
 
6
16
  options.attributes.forEach(attribute => {
7
- if (options[attribute] && String(options[attribute]) !== 'undefined') {
8
- const value = special.includes(attribute) ? attribute : options[attribute]
17
+ if (options[attribute] !== undefined) {
18
+ const value = special.includes(attribute) ? '' : options[attribute]
9
19
  element.setAttribute(attribute, value)
10
20
  }
11
21
  })
@@ -1,11 +1,18 @@
1
-
2
- function dataset (element, data) {
1
+ /**
2
+ * Applies data attributes to a DOM element based on an object's properties.
3
+ *
4
+ * This function takes an object and applies its key-value pairs as data attributes
5
+ * to a provided DOM element. Only the object's own properties (not inherited properties)
6
+ * are applied.
7
+ *
8
+ * @param {Element} element - The DOM element to which data attributes will be applied.
9
+ * @param {Object} data - An object containing key-value pairs to be applied as data attributes.
10
+ */
11
+ const dataset = (element, data) => {
3
12
  if (data) {
4
- for (const property in data) {
5
- if (data.hasOwnProperty(property)) {
6
- element.dataset[property] = data[property]
7
- }
8
- }
13
+ Object.entries(data).forEach(([key, value]) => {
14
+ element.dataset[key] = value
15
+ })
9
16
  }
10
17
  }
11
18
 
@@ -6,6 +6,8 @@ const previousDevice = window.device
6
6
  const device = {}
7
7
  const os = ['ios', 'iphone', 'ipad', 'ipod', 'android', 'blackberry', 'macos', 'windows', 'fxos', 'meego', 'television']
8
8
  const type = ['mobile', 'tablet', 'desktop']
9
+ const television = ['googletv', 'viera', 'smarttv', 'internet.tv', 'netcast', 'nettv', 'appletv', 'boxee', 'kylo', 'roku', 'dlnadoc', 'pov_tv', 'hbbtv', 'ce-html']
10
+
9
11
  const changeOrientationList = []
10
12
 
11
13
  // Add device as a global object.
@@ -19,22 +21,6 @@ const documentElement = window.document.documentElement
19
21
  let userAgent = window.navigator.userAgent.toLowerCase()
20
22
 
21
23
  // Detectable television devices.
22
- const television = [
23
- 'googletv',
24
- 'viera',
25
- 'smarttv',
26
- 'internet.tv',
27
- 'netcast',
28
- 'nettv',
29
- 'appletv',
30
- 'boxee',
31
- 'kylo',
32
- 'roku',
33
- 'dlnadoc',
34
- 'pov_tv',
35
- 'hbbtv',
36
- 'ce-html'
37
- ]
38
24
 
39
25
  // Main functions
40
26
  // --------------
package/src/module/dot.js CHANGED
@@ -1,20 +1,32 @@
1
+ /**
2
+ * Converts a nested object into a flat object with dotted paths as keys.
3
+ *
4
+ * @param {Object} obj - The object to be flattened.
5
+ * @param {string} [base=''] - (Internal use) A prefix used to build the dotted paths. Users typically do not need to provide this.
6
+ * @returns {Object} A new object with the same values as `obj`, but flattened.
7
+ *
8
+ * @example
9
+ * const nested = { a: { b: { c: 1 }, d: 2 }, e: 3 };
10
+ * const flat = dot(nested);
11
+ * // flat is now { 'a.b.c': 1, 'a.d': 2, 'e': 3 }
12
+ *
13
+ * @example
14
+ * // It does not flatten arrays or date objects
15
+ * const complex = { a: [{ b: 1 }, { c: 2 }], d: new Date() };
16
+ * const flatComplex = dot(complex);
17
+ * // flatComplex is now { 'a': [{ b: 1 }, { c: 2 }], 'd': [Date Object] }
18
+ */
1
19
 
2
- function dot (obj) {
3
- const res = {}
4
- function recurse (obj, current) {
5
- for (const key in obj) {
6
- const value = obj[key]
7
- const newKey = (current ? current + '.' + key : key)
8
- if (value && typeof value === 'object') {
9
- recurse(value, newKey)
10
- } else {
11
- res[newKey] = value
12
- }
20
+ const dot = (obj, base = '') => {
21
+ return Object.entries(obj).reduce((acc, [key, value]) => {
22
+ const newKey = base ? `${base}.${key}` : key
23
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
24
+ Object.assign(acc, dot(value, newKey))
25
+ } else {
26
+ acc[newKey] = value
13
27
  }
14
- }
15
-
16
- recurse(obj)
17
- return res
28
+ return acc
29
+ }, {})
18
30
  }
19
31
 
20
32
  export default dot
@@ -2,11 +2,17 @@ import extract from './extract'
2
2
  import last from './last'
3
3
 
4
4
  const events = {
5
- eventHandlers: [], // Utiliser un tableau pour stocker les événements et les gestionnaires
5
+ eventHandlers: {}, // Utiliser un tableau pour stocker les événements et les gestionnaires
6
6
 
7
7
  attach (eventsArray, context) {
8
8
  if (!eventsArray) return
9
9
 
10
+ // Ensure each context has a unique UID
11
+ const uid = context._uid || (context._uid = context.options.class + Math.random().toString(36).substr(2, 9))
12
+
13
+ // Initialize storage for this context if it doesn't already exist
14
+ this.eventHandlers[uid] = this.eventHandlers[uid] || {}
15
+
10
16
  eventsArray.forEach(([eventDef, funcDef, option]) => {
11
17
  const e = extract.e(context, eventDef)
12
18
  const isFunction = typeof funcDef === 'function'
@@ -20,7 +26,6 @@ const events = {
20
26
  }
21
27
 
22
28
  const bound = last(keys.join('.'), context)
23
-
24
29
  let handler = null
25
30
 
26
31
  if (isFunction) {
@@ -28,18 +33,17 @@ const events = {
28
33
  } else if (f && bound) {
29
34
  handler = f.bind(bound)
30
35
  } else {
31
- // console.error(`Can't bind function for ${eventDef}`)
32
- return
36
+ return // Cannot bind function for eventDef
33
37
  }
34
38
 
35
- if (handler && e && e.element && e.element.addEventListener) {
36
- e.element.addEventListener(e.name, handler, option)
37
- this.eventHandlers.push({ eventDef, element: e.element, handler })
38
- } else if (e && e.element && e.element.on && handler) {
39
- e.element.on(e.name, handler)
40
- this.eventHandlers.push({ eventDef, element: e.element, handler })
41
- } else {
42
- // console.error(`Can't attach ${eventDef}`)
39
+ if (!this.eventHandlers[uid][eventDef]) {
40
+ if (handler && e && e.element?.addEventListener) {
41
+ e.element.addEventListener(e.name, handler, option)
42
+ this.eventHandlers[uid][eventDef] = handler
43
+ } else if (e && e.element?.on && handler) {
44
+ e.element.on(e.name, handler)
45
+ this.eventHandlers[uid][eventDef] = handler
46
+ }
43
47
  }
44
48
  })
45
49
 
@@ -47,20 +51,26 @@ const events = {
47
51
  },
48
52
 
49
53
  detach (eventsArray, context) {
50
- (eventsArray || []).forEach(([eventDef]) => {
51
- this.eventHandlers.forEach((handlerObj, index) => {
52
- if (handlerObj.eventDef === eventDef) {
53
- const { element, handler } = handlerObj
54
- if (element.removeEventListener) {
55
- element.removeEventListener(eventDef, handler)
56
- } else if (element.off) {
57
- element.off(eventDef, handler)
58
- }
59
- delete this.eventHandlers[index]
60
- }
61
- })
62
- this.eventHandlers = this.eventHandlers.filter(Boolean) // Nettoyer les entrées supprimées
63
- })
54
+ const uid = context._uid
55
+ if (!this.eventHandlers[uid]) return
56
+
57
+ eventsArray.forEach(([eventDef, funcDef, option]) => {
58
+ const e = extract.e(context, eventDef)
59
+ const handler = this.eventHandlers[uid][eventDef]
60
+ if (handler) {
61
+ e.element.removeEventListener(e.name, handler)
62
+ } else if (e.element.off) {
63
+ e.element.off(e.name, handler)
64
+ }
65
+
66
+ delete this.eventHandlers[uid][eventDef]
67
+ }
68
+ )
69
+ },
70
+
71
+ list (context) {
72
+ const uid = context?._uid
73
+ return uid ? this.eventHandlers[uid] || {} : this.eventHandlers
64
74
  }
65
75
  }
66
76
 
@@ -2,26 +2,17 @@
2
2
  //display: none
3
3
  &.show
4
4
  //display: block
5
- .surface
6
- .list
7
- background: white
8
- .item
9
- color: rgba(0,0,0,.87)
5
+ .list
6
+ background: white
7
+ .item
8
+ color: rgba(0,0,0,.87)
10
9
 
11
10
  &.floating
12
- position: fixed
13
- top: 0
14
- left: 0
15
- bottom: 0
16
- right: 0
17
11
  display: flex
18
12
  align-items: center
19
13
  justify-content: center
20
14
  overflow: hidden
21
15
  opacity: 0
22
16
  transition: 0.2s
23
- .surface
24
- position: absolute
25
- background-color: white
26
17
 
27
18
 
package/src/switcher.js CHANGED
@@ -66,7 +66,7 @@ class Switcher extends EventEmitter {
66
66
  }
67
67
 
68
68
  setup () {
69
- console.log('events.attach', this.options.events)
69
+ // console.log('events.attach', this.options.events)
70
70
  events.attach(this.options.events, this)
71
71
 
72
72
  if (this.options.default) {
@@ -75,7 +75,7 @@ class Switcher extends EventEmitter {
75
75
  }
76
76
 
77
77
  click (event) {
78
- console.log('click', event)
78
+ // console.log('click', event)
79
79
  if (!event.target.dataset.switcher) return
80
80
  this.select(event.target)
81
81
  }